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:
blitzman
2017-02-25 18:21:07 -05:00
69 changed files with 6254 additions and 287 deletions

1
.gitignore vendored
View File

@@ -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

View File

@@ -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/)

View File

@@ -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

File diff suppressed because it is too large Load Diff

View 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;")

View File

@@ -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"

View File

@@ -1,7 +1,7 @@
# ammoInfluenceRange
#
# Used by:
# Items from category: Charge (571 of 899)
# Items from category: Charge (571 of 912)
type = "passive"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -2,7 +2,6 @@
#
# Used by:
# Modules from group: Data Miners (15 of 16)
# Module: QA Cross Protocol Analyzer
type = "active"

View File

@@ -2,7 +2,6 @@
#
# Used by:
# Modules from group: Automated Targeting System (6 of 6)
# Module: QA Damage Module
type = "passive"

View File

@@ -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"

View File

@@ -2,7 +2,6 @@
#
# Used by:
# Modules from group: Tracking Enhancer (10 of 10)
# Module: QA Damage Module
type = "passive"

View File

@@ -2,7 +2,6 @@
#
# Used by:
# Modules from group: Tracking Enhancer (10 of 10)
# Module: QA Damage Module
type = "passive"

View File

@@ -2,7 +2,6 @@
#
# Used by:
# Modules from group: Tracking Enhancer (10 of 10)
# Module: QA Damage Module
type = "passive"

View File

@@ -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"

View File

@@ -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")

View File

@@ -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"

View File

@@ -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"

View File

@@ -1,4 +1,4 @@
# remoteWeaponDisruptEntity
# npcEntityWeaponDisruptor
#
# Used by:
# Drones named like: TD (3 of 3)

View File

@@ -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"

View File

@@ -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"

View File

@@ -1,7 +1,4 @@
# scanStrengthBonusPercentActivate
#
# Used by:
# Module: QA ECCM
# Not used by any item
type = "active"

View File

@@ -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"

View File

@@ -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"

View File

@@ -1,7 +1,4 @@
# shieldTransfer
#
# Used by:
# Module: QA Shield Transporter - 5 Players
# Not used by any item
type = "projected", "active"

View File

@@ -1,4 +1,4 @@
# remoteGuidanceDisruptFalloff
# shipModuleGuidanceDisruptor
#
# Used by:
# Variations of module: Guidance Disruptor I (6 of 6)

View File

@@ -1,4 +1,4 @@
# remoteTrackingAssistFalloff
# shipModuleRemoteTrackingComputer
#
# Used by:
# Modules from group: Remote Tracking Computer (8 of 8)

View File

@@ -1,4 +1,4 @@
# remoteTrackingDisruptFalloff
# shipModuleTrackingDisruptor
#
# Used by:
# Variations of module: Tracking Disruptor I (6 of 6)

View File

@@ -2,7 +2,6 @@
#
# Used by:
# Modules from group: Signal Amplifier (7 of 7)
# Module: QA Damage Module
type = "passive"

View File

@@ -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)

View File

@@ -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"

View File

@@ -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"

View File

@@ -1,7 +1,4 @@
# targetArmorRepair
#
# Used by:
# Module: QA Remote Armor Repair System - 5 Players
# Not used by any item
type = "projected", "active"

View File

@@ -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")

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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

BIN
eve.db

Binary file not shown.

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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,
)

View File

@@ -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
View 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()

View File

@@ -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)

View File

@@ -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()

View File

@@ -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__)

View File

@@ -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(

View File

@@ -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
View File

@@ -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
View 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',
)

View 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

View 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

View 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

Binary file not shown.

View File

@@ -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()

View File

@@ -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 = {

View File

@@ -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

View File

@@ -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:

View File

@@ -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()

View File

@@ -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 = {}

View 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

View 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

View 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

View File

@@ -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__))