Merge branch 'development' into LogBook_v2
Conflicts: config.py eos/saveddata/fit.py gui/bitmapLoader.py gui/graphFrame.py gui/utils/exportHtml.py pyfa.py service/crest.py service/price.py service/server.py
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -49,7 +49,6 @@ Pyfa.egg-info/
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
@@ -51,8 +51,7 @@ pyfa is licensed under the GNU GPL v3.0, see LICENSE
|
||||
|
||||
## Resources
|
||||
* Development repository: [https://github.com/pyfa-org/Pyfa](https://github.com/pyfa-org/Pyfa)
|
||||
* XMPP conference: [pyfa@conference.jabber.org](pyfa@conference.jabber.org)
|
||||
* [EVE forum thread](http://forums.eveonline.com/default.aspx?g=posts&t=247609)
|
||||
* [EVE forum thread](https://forums.eveonline.com/default.aspx?g=posts&t=466425)
|
||||
* [EVE University guide using pyfa](http://wiki.eveuniversity.org/Guide_to_using_PYFA)
|
||||
* [EVE Online website](http://www.eveonline.com/)
|
||||
|
||||
|
||||
38
config.py
38
config.py
@@ -19,10 +19,10 @@ debug = False
|
||||
saveInRoot = False
|
||||
|
||||
# Version data
|
||||
version = "1.26.1"
|
||||
version = "1.27.2"
|
||||
tag = "git"
|
||||
expansionName = "YC118.10"
|
||||
expansionVersion = "1.2"
|
||||
expansionName = "YC119.2"
|
||||
expansionVersion = "1.4"
|
||||
evemonMinVersion = "4081"
|
||||
|
||||
pyfaPath = None
|
||||
@@ -43,6 +43,17 @@ def __createDirs(path):
|
||||
os.makedirs(path)
|
||||
|
||||
|
||||
def getPyfaRoot():
|
||||
base = getattr(sys.modules['__main__'], "__file__", sys.executable) if isFrozen() else sys.argv[0]
|
||||
root = os.path.dirname(os.path.realpath(os.path.abspath(base)))
|
||||
root = unicode(root, sys.getfilesystemencoding())
|
||||
return root
|
||||
|
||||
|
||||
def getDefaultSave():
|
||||
return unicode(os.path.expanduser(os.path.join("~", ".pyfa")), sys.getfilesystemencoding())
|
||||
|
||||
|
||||
def defPaths(customSavePath):
|
||||
global debug
|
||||
global pyfaPath
|
||||
@@ -57,37 +68,36 @@ def defPaths(customSavePath):
|
||||
# Python 2.X uses ANSI by default, so we need to convert the character encoding
|
||||
pyfaPath = getattr(configforced, "pyfaPath", pyfaPath)
|
||||
if pyfaPath is None:
|
||||
pyfaPath = getPyfaPath()
|
||||
pyfaPath = getPyfaRoot()
|
||||
|
||||
# Where we store the saved fits etc, default is the current users home directory
|
||||
if saveInRoot is True:
|
||||
savePath = getattr(configforced, "savePath", None)
|
||||
if savePath is None:
|
||||
savePath = getPyfaPath("saveddata")
|
||||
savePath = os.path.join(pyfaPath, "saveddata")
|
||||
else:
|
||||
savePath = getattr(configforced, "savePath", None)
|
||||
if savePath is None:
|
||||
if customSavePath is None: # customSavePath is not overriden
|
||||
savePath = os.path.expanduser(os.path.join("~", ".pyfa"))
|
||||
savePath = getDefaultSave()
|
||||
else:
|
||||
savePath = customSavePath
|
||||
|
||||
__createDirs(savePath)
|
||||
|
||||
if isFrozen():
|
||||
certName = "cacert.pem"
|
||||
os.environ["REQUESTS_CA_BUNDLE"] = getPyfaPath(certName).encode('utf8')
|
||||
os.environ["SSL_CERT_FILE"] = getPyfaPath(certName).encode('utf8')
|
||||
os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(pyfaPath, "cacert.pem").encode('utf8')
|
||||
os.environ["SSL_CERT_FILE"] = os.path.join(pyfaPath, "cacert.pem").encode('utf8')
|
||||
|
||||
# The database where we store all the fits etc
|
||||
saveDB = getSavePath("saveddata.db")
|
||||
saveDB = os.path.join(savePath, "saveddata.db")
|
||||
|
||||
# The database where the static EVE data from the datadump is kept.
|
||||
# This is not the standard sqlite datadump but a modified version created by eos
|
||||
# maintenance script
|
||||
gameDB = getPyfaPath("eve.db")
|
||||
gameDB = os.path.join(pyfaPath, "eve.db")
|
||||
|
||||
# DON'T MODIFY ANYTHING BELOW!
|
||||
## DON'T MODIFY ANYTHING BELOW ##
|
||||
import eos.config
|
||||
|
||||
# Caching modifiers, disable all gamedata caching, its unneeded.
|
||||
@@ -96,7 +106,8 @@ def defPaths(customSavePath):
|
||||
eos.config.saveddata_connectionstring = "sqlite:///" + saveDB + "?check_same_thread=False"
|
||||
eos.config.gamedata_connectionstring = "sqlite:///" + gameDB + "?check_same_thread=False"
|
||||
|
||||
|
||||
# Keeping disabled code here for now until we can determine with decent certainty that this isn't needed
|
||||
'''
|
||||
def getPyfaPath(Append=None):
|
||||
base = getattr(sys.modules['__main__'], "__file__", sys.executable) if isFrozen() else sys.argv[0]
|
||||
root = os.path.dirname(os.path.realpath(os.path.abspath(base)))
|
||||
@@ -133,3 +144,4 @@ def parsePath(root, Append=None):
|
||||
path = path.decode('windows-1252')
|
||||
|
||||
return path
|
||||
'''
|
||||
5666
dist_assets/cacert.pem
Normal file
5666
dist_assets/cacert.pem
Normal file
File diff suppressed because it is too large
Load Diff
10
eos/db/migrations/upgrade21.py
Normal file
10
eos/db/migrations/upgrade21.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Migration 21
|
||||
|
||||
- Fixes discrepancy in drone table where we may have an amount active that is not equal to the amount in the stack
|
||||
(we don't support activating only 2/5 drones). See GH issue #728
|
||||
"""
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
saveddata_engine.execute("UPDATE drones SET amountActive = amount where amountActive > 0 AND amountActive <> amount;")
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoInfluenceCapNeed
|
||||
#
|
||||
# Used by:
|
||||
# Items from category: Charge (465 of 899)
|
||||
# Items from category: Charge (465 of 912)
|
||||
# Charges from group: Frequency Crystal (185 of 185)
|
||||
# Charges from group: Hybrid Charge (209 of 209)
|
||||
type = "passive"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoInfluenceRange
|
||||
#
|
||||
# Used by:
|
||||
# Items from category: Charge (571 of 899)
|
||||
# Items from category: Charge (571 of 912)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoSpeedMultiplier
|
||||
#
|
||||
# Used by:
|
||||
# Charges from group: Festival Charges (9 of 9)
|
||||
# Charges from group: Festival Charges (22 of 22)
|
||||
# Charges from group: Interdiction Probe (2 of 2)
|
||||
# Charges from group: Survey Probe (3 of 3)
|
||||
type = "passive"
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
# Used by:
|
||||
# Modules from group: Armor Coating (202 of 202)
|
||||
# Modules from group: Armor Plating Energized (187 of 187)
|
||||
# Modules named like: QA Multiship Module Players (4 of 4)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# armorRepairAmountBonusSubcap
|
||||
#
|
||||
# Used by:
|
||||
# Implants named like: Grade Asklepian (15 of 16)
|
||||
# Implants named like: grade Asklepian (15 of 18)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Data Miners (15 of 16)
|
||||
# Module: QA Cross Protocol Analyzer
|
||||
type = "active"
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Automated Targeting System (6 of 6)
|
||||
# Module: QA Damage Module
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Heat Sink (18 of 18)
|
||||
# Modules named like: QA Multiship Module Players (4 of 4)
|
||||
# Module: QA Damage Module
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Tracking Enhancer (10 of 10)
|
||||
# Module: QA Damage Module
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Tracking Enhancer (10 of 10)
|
||||
# Module: QA Damage Module
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Tracking Enhancer (10 of 10)
|
||||
# Module: QA Damage Module
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Magnetic Field Stabilizer (14 of 14)
|
||||
# Modules named like: QA Multiship Module Players (4 of 4)
|
||||
# Module: QA Damage Module
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ type = "passive"
|
||||
|
||||
|
||||
def handler(fit, src, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining Foreman"), "warfareBuff4Multiplier",
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining Foreman"), "warfareBuff4Value",
|
||||
src.getModifiedItemAttr("shipBonusICS2"), skill="Industrial Command Ships")
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining Foreman"), "warfareBuff1Multiplier",
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining Foreman"), "warfareBuff1Value",
|
||||
src.getModifiedItemAttr("shipBonusICS2"), skill="Industrial Command Ships")
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining Foreman"), "buffDuration",
|
||||
src.getModifiedItemAttr("shipBonusICS2"), skill="Industrial Command Ships")
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining Foreman"), "warfareBuff3Multiplier",
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining Foreman"), "warfareBuff3Value",
|
||||
src.getModifiedItemAttr("shipBonusICS2"), skill="Industrial Command Ships")
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining Foreman"), "warfareBuff2Multiplier",
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining Foreman"), "warfareBuff2Value",
|
||||
src.getModifiedItemAttr("shipBonusICS2"), skill="Industrial Command Ships")
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Ballistic Control system (17 of 17)
|
||||
# Modules named like: QA Multiship Module Players (4 of 4)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
# Modules from group: Reactor Control Unit (22 of 22)
|
||||
# Modules from group: Shield Recharger (4 of 4)
|
||||
# Modules named like: Flux Coil (12 of 12)
|
||||
# Modules named like: QA Multiship Module Players (4 of 4)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# remoteWeaponDisruptEntity
|
||||
# npcEntityWeaponDisruptor
|
||||
#
|
||||
# Used by:
|
||||
# Drones named like: TD (3 of 3)
|
||||
@@ -11,8 +11,6 @@
|
||||
# Modules from group: Smart Bomb (118 of 118)
|
||||
# Modules from group: Warp Disrupt Field Generator (7 of 7)
|
||||
# Modules named like: Ancillary Remote (8 of 8)
|
||||
# Module: QA Remote Armor Repair System - 5 Players
|
||||
# Module: QA Shield Transporter - 5 Players
|
||||
# Module: Reactive Armor Hardener
|
||||
# Module: Target Spectrum Breaker
|
||||
type = "overheat"
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Gyrostabilizer (13 of 13)
|
||||
# Modules named like: QA Multiship Module Players (4 of 4)
|
||||
# Module: QA Damage Module
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
# scanStrengthBonusPercentActivate
|
||||
#
|
||||
# Used by:
|
||||
# Module: QA ECCM
|
||||
# Not used by any item
|
||||
type = "active"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# scanStrengthBonusPercentPassive
|
||||
#
|
||||
# Used by:
|
||||
# Implants named like: High grade (20 of 61)
|
||||
# Implants named like: High grade (20 of 66)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# setBonusAsklepian
|
||||
#
|
||||
# Used by:
|
||||
# Implants named like: Grade Asklepian (16 of 16)
|
||||
# Implants named like: grade Asklepian Omega (2 of 2)
|
||||
# Implants named like: grade Asklepian (18 of 18)
|
||||
runTime = "early"
|
||||
type = "passive"
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
# shieldTransfer
|
||||
#
|
||||
# Used by:
|
||||
# Module: QA Shield Transporter - 5 Players
|
||||
# Not used by any item
|
||||
type = "projected", "active"
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# remoteGuidanceDisruptFalloff
|
||||
# shipModuleGuidanceDisruptor
|
||||
#
|
||||
# Used by:
|
||||
# Variations of module: Guidance Disruptor I (6 of 6)
|
||||
@@ -1,4 +1,4 @@
|
||||
# remoteTrackingAssistFalloff
|
||||
# shipModuleRemoteTrackingComputer
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Remote Tracking Computer (8 of 8)
|
||||
@@ -1,4 +1,4 @@
|
||||
# remoteTrackingDisruptFalloff
|
||||
# shipModuleTrackingDisruptor
|
||||
#
|
||||
# Used by:
|
||||
# Variations of module: Tracking Disruptor I (6 of 6)
|
||||
@@ -2,7 +2,6 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Signal Amplifier (7 of 7)
|
||||
# Module: QA Damage Module
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# standardMissilesSkillBoostMissileVelocityBonus
|
||||
#
|
||||
# Used by:
|
||||
# Skill: Defender Missiles
|
||||
type = "passive"
|
||||
|
||||
|
||||
def handler(fit, skill, context):
|
||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Defender Missiles"),
|
||||
"maxVelocity", skill.getModifiedItemAttr("missileVelocityBonus") * skill.level)
|
||||
@@ -3,7 +3,6 @@
|
||||
# Used by:
|
||||
# Implants named like: Inherent Implants 'Noble' Repair Proficiency RP (6 of 6)
|
||||
# Modules named like: Auxiliary Nano Pump (8 of 8)
|
||||
# Modules named like: QA Multiship Module Players (4 of 4)
|
||||
# Implant: Imperial Navy Modified 'Noble' Implant
|
||||
type = "passive"
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
# Used by:
|
||||
# Modules from group: Nanofiber Internal Structure (7 of 7)
|
||||
# Modules from group: Reinforced Bulkhead (8 of 8)
|
||||
# Modules named like: QA Multiship Module Players (4 of 4)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
# targetArmorRepair
|
||||
#
|
||||
# Used by:
|
||||
# Module: QA Remote Armor Repair System - 5 Players
|
||||
# Not used by any item
|
||||
type = "projected", "active"
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Not used by any item
|
||||
type = "projected", "active"
|
||||
|
||||
|
||||
def handler(fit, container, context):
|
||||
if "projected" in context:
|
||||
fit.modules.filteredItemMultiply(lambda mod: mod.item.requiresSkill("Gunnery"),
|
||||
"trackingSpeed", container.getModifiedItemAttr("trackingSpeedMultiplier"),
|
||||
stackingPenalties=True, penaltyGroup="postMul")
|
||||
fit.modules.filteredItemMultiply(lambda mod: mod.item.requiresSkill("Gunnery"),
|
||||
"maxRange", container.getModifiedItemAttr("maxRangeMultiplier"),
|
||||
stackingPenalties=True, penaltyGroup="postMul")
|
||||
fit.modules.filteredItemMultiply(lambda mod: mod.item.requiresSkill("Gunnery"),
|
||||
"falloff", container.getModifiedItemAttr("fallofMultiplier"),
|
||||
stackingPenalties=True, penaltyGroup="postMul")
|
||||
@@ -3,7 +3,7 @@
|
||||
# Used by:
|
||||
# Modules from group: Missile Launcher Heavy (12 of 12)
|
||||
# Modules from group: Missile Launcher Rocket (15 of 15)
|
||||
# Modules named like: Launcher (151 of 151)
|
||||
# Modules named like: Launcher (153 of 153)
|
||||
type = 'active', "projected"
|
||||
|
||||
|
||||
|
||||
@@ -47,9 +47,6 @@ class Effect(EqBase):
|
||||
# Filter to change names of effects to valid python method names
|
||||
nameFilter = re.compile("[^A-Za-z0-9]")
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
"""
|
||||
@@ -217,9 +214,6 @@ class Item(EqBase):
|
||||
|
||||
MOVE_ATTR_INFO = None
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def getMoveAttrInfo(cls):
|
||||
info = getattr(cls, "MOVE_ATTR_INFO", None)
|
||||
@@ -460,8 +454,6 @@ class Category(EqBase):
|
||||
|
||||
|
||||
class AlphaClone(EqBase):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
@@ -490,8 +482,6 @@ class Icon(EqBase):
|
||||
|
||||
|
||||
class MarketGroup(EqBase):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return u"MarketGroup(ID={}, name={}, parent={}) at {}".format(
|
||||
@@ -504,9 +494,6 @@ class MetaGroup(EqBase):
|
||||
|
||||
|
||||
class MetaType(EqBase):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -17,15 +17,9 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
import decimal
|
||||
from math import floor
|
||||
|
||||
|
||||
def floorFloat(value):
|
||||
"""Round float down to integer"""
|
||||
# We have to convert float to str to keep compatibility with
|
||||
# decimal module in python 2.6
|
||||
value = str(value)
|
||||
# Do the conversions for proper rounding down, avoiding float
|
||||
# representation errors
|
||||
result = int(decimal.Decimal(value).to_integral_value(rounding=decimal.ROUND_DOWN))
|
||||
result = int(floor(value))
|
||||
return result
|
||||
|
||||
@@ -140,7 +140,17 @@ class Character(object):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.savedName if not self.isDirty else "{} *".format(self.savedName)
|
||||
name = self.savedName
|
||||
|
||||
if self.isDirty:
|
||||
name += " *"
|
||||
|
||||
if self.alphaCloneID:
|
||||
clone = eos.db.getAlphaClone(self.alphaCloneID)
|
||||
type = clone.alphaCloneName.split()[1]
|
||||
name += u' (\u03B1{})'.format(type[0].upper())
|
||||
|
||||
return name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
|
||||
@@ -515,7 +515,7 @@ class Fit(object):
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Sensor Dampener",
|
||||
attr, value)
|
||||
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.gorup.name == "Target Painter",
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Target Painter",
|
||||
"signatureRadiusBonus", value, stackingPenalties=True)
|
||||
|
||||
if warfareBuffID == 18: # Information Burst: Electronic Hardening: Scan Strength
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
import cStringIO
|
||||
import os.path
|
||||
import zipfile
|
||||
from config import parsePath
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
@@ -38,7 +37,7 @@ except ImportError:
|
||||
|
||||
class BitmapLoader(object):
|
||||
try:
|
||||
archive = zipfile.ZipFile(config.getPyfaPath('imgs.zip'), 'r')
|
||||
archive = zipfile.ZipFile(os.path.join(config.pyfaPath, 'imgs.zip', 'r'))
|
||||
logging.info("Using zipped image files.")
|
||||
except IOError:
|
||||
logging.info("Using local image files.")
|
||||
@@ -83,7 +82,7 @@ class BitmapLoader(object):
|
||||
filename = "{0}.png".format(name)
|
||||
|
||||
if cls.archive:
|
||||
path = parsePath(location, filename)
|
||||
path = os.path.join(location, filename)
|
||||
if os.sep != "/" and os.sep in path:
|
||||
path = path.replace(os.sep, "/")
|
||||
|
||||
@@ -94,7 +93,7 @@ class BitmapLoader(object):
|
||||
except KeyError:
|
||||
print("Missing icon file from zip: {0}".format(path))
|
||||
else:
|
||||
path = config.getPyfaPath('imgs' + os.sep + location + os.sep + filename)
|
||||
path = os.path.join(config.pyfaPath, 'imgs' + os.sep + location + os.sep + filename)
|
||||
|
||||
if os.path.exists(path):
|
||||
return wx.Image(path)
|
||||
|
||||
@@ -52,6 +52,7 @@ class AmountChanger(wx.Dialog):
|
||||
def change(self, event):
|
||||
if self.input.GetLineText(0).strip() == '':
|
||||
event.Skip()
|
||||
self.Close()
|
||||
return
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
@@ -68,6 +69,7 @@ class AmountChanger(wx.Dialog):
|
||||
wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
event.Skip()
|
||||
self.Close()
|
||||
|
||||
# checks to make sure it's valid number
|
||||
@staticmethod
|
||||
|
||||
@@ -197,7 +197,6 @@ class PFGeneralPref(PreferenceView):
|
||||
|
||||
def onPriceSelection(self, event):
|
||||
system = self.chPriceSystem.GetString(self.chPriceSystem.GetSelection())
|
||||
Price.currentSystemId = Price.systemsList.get(system)
|
||||
self.sFit.serviceFittingOptions["priceSystem"] = system
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
@@ -180,27 +180,27 @@ class ContextMenu(object):
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from gui.builtinContextMenus import ( # noqa: E402,F401
|
||||
ammoPattern,
|
||||
amount,
|
||||
cargo,
|
||||
changeAffectingSkills,
|
||||
damagePattern,
|
||||
droneRemoveStack,
|
||||
droneSplit,
|
||||
factorReload,
|
||||
fighterAbilities,
|
||||
implantSets,
|
||||
itemRemove,
|
||||
itemStats,
|
||||
marketJump,
|
||||
metaSwap,
|
||||
moduleAmmoPicker,
|
||||
moduleGlobalAmmoPicker,
|
||||
openFit,
|
||||
priceClear,
|
||||
# moduleGlobalAmmoPicker,
|
||||
moduleAmmoPicker,
|
||||
itemStats,
|
||||
damagePattern,
|
||||
marketJump,
|
||||
droneSplit,
|
||||
itemRemove,
|
||||
droneRemoveStack,
|
||||
ammoPattern,
|
||||
project,
|
||||
factorReload,
|
||||
whProjector,
|
||||
cargo,
|
||||
shipJump,
|
||||
changeAffectingSkills,
|
||||
tacticalMode,
|
||||
targetResists,
|
||||
whProjector
|
||||
priceClear,
|
||||
amount,
|
||||
metaSwap,
|
||||
implantSets,
|
||||
fighterAbilities,
|
||||
)
|
||||
|
||||
@@ -147,6 +147,7 @@ class DroneView(Display):
|
||||
def _merge(self, src, dst):
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
if sFit.mergeDrones(fitID, self.drones[src], self.drones[dst]):
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
|
||||
91
gui/errorDialog.py
Normal file
91
gui/errorDialog.py
Normal file
@@ -0,0 +1,91 @@
|
||||
#===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
#===============================================================================
|
||||
|
||||
import wx
|
||||
import sys
|
||||
import gui.utils.fonts as fonts
|
||||
import config
|
||||
|
||||
class ErrorFrame(wx.Frame):
|
||||
|
||||
def __init__(self, exception, tb):
|
||||
wx.Frame.__init__(self, None, id=wx.ID_ANY, title="pyfa error", pos=wx.DefaultPosition, size=wx.Size(500, 400), style=wx.DEFAULT_FRAME_STYLE^ wx.RESIZE_BORDER|wx.STAY_ON_TOP)
|
||||
|
||||
desc = "pyfa has experienced an unexpected error. Below is the " \
|
||||
"Traceback that contains crucial information about how this " \
|
||||
"error was triggered. Please contact the developers with " \
|
||||
"the information provided through the EVE Online forums " \
|
||||
"or file a GitHub issue."
|
||||
|
||||
self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)
|
||||
|
||||
if 'wxMSW' in wx.PlatformInfo:
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
headSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.headingText = wx.StaticText(self, wx.ID_ANY, "Error!", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE)
|
||||
self.headingText.SetFont(wx.Font(14, 74, 90, 92, False))
|
||||
|
||||
headSizer.Add(self.headingText, 1, wx.ALL, 5)
|
||||
mainSizer.Add(headSizer, 0, wx.EXPAND, 5)
|
||||
|
||||
mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
descSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.descText = wx.TextCtrl(self, wx.ID_ANY, desc, wx.DefaultPosition, wx.DefaultSize, wx.TE_AUTO_URL|wx.TE_MULTILINE|wx.TE_READONLY|wx.BORDER_NONE|wx.TRANSPARENT_WINDOW )
|
||||
self.descText.SetFont(wx.Font(fonts.BIG, wx.SWISS, wx.NORMAL, wx.NORMAL))
|
||||
descSizer.Add(self.descText, 1, wx.ALL, 5)
|
||||
mainSizer.Add(descSizer, 1, wx.EXPAND, 5)
|
||||
|
||||
self.eveForums = wx.HyperlinkCtrl(self, wx.ID_ANY, "EVE Forums Thread", "https://forums.eveonline.com/default.aspx?g=posts&t=466425",
|
||||
wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE)
|
||||
|
||||
mainSizer.Add(self.eveForums, 0, wx.ALL, 2)
|
||||
|
||||
self.eveForums = wx.HyperlinkCtrl(self, wx.ID_ANY, "Github Issues", "https://github.com/pyfa-org/Pyfa/issues",
|
||||
wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE)
|
||||
|
||||
mainSizer.Add(self.eveForums, 0, wx.ALL, 2)
|
||||
|
||||
#mainSizer.AddSpacer((0, 5), 0, wx.EXPAND, 5)
|
||||
|
||||
self.errorTextCtrl = wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_RICH2|wx.TE_DONTWRAP)
|
||||
self.errorTextCtrl.SetFont(wx.Font(8, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL))
|
||||
mainSizer.Add(self.errorTextCtrl, 0, wx.EXPAND|wx.LEFT|wx.RIGHT, 5)
|
||||
|
||||
self.errorTextCtrl.AppendText("pyfa root: ")
|
||||
self.errorTextCtrl.AppendText(config.pyfaPath or "Unknown")
|
||||
self.errorTextCtrl.AppendText('\n')
|
||||
self.errorTextCtrl.AppendText("save path: ")
|
||||
self.errorTextCtrl.AppendText(config.savePath or "Unknown")
|
||||
self.errorTextCtrl.AppendText('\n')
|
||||
self.errorTextCtrl.AppendText("fs encoding: ")
|
||||
self.errorTextCtrl.AppendText(sys.getfilesystemencoding())
|
||||
self.errorTextCtrl.AppendText('\n\n')
|
||||
self.errorTextCtrl.AppendText(tb)
|
||||
|
||||
self.SetSizer(mainSizer)
|
||||
mainSizer.Layout()
|
||||
self.Layout()
|
||||
|
||||
self.Centre(wx.BOTH)
|
||||
|
||||
self.Show()
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
import os
|
||||
from logbook import Logger
|
||||
import imp
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
@@ -30,10 +29,37 @@ import gui.mainFrame
|
||||
import gui.globalEvents as GE
|
||||
from gui.graph import Graph
|
||||
from gui.bitmapLoader import BitmapLoader
|
||||
from config import parsePath
|
||||
|
||||
# Don't actually import the thing, since it takes for fucking ever
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
try:
|
||||
import matplotlib as mpl
|
||||
|
||||
mpl_version = int(mpl.__version__[0])
|
||||
if mpl_version >= 2:
|
||||
mpl.use('wxagg')
|
||||
mplImported = True
|
||||
else:
|
||||
mplImported = False
|
||||
from matplotlib.patches import Patch
|
||||
|
||||
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas
|
||||
from matplotlib.figure import Figure
|
||||
|
||||
graphFrame_enabled = True
|
||||
mplImported = True
|
||||
except ImportError:
|
||||
Patch = mpl = Canvas = Figure = None
|
||||
graphFrame_enabled = False
|
||||
mplImported = False
|
||||
|
||||
class GraphFrame(wx.Frame):
|
||||
def __init__(self, parent, style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE | wx.FRAME_FLOAT_ON_PARENT):
|
||||
|
||||
global graphFrame_enabled
|
||||
global mplImported
|
||||
|
||||
self.mpl_version = int(mpl.__version__[0])
|
||||
imp.find_module('matplotlib')
|
||||
graphFrame_enabled = True
|
||||
mplImported = True
|
||||
@@ -81,7 +107,7 @@ class GraphFrame(wx.Frame):
|
||||
except:
|
||||
cache_dir = os.path.expanduser(os.path.join("~", ".matplotlib"))
|
||||
|
||||
cache_file = parsePath(cache_dir, 'fontList.cache')
|
||||
cache_file = path = os.path.join(cache_dir, 'fontList.cache')
|
||||
|
||||
if os.access(cache_dir, os.W_OK | os.X_OK) and os.path.isfile(cache_file):
|
||||
# remove matplotlib font cache, see #234
|
||||
@@ -283,7 +309,7 @@ class GraphFrame(wx.Frame):
|
||||
selected_color = legend_colors[i]
|
||||
except:
|
||||
selected_color = None
|
||||
legend2.append(self.Patch(color=selected_color, label=i_name), )
|
||||
legend2.append(Patch(color=selected_color, label=i_name), )
|
||||
|
||||
if len(legend2) > 0:
|
||||
leg = self.subplot.legend(handles=legend2)
|
||||
|
||||
@@ -849,7 +849,7 @@ class ItemEffects(wx.Panel):
|
||||
If effect file does not exist, create it
|
||||
"""
|
||||
|
||||
file_ = config.getPyfaPath(os.path.join("eos", "effects", "%s.py" % event.GetText().lower()))
|
||||
file_ = os.path.join(config.pyfaPath, "eos", "effects", "%s.py" % event.GetText().lower())
|
||||
|
||||
if not os.path.isfile(file_):
|
||||
open(file_, 'a').close()
|
||||
|
||||
@@ -85,14 +85,14 @@ if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION
|
||||
from service.crest import CrestModes
|
||||
from gui.crestFittings import CrestFittings, ExportToEve, CrestMgmt
|
||||
|
||||
try:
|
||||
from gui.propertyEditor import AttributeEditor
|
||||
disableOverrideEditor = False
|
||||
|
||||
disableOverrideEditor = False
|
||||
except ImportError as e:
|
||||
AttributeEditor = None
|
||||
print("Error loading Attribute Editor: %s.\nAccess to Attribute Editor is disabled." % e.message)
|
||||
disableOverrideEditor = True
|
||||
try:
|
||||
from gui.propertyEditor import AttributeEditor
|
||||
except ImportError as e:
|
||||
AttributeEditor = None
|
||||
print("Error loading Attribute Editor: %s.\nAccess to Attribute Editor is disabled." % e.message)
|
||||
disableOverrideEditor = True
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
@@ -356,7 +356,9 @@ class NavigationPanel(SFItem.SFBrowserItem):
|
||||
self.btnSwitch = self.toolbar.AddButton(self.switchBmpD, "Hide empty ship groups",
|
||||
clickCallback=self.ToggleEmptyGroupsView, hoverBitmap=self.switchBmpH,
|
||||
show=False)
|
||||
self.toolbar.AddButton(self.searchBmp, "Search fittings", clickCallback=self.ToggleSearchBox,
|
||||
|
||||
modifier = "CTRL" if 'wxMac' not in wx.PlatformInfo else "CMD"
|
||||
self.toolbar.AddButton(self.searchBmp, "Search fittings ({}+F)".format(modifier), clickCallback=self.ToggleSearchBox,
|
||||
hoverBitmap=self.searchBmpH)
|
||||
|
||||
self.padding = 4
|
||||
@@ -693,7 +695,8 @@ class ShipBrowser(wx.Panel):
|
||||
# set map & cache of fittings per category
|
||||
for cat in self.categoryList:
|
||||
itemIDs = [x.ID for x in cat.items]
|
||||
self.categoryFitCache[cat.ID] = sFit.countFitsWithShip(itemIDs) > 1
|
||||
num = sFit.countFitsWithShip(itemIDs)
|
||||
self.categoryFitCache[cat.ID] = num > 0
|
||||
|
||||
for ship in self.categoryList:
|
||||
if self.filterShipsWithNoFits and not self.categoryFitCache[ship.ID]:
|
||||
@@ -939,7 +942,7 @@ class ShipBrowser(wx.Panel):
|
||||
|
||||
if fits:
|
||||
for fit in fits:
|
||||
shipTrait = fit.ship.traits.traitText if (fit.ship.traits is not None) else ""
|
||||
shipTrait = fit.ship.item.traits.traitText if (fit.ship.item.traits is not None) else ""
|
||||
# empty string if no traits
|
||||
|
||||
self.lpane.AddWidget(FitItem(
|
||||
|
||||
@@ -4,10 +4,12 @@ import time
|
||||
import wx
|
||||
from service.settings import HTMLExportSettings
|
||||
from service.fit import Fit
|
||||
from service.port import Port
|
||||
from service.market import Market
|
||||
from logbook import Logger
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
from eos.db import getFit
|
||||
|
||||
|
||||
class exportHtml(object):
|
||||
@@ -187,6 +189,7 @@ class exportHtmlThread(threading.Thread):
|
||||
groupFits = 0
|
||||
for ship in ships:
|
||||
fits = sFit.getFitsWithShip(ship.ID)
|
||||
|
||||
if len(fits) > 0:
|
||||
groupFits += len(fits)
|
||||
|
||||
@@ -195,7 +198,7 @@ class exportHtmlThread(threading.Thread):
|
||||
return
|
||||
fit = fits[0]
|
||||
try:
|
||||
dnaFit = sFit.exportDna(fit[0])
|
||||
dnaFit = Port.exportDna(getFit(fit[0]))
|
||||
HTMLgroup += ' <li><a data-dna="' + dnaFit + '" target="_blank">' + ship.name + ": " + \
|
||||
fit[1] + '</a></li>\n'
|
||||
except:
|
||||
@@ -218,7 +221,8 @@ class exportHtmlThread(threading.Thread):
|
||||
if self.stopRunning:
|
||||
return
|
||||
try:
|
||||
dnaFit = sFit.exportDna(fit[0])
|
||||
dnaFit = Port.exportDna(getFit(fit[0]))
|
||||
print dnaFit
|
||||
HTMLship += ' <li><a data-dna="' + dnaFit + '" target="_blank">' + fit[
|
||||
1] + '</a></li>\n'
|
||||
except:
|
||||
@@ -271,7 +275,7 @@ class exportHtmlThread(threading.Thread):
|
||||
if self.stopRunning:
|
||||
return
|
||||
try:
|
||||
dnaFit = sFit.exportDna(fit[0])
|
||||
dnaFit = Port.exportDna(getFit(fit[0]))
|
||||
HTML += '<a class="outOfGameBrowserLink" target="_blank" href="' + dnaUrl + dnaFit + '">' + ship.name + ': ' + \
|
||||
fit[1] + '</a><br> \n'
|
||||
except:
|
||||
|
||||
161
pyfa.py
161
pyfa.py
@@ -155,81 +155,110 @@ if __name__ == "__main__":
|
||||
options.title = "pyfa %s%s - Python Fitting Assistant" % (config.version, "" if config.tag.lower() != 'git' else " (git)")
|
||||
|
||||
config.debug = options.debug
|
||||
# convert to unicode if it is set
|
||||
if options.savepath is not None:
|
||||
options.savepath = unicode(options.savepath)
|
||||
config.defPaths(options.savepath)
|
||||
|
||||
# Basic logging initialization
|
||||
|
||||
# Logging levels:
|
||||
'''
|
||||
logbook.CRITICAL
|
||||
logbook.ERROR
|
||||
logbook.WARNING
|
||||
logbook.INFO
|
||||
logbook.DEBUG
|
||||
logbook.NOTSET
|
||||
'''
|
||||
|
||||
if options.debug:
|
||||
savePath_filename = "Pyfa_debug.log"
|
||||
else:
|
||||
savePath_filename = "Pyfa.log"
|
||||
|
||||
savePath_Destination = config.getSavePath(savePath_filename)
|
||||
# Import everything
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
import os
|
||||
import os.path
|
||||
|
||||
try:
|
||||
# convert to unicode if it is set
|
||||
if options.savepath is not None:
|
||||
options.savepath = unicode(options.savepath)
|
||||
config.defPaths(options.savepath)
|
||||
|
||||
# Basic logging initialization
|
||||
|
||||
# Logging levels:
|
||||
'''
|
||||
logbook.CRITICAL
|
||||
logbook.ERROR
|
||||
logbook.WARNING
|
||||
logbook.INFO
|
||||
logbook.DEBUG
|
||||
logbook.NOTSET
|
||||
'''
|
||||
|
||||
if options.debug:
|
||||
logging_mode = "Debug"
|
||||
logging_setup = NestedSetup([
|
||||
# make sure we never bubble up to the stderr handler
|
||||
# if we run out of setup handling
|
||||
NullHandler(),
|
||||
StreamHandler(
|
||||
savePath_filename = "Pyfa_debug.log"
|
||||
else:
|
||||
savePath_filename = "Pyfa.log"
|
||||
|
||||
savePath_Destination = config.getSavePath(savePath_filename)
|
||||
|
||||
try:
|
||||
if options.debug:
|
||||
logging_mode = "Debug"
|
||||
logging_setup = NestedSetup([
|
||||
# make sure we never bubble up to the stderr handler
|
||||
# if we run out of setup handling
|
||||
NullHandler(),
|
||||
StreamHandler(
|
||||
sys.stdout,
|
||||
bubble=False,
|
||||
level=options.logginglevel
|
||||
),
|
||||
TimedRotatingFileHandler(
|
||||
),
|
||||
TimedRotatingFileHandler(
|
||||
savePath_Destination,
|
||||
level=0,
|
||||
backup_count=3,
|
||||
bubble=True,
|
||||
date_format='%Y-%m-%d',
|
||||
),
|
||||
])
|
||||
else:
|
||||
logging_mode = "User"
|
||||
logging_setup = NestedSetup([
|
||||
# make sure we never bubble up to the stderr handler
|
||||
# if we run out of setup handling
|
||||
NullHandler(),
|
||||
FingersCrossedHandler(
|
||||
),
|
||||
])
|
||||
else:
|
||||
logging_mode = "User"
|
||||
logging_setup = NestedSetup([
|
||||
# make sure we never bubble up to the stderr handler
|
||||
# if we run out of setup handling
|
||||
NullHandler(),
|
||||
FingersCrossedHandler(
|
||||
TimedRotatingFileHandler(
|
||||
savePath_Destination,
|
||||
level=0,
|
||||
backup_count=3,
|
||||
bubble=False,
|
||||
date_format='%Y-%m-%d',
|
||||
savePath_Destination,
|
||||
level=0,
|
||||
backup_count=3,
|
||||
bubble=False,
|
||||
date_format='%Y-%m-%d',
|
||||
),
|
||||
action_level=ERROR,
|
||||
buffer_size=1000,
|
||||
# pull_information=True,
|
||||
# reset=False,
|
||||
)
|
||||
])
|
||||
except:
|
||||
logging_mode = "Console Only"
|
||||
logging_setup = NestedSetup([
|
||||
# make sure we never bubble up to the stderr handler
|
||||
# if we run out of setup handling
|
||||
NullHandler(),
|
||||
StreamHandler(
|
||||
sys.stdout,
|
||||
bubble=False
|
||||
)
|
||||
])
|
||||
except:
|
||||
logging_mode = "Console Only"
|
||||
logging_setup = NestedSetup([
|
||||
# make sure we never bubble up to the stderr handler
|
||||
# if we run out of setup handling
|
||||
NullHandler(),
|
||||
StreamHandler(
|
||||
sys.stdout,
|
||||
bubble=False
|
||||
)
|
||||
])
|
||||
|
||||
import eos.db
|
||||
# noinspection PyUnresolvedReferences
|
||||
import service.prefetch # noqa: F401
|
||||
|
||||
# Make sure the saveddata db exists
|
||||
if not os.path.exists(config.savePath):
|
||||
os.mkdir(config.savePath)
|
||||
|
||||
eos.db.saveddata_meta.create_all()
|
||||
|
||||
except Exception, e:
|
||||
import traceback
|
||||
from gui.errorDialog import ErrorFrame
|
||||
|
||||
tb = traceback.format_exc()
|
||||
|
||||
pyfa = wx.App(False)
|
||||
ErrorFrame(e, tb)
|
||||
pyfa.MainLoop()
|
||||
sys.exit()
|
||||
|
||||
with logging_setup.threadbound():
|
||||
# Don't redirect if frozen
|
||||
@@ -253,29 +282,9 @@ if __name__ == "__main__":
|
||||
if hasattr(sys, 'frozen') and options.debug:
|
||||
pyfalog.critical("Running in frozen mode with debug turned on. Forcing all output to be written to log.")
|
||||
|
||||
# Import everything
|
||||
pyfalog.debug("Import wx")
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
pyfalog.debug("Import eos.db")
|
||||
import eos.db
|
||||
|
||||
pyfalog.debug("Run prefetch")
|
||||
# noinspection PyUnresolvedReferences
|
||||
import service.prefetch # noqa: F401
|
||||
|
||||
if not os.path.exists(config.savePath):
|
||||
pyfalog.debug("Saveddata path does not exist, creating new path")
|
||||
os.mkdir(config.savePath)
|
||||
else:
|
||||
pyfalog.debug("Using existing saveddata path: {0}", config.savePath)
|
||||
|
||||
eos.db.saveddata_meta.create_all()
|
||||
from gui.mainFrame import MainFrame
|
||||
|
||||
pyfa = wx.App(False)
|
||||
pyfalog.debug("Show GUI")
|
||||
from gui.mainFrame import MainFrame
|
||||
MainFrame(options.title)
|
||||
pyfalog.debug("Run MainLoop()")
|
||||
pyfa.MainLoop()
|
||||
|
||||
83
pyfa.spec
Normal file
83
pyfa.spec
Normal file
@@ -0,0 +1,83 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
# Note: This script is provided AS-IS for those that may be interested.
|
||||
# pyfa does not currently support pyInstaller (or any other build process) 100% at the moment
|
||||
|
||||
# Command line to build:
|
||||
# (Run from directory where pyfa.py and pyfa.spec lives.)
|
||||
# c:\Python27\scripts\pyinstaller.exe --clean --noconfirm --windowed --upx-dir=.\scripts\upx.exe pyfa.spec
|
||||
|
||||
# Don't forget to change the path to where your pyfa.py and pyfa.spec lives
|
||||
# pathex=['C:\\Users\\Ebag333\\Documents\\GitHub\\Ebag333\\Pyfa'],
|
||||
|
||||
import os
|
||||
|
||||
block_cipher = None
|
||||
|
||||
added_files = [
|
||||
( 'imgs/gui/*.png', 'imgs/gui' ),
|
||||
( 'imgs/gui/*.gif', 'imgs/gui' ),
|
||||
( 'imgs/icons/*.png', 'imgs/icons' ),
|
||||
( 'imgs/renders/*.png', 'imgs/renders' ),
|
||||
( 'dist_assets/win/pyfa.ico', '.' ),
|
||||
( 'dist_assets/cacert.pem', '.' ),
|
||||
( 'eve.db', '.' ),
|
||||
( 'README.md', '.' ),
|
||||
( 'LICENSE', '.' ),
|
||||
]
|
||||
|
||||
import_these = []
|
||||
|
||||
# Walk eos.effects and add all effects so we can import them properly
|
||||
for root, folders, files in os.walk("eos/effects"):
|
||||
for file_ in files:
|
||||
if file_.endswith(".py") and not file_.startswith("_"):
|
||||
mod_name = "{}.{}".format(
|
||||
root.replace("/", "."),
|
||||
file_.split(".py")[0],
|
||||
)
|
||||
import_these.append(mod_name)
|
||||
|
||||
a = Analysis(
|
||||
['pyfa.py'],
|
||||
pathex=['C:\\Users\\Ebag333\\Documents\\GitHub\\Ebag333\\Pyfa'],
|
||||
binaries=[],
|
||||
datas=added_files,
|
||||
hiddenimports=import_these,
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
)
|
||||
|
||||
pyz = PYZ(
|
||||
a.pure,
|
||||
a.zipped_data,
|
||||
cipher=block_cipher,
|
||||
)
|
||||
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
exclude_binaries=True,
|
||||
debug=False,
|
||||
console=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
name='pyfa',
|
||||
icon='dist_assets/win/pyfa.ico',
|
||||
onefile=False,
|
||||
)
|
||||
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
onefile=False,
|
||||
name='pyfa',
|
||||
icon='dist_assets/win/pyfa.ico',
|
||||
)
|
||||
9
requirements_build_OSx.txt
Normal file
9
requirements_build_OSx.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
PyInstaller >= 3.2.1
|
||||
cycler >= 0.10.0
|
||||
functools32 >= 3.2.3
|
||||
future >= 0.16.0
|
||||
numpy >= 1.12.
|
||||
pyparsing >= 2.1.10
|
||||
pypiwin32 >= 219
|
||||
pytz >= 2016.10
|
||||
six >= 1.10.0
|
||||
8
requirements_build_linux.txt
Normal file
8
requirements_build_linux.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
PyInstaller >= 3.2.1
|
||||
cycler >= 0.10.0
|
||||
functools32 >= 3.2.3
|
||||
future >= 0.16.0
|
||||
numpy >= 1.12.
|
||||
pyparsing >= 2.1.10
|
||||
pytz >= 2016.10
|
||||
six
|
||||
10
requirements_build_windows.txt
Normal file
10
requirements_build_windows.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
PyInstaller >= 3.2.1
|
||||
cycler >= 0.10.0
|
||||
functools32 >= 3.2.3
|
||||
future >= 0.16.0
|
||||
numpy >= 1.12.
|
||||
pyparsing >= 2.1.10
|
||||
pypiwin32 >= 219
|
||||
pytz >= 2016.10
|
||||
six >= 1.10.0
|
||||
tornado
|
||||
BIN
scripts/upx.exe
Normal file
BIN
scripts/upx.exe
Normal file
Binary file not shown.
@@ -168,19 +168,23 @@ class Crest(object):
|
||||
self.stopServer()
|
||||
time.sleep(1)
|
||||
# we need this to ensure that the previous get_request finishes, and then the socket will close
|
||||
self.httpd = StoppableHTTPServer(('', 6461), AuthHandler)
|
||||
thread.start_new_thread(self.httpd.serve, (self.handleLogin,))
|
||||
self.httpd = StoppableHTTPServer(('localhost', 6461), AuthHandler)
|
||||
|
||||
self.serverThread = threading.Thread(target=self.httpd.serve, args=(self.handleLogin,))
|
||||
self.serverThread.name = "CRESTServer"
|
||||
self.serverThread.daemon = True
|
||||
self.serverThread.start()
|
||||
|
||||
self.state = str(uuid.uuid4())
|
||||
return self.eve.auth_uri(scopes=self.scopes, state=self.state)
|
||||
|
||||
def handleLogin(self, message):
|
||||
if not message:
|
||||
return
|
||||
raise Exception("Could not parse out querystring parameters.")
|
||||
|
||||
if message['state'][0] != self.state:
|
||||
pyfalog.warn("OAUTH state mismatch")
|
||||
return
|
||||
raise Exception("OAUTH State Mismatch.")
|
||||
|
||||
pyfalog.debug("Handling CREST login with: {0}", message)
|
||||
|
||||
@@ -222,5 +226,3 @@ class Crest(object):
|
||||
eos.db.save(char)
|
||||
|
||||
wx.PostEvent(self.mainFrame, GE.SsoLogin(type=CrestModes.USER))
|
||||
|
||||
self.stopServer()
|
||||
|
||||
@@ -728,7 +728,13 @@ class Fit(object):
|
||||
fit.drones.remove(d1)
|
||||
|
||||
d2.amount += d1.amount
|
||||
d2.amountActive += d1.amountActive if d1.amountActive > 0 else -d2.amountActive
|
||||
d2.amountActive += d1.amountActive
|
||||
|
||||
# If we have less than the total number of drones active, make them all active. Fixes #728
|
||||
# This could be removed if we ever add an enhancement to make drone stacks partially active.
|
||||
if d2.amount > d2.amountActive:
|
||||
d2.amountActive = d2.amount
|
||||
|
||||
eos.db.commit()
|
||||
self.recalc(fit)
|
||||
return True
|
||||
@@ -943,21 +949,27 @@ class Fit(object):
|
||||
self.recalc(fit)
|
||||
|
||||
def toggleModulesState(self, fitID, base, modules, click):
|
||||
changed = False
|
||||
proposedState = self.__getProposedState(base, click)
|
||||
|
||||
if proposedState != base.state:
|
||||
changed = True
|
||||
base.state = proposedState
|
||||
for mod in modules:
|
||||
if mod != base:
|
||||
mod.state = self.__getProposedState(mod, click,
|
||||
proposedState)
|
||||
p = self.__getProposedState(mod, click, proposedState)
|
||||
mod.state = p
|
||||
if p != mod.state:
|
||||
changed = True
|
||||
|
||||
eos.db.commit()
|
||||
fit = eos.db.getFit(fitID)
|
||||
if changed:
|
||||
eos.db.commit()
|
||||
fit = eos.db.getFit(fitID)
|
||||
|
||||
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
|
||||
self.recalc(fit)
|
||||
# Then, check states of all modules and change where needed. This will recalc if needed
|
||||
self.checkStates(fit, base)
|
||||
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
|
||||
self.recalc(fit)
|
||||
# Then, check states of all modules and change where needed. This will recalc if needed
|
||||
self.checkStates(fit, base)
|
||||
|
||||
# Old state : New State
|
||||
localMap = {
|
||||
|
||||
@@ -25,6 +25,7 @@ from eos import db
|
||||
from service.network import Network, TimeoutError
|
||||
from logbook import Logger
|
||||
pyfalog = Logger(__name__)
|
||||
from service.fit import Fit
|
||||
|
||||
VALIDITY = 24 * 60 * 60 # Price validity period, 24 hours
|
||||
REREQUEST = 4 * 60 * 60 # Re-request delay for failed fetches, 4 hours
|
||||
@@ -40,8 +41,6 @@ class Price(object):
|
||||
"Hek": 30002053
|
||||
}
|
||||
|
||||
currentSystemId = ""
|
||||
|
||||
@classmethod
|
||||
def invalidPrices(cls, prices):
|
||||
for price in prices:
|
||||
@@ -80,9 +79,10 @@ class Price(object):
|
||||
# This will store POST data for eve-central
|
||||
data = []
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
# Base request URL
|
||||
baseurl = "https://eve-central.com/api/marketstat"
|
||||
data.append(("usesystem", Price.currentSystemId)) # Use Jita for market
|
||||
data.append(("usesystem", cls.systemsList[sFit.serviceFittingOptions["priceSystem"]])) # Use Jita for market
|
||||
|
||||
for typeID in toRequest: # Add all typeID arguments
|
||||
data.append(("typeid", typeID))
|
||||
@@ -143,15 +143,12 @@ class Price(object):
|
||||
typeIDs.append(mod.itemID)
|
||||
|
||||
for drone in fit.drones:
|
||||
for _ in xrange(drone.amount):
|
||||
typeIDs.append(drone.itemID)
|
||||
typeIDs.append(drone.itemID)
|
||||
|
||||
for fighter in fit.fighters:
|
||||
for _ in xrange(fighter.amountActive):
|
||||
typeIDs.append(fighter.itemID)
|
||||
typeIDs.append(fighter.itemID)
|
||||
|
||||
for cargo in fit.cargo:
|
||||
for _ in xrange(cargo.amount):
|
||||
typeIDs.append(cargo.itemID)
|
||||
typeIDs.append(cargo.itemID)
|
||||
|
||||
return typeIDs
|
||||
|
||||
@@ -43,7 +43,7 @@ class FileCache(APICache):
|
||||
os.mkdir(self.path, 0o700)
|
||||
|
||||
def _getpath(self, key):
|
||||
return config.parsePath(self.path, str(hash(key)) + '.cache')
|
||||
return os.path.join(self.path, str(hash(key)) + '.cache')
|
||||
|
||||
def put(self, key, value):
|
||||
with open(self._getpath(key), 'wb') as f:
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import BaseHTTPServer
|
||||
import urlparse
|
||||
import socket
|
||||
import thread
|
||||
import threading
|
||||
from logbook import Logger
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
from service.settings import CRESTSettings
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -19,12 +16,13 @@ HTML = '''
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
<title>pyfa Local Server</title>
|
||||
<style type="text/css">
|
||||
body { text-align: center; padding: 150px; }
|
||||
h1 { font-size: 40px; }
|
||||
body { font: 20px Helvetica, sans-serif; color: #333; }
|
||||
#article { display: block; text-align: left; width: 650px; margin: 0 auto; }
|
||||
a { color: #dc8100; text-decoration: none; }
|
||||
a:hover { color: #333; text-decoration: none; }
|
||||
body {{ text-align: center; padding: 150px; }}
|
||||
h1 {{ font-size: 40px; }}
|
||||
h2 {{ font-size: 32px; }}
|
||||
body {{ font: 20px Helvetica, sans-serif; color: #333; }}
|
||||
#article {{ display: block; text-align: left; width: 650px; margin: 0 auto; }}
|
||||
a {{ color: #dc8100; text-decoration: none; }}
|
||||
a:hover {{ color: #333; text-decoration: none; }}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -33,26 +31,36 @@ HTML = '''
|
||||
<!-- Layout from Short Circuit's CREST login. Shout out! https://github.com/farshield/shortcircuit -->
|
||||
<div id="article">
|
||||
<h1>pyfa</h1>
|
||||
<div>
|
||||
<p>If you see this message then it means you should be logged into CREST. You may close this window and return to the application.</p>
|
||||
</div>
|
||||
{0}
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function extractFromHash(name, hash) {
|
||||
function extractFromHash(name, hash) {{
|
||||
var match = hash.match(new RegExp(name + "=([^&]+)"));
|
||||
return !!match && match[1];
|
||||
}
|
||||
}}
|
||||
|
||||
var hash = window.location.hash;
|
||||
var token = extractFromHash("access_token", hash);
|
||||
var step2 = extractFromHash("step2", hash);
|
||||
|
||||
if (token){
|
||||
var redirect = window.location.origin.concat('/?', window.location.hash.substr(1));
|
||||
window.location = redirect;
|
||||
}
|
||||
else {
|
||||
console.log("do nothing");
|
||||
}
|
||||
function doRedirect() {{
|
||||
if (token){{
|
||||
// implicit authentication
|
||||
var redirect = window.location.origin.concat('/?', window.location.hash.substr(1), '&step=2');
|
||||
window.location = redirect;
|
||||
}}
|
||||
else {{
|
||||
// user-defined
|
||||
var redirect = window.location.href + '&step=2';
|
||||
window.location = redirect;
|
||||
}}
|
||||
}}
|
||||
|
||||
// do redirect if we are not already on step 2
|
||||
if (window.location.href.indexOf('step=2') == -1) {{
|
||||
setTimeout(doRedirect(), 1000);
|
||||
}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -64,13 +72,30 @@ class AuthHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path == "/favicon.ico":
|
||||
return
|
||||
|
||||
parsed_path = urlparse.urlparse(self.path)
|
||||
parts = urlparse.parse_qs(parsed_path.query)
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(HTML)
|
||||
msg = ""
|
||||
|
||||
wx.CallAfter(self.server.callback, parts)
|
||||
step2 = 'step' in parts
|
||||
|
||||
try:
|
||||
if step2:
|
||||
self.server.callback(parts)
|
||||
msg = "If you see this message then it means you should be logged into CREST. You may close this window and return to the application."
|
||||
else:
|
||||
# For implicit mode, we have to serve up the page which will take the hash and redirect useing a querystring
|
||||
msg = "Processing response from EVE Online"
|
||||
except Exception, ex:
|
||||
msg = "<h2>Error</h2>\n<p>{}</p>".format(ex.message)
|
||||
finally:
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(HTML.format(msg))
|
||||
|
||||
if step2:
|
||||
# Only stop once if we've received something in the querystring
|
||||
self.server.stop()
|
||||
|
||||
def log_message(self, format, *args):
|
||||
return
|
||||
@@ -86,7 +111,7 @@ class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
|
||||
sec = self.settings.get('timeout')
|
||||
pyfalog.debug("Running server for {0} seconds", sec)
|
||||
|
||||
self.socket.settimeout(0.5)
|
||||
self.socket.settimeout(1)
|
||||
self.max_tries = sec / self.socket.gettimeout()
|
||||
self.tries = 0
|
||||
self.run = True
|
||||
@@ -111,7 +136,7 @@ class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
|
||||
pyfalog.debug("Server timed out waiting for connection")
|
||||
self.stop()
|
||||
|
||||
def serve(self, callback):
|
||||
def serve(self, callback=None):
|
||||
self.callback = callback
|
||||
while self.run:
|
||||
try:
|
||||
@@ -119,11 +144,12 @@ class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
|
||||
except TypeError:
|
||||
pyfalog.debug("Caught exception in serve")
|
||||
pass
|
||||
|
||||
self.server_close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
httpd = StoppableHTTPServer(('', 6461), AuthHandler)
|
||||
thread.start_new_thread(httpd.serve, ())
|
||||
t = threading.Thread(target=httpd.serve)
|
||||
raw_input("Press <RETURN> to stop server\n")
|
||||
httpd.stop()
|
||||
|
||||
@@ -25,7 +25,7 @@ import config
|
||||
|
||||
|
||||
class SettingsProvider(object):
|
||||
BASE_PATH = config.getSavePath("settings")
|
||||
BASE_PATH = os.path.join(config.savePath, 'settings')
|
||||
settings = {}
|
||||
_instance = None
|
||||
|
||||
@@ -44,7 +44,7 @@ class SettingsProvider(object):
|
||||
|
||||
s = self.settings.get(area)
|
||||
if s is None:
|
||||
p = config.parsePath(self.BASE_PATH, area)
|
||||
p = os.path.join(self.BASE_PATH, area)
|
||||
|
||||
if not os.path.exists(p):
|
||||
info = {}
|
||||
|
||||
12
tests/test_modules/eos/test_mathUtils.py
Normal file
12
tests/test_modules/eos/test_mathUtils.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from eos.mathUtils import floorFloat
|
||||
|
||||
|
||||
def test_floorFloat():
|
||||
assert type(floorFloat(1)) is not float
|
||||
assert type(floorFloat(1)) is int
|
||||
assert type(floorFloat(1.1)) is not float
|
||||
assert type(floorFloat(1.1)) is int
|
||||
assert floorFloat(1.1) == 1
|
||||
assert floorFloat(1.9) == 1
|
||||
assert floorFloat(1.5) == 1
|
||||
assert floorFloat(-1.5) == -2
|
||||
9
tests/test_modules/gui/test_aboutData.py
Normal file
9
tests/test_modules/gui/test_aboutData.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from gui.aboutData import versionString, licenses, developers, credits, description
|
||||
|
||||
|
||||
def test_aboutData():
|
||||
assert versionString.__len__() > 0
|
||||
assert licenses.__len__() > 0
|
||||
assert developers.__len__() > 0
|
||||
assert credits.__len__() > 0
|
||||
assert description.__len__() > 0
|
||||
42
tests/test_modules/service/test_attribute.py
Normal file
42
tests/test_modules/service/test_attribute.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from service.attribute import Attribute
|
||||
|
||||
|
||||
def test_attribute():
|
||||
"""
|
||||
We don't really have much to test here, to throw a generic attribute at it and validate we get the expected results
|
||||
|
||||
:return:
|
||||
"""
|
||||
sAttr = Attribute.getInstance()
|
||||
info = sAttr.getAttributeInfo("maxRange")
|
||||
|
||||
assert info.attributeID == 54
|
||||
assert type(info.attributeID) is int
|
||||
assert info.attributeName == 'maxRange'
|
||||
assert type(info.attributeName) is unicode
|
||||
assert info.defaultValue == 0.0
|
||||
assert type(info.defaultValue) is float
|
||||
assert info.description == 'Distance below which range does not affect the to-hit equation.'
|
||||
assert type(info.description) is unicode
|
||||
assert info.displayName == 'Optimal Range'
|
||||
assert type(info.displayName) is unicode
|
||||
assert info.highIsGood is True
|
||||
assert type(info.highIsGood) is bool
|
||||
assert info.iconID == 1391
|
||||
assert type(info.iconID) is int
|
||||
assert info.name == 'maxRange'
|
||||
assert type(info.name) is unicode
|
||||
assert info.published is True
|
||||
assert type(info.published) is bool
|
||||
assert info.unitID == 1
|
||||
assert type(info.unitID) is int
|
||||
assert info.unit.ID == 1
|
||||
assert type(info.unit.ID) is int
|
||||
assert info.unit.displayName == 'm'
|
||||
assert type(info.unit.displayName) is unicode
|
||||
assert info.unit.name == 'Length'
|
||||
assert type(info.unit.name) is unicode
|
||||
assert info.unit.unitID == 1
|
||||
assert type(info.unit.unitID) is int
|
||||
assert info.unit.unitName == 'Length'
|
||||
assert type(info.unit.unitName) is unicode
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import importlib
|
||||
# import importlib
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import pytest
|
||||
# import pytest
|
||||
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
Reference in New Issue
Block a user