Merge branch 'singularity'
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,7 +13,7 @@
|
||||
*.patch
|
||||
|
||||
#Personal
|
||||
saveddata/
|
||||
/saveddata/
|
||||
|
||||
#PyCharm
|
||||
.idea/
|
||||
|
||||
@@ -92,6 +92,9 @@ def defPaths():
|
||||
|
||||
__createDirs(savePath)
|
||||
|
||||
if isFrozen():
|
||||
os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(pyfaPath, "cacert.pem")
|
||||
|
||||
format = '%(asctime)s %(name)-24s %(levelname)-8s %(message)s'
|
||||
logging.basicConfig(format=format, level=logLevel)
|
||||
handler = logging.handlers.RotatingFileHandler(os.path.join(savePath, "log.txt"), maxBytes=1000000, backupCount=3)
|
||||
|
||||
@@ -68,14 +68,8 @@ from eos.db.gamedata import *
|
||||
from eos.db.saveddata import *
|
||||
|
||||
#Import queries
|
||||
from eos.db.gamedata.queries import getItem, searchItems, getVariations, getItemsByCategory, directAttributeRequest, \
|
||||
getMarketGroup, getGroup, getCategory, getAttributeInfo, getMetaData, getMetaGroup
|
||||
from eos.db.saveddata.queries import getUser, getCharacter, getFit, getFitsWithShip, countFitsWithShip, searchFits, \
|
||||
getCharacterList, getPrice, getDamagePatternList, getDamagePattern, \
|
||||
getFitList, getFleetList, getFleet, save, remove, commit, add, \
|
||||
getCharactersForUser, getMiscData, getSquadsIDsWithFitID, getWing, \
|
||||
getSquad, getBoosterFits, getProjectedFits, getTargetResistsList, getTargetResists,\
|
||||
clearPrices, countAllFits
|
||||
from eos.db.gamedata.queries import *
|
||||
from eos.db.saveddata.queries import *
|
||||
|
||||
#If using in memory saveddata, you'll want to reflect it so the data structure is good.
|
||||
if config.saveddata_connectionstring == "sqlite:///:memory:":
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
__all__ = ["character", "fit", "module", "user", "skill", "price",
|
||||
"booster", "drone", "implant", "fleet", "damagePattern",
|
||||
"miscData", "targetResists"]
|
||||
__all__ = [
|
||||
"character",
|
||||
"fit",
|
||||
"module",
|
||||
"user",
|
||||
"skill",
|
||||
"price",
|
||||
"booster",
|
||||
"drone",
|
||||
"implant",
|
||||
"fleet",
|
||||
"damagePattern",
|
||||
"miscData",
|
||||
"targetResists",
|
||||
"override",
|
||||
"crest"
|
||||
]
|
||||
|
||||
|
||||
31
eos/db/saveddata/crest.py
Normal file
31
eos/db/saveddata/crest.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of eos.
|
||||
#
|
||||
# eos is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# eos 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
#===============================================================================
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, String, Boolean
|
||||
from sqlalchemy.orm import mapper
|
||||
|
||||
from eos.db import saveddata_meta
|
||||
from eos.types import CrestChar
|
||||
|
||||
crest_table = Table("crest", saveddata_meta,
|
||||
Column("ID", Integer, primary_key = True),
|
||||
Column("name", String, nullable = False, unique = True),
|
||||
Column("refresh_token", String, nullable = False))
|
||||
|
||||
mapper(CrestChar, crest_table)
|
||||
31
eos/db/saveddata/override.py
Normal file
31
eos/db/saveddata/override.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of eos.
|
||||
#
|
||||
# eos is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# eos 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
#===============================================================================
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, Float
|
||||
from sqlalchemy.orm import mapper
|
||||
|
||||
from eos.db import saveddata_meta
|
||||
from eos.types import Override
|
||||
|
||||
overrides_table = Table("overrides", saveddata_meta,
|
||||
Column("itemID", Integer, primary_key=True, index = True),
|
||||
Column("attrID", Integer, primary_key=True, index = True),
|
||||
Column("value", Float, nullable = False))
|
||||
|
||||
mapper(Override, overrides_table)
|
||||
@@ -19,7 +19,8 @@
|
||||
|
||||
from eos.db.util import processEager, processWhere
|
||||
from eos.db import saveddata_session, sd_lock
|
||||
from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad, TargetResists
|
||||
|
||||
from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad, TargetResists, Override, CrestChar
|
||||
from eos.db.saveddata.fleet import squadmembers_table
|
||||
from eos.db.saveddata.fit import projectedFits_table
|
||||
from sqlalchemy.sql import and_
|
||||
@@ -182,7 +183,7 @@ def getFit(lookfor, eager=None):
|
||||
else:
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
fit = saveddata_session.query(Fit).options(*eager).filter(Fit.ID == fitID).first()
|
||||
fit = saveddata_session.query(Fit).options(*eager).filter(Fit.ID == lookfor).first()
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
|
||||
@@ -416,6 +417,45 @@ def getProjectedFits(fitID):
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
|
||||
def getCrestCharacters(eager=None):
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
characters = saveddata_session.query(CrestChar).options(*eager).all()
|
||||
return characters
|
||||
|
||||
@cachedQuery(CrestChar, 1, "lookfor")
|
||||
def getCrestCharacter(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
with sd_lock:
|
||||
character = saveddata_session.query(CrestChar).get(lookfor)
|
||||
else:
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
character = saveddata_session.query(CrestChar).options(*eager).filter(CrestChar.ID == lookfor).first()
|
||||
elif isinstance(lookfor, basestring):
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
character = saveddata_session.query(CrestChar).options(*eager).filter(CrestChar.name == lookfor).first()
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
return character
|
||||
|
||||
def getOverrides(itemID, eager=None):
|
||||
if isinstance(itemID, int):
|
||||
return saveddata_session.query(Override).filter(Override.itemID == itemID).all()
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
|
||||
def clearOverrides():
|
||||
with sd_lock:
|
||||
deleted_rows = saveddata_session.query(Override).delete()
|
||||
commit()
|
||||
return deleted_rows
|
||||
|
||||
def getAllOverrides(eager=None):
|
||||
return saveddata_session.query(Override).all()
|
||||
|
||||
def removeInvalid(fits):
|
||||
invalids = [f for f in fits if f.isInvalid]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# armorTankingGang2
|
||||
# armorWarfareArmorHpReplacer
|
||||
#
|
||||
# Used by:
|
||||
# Implant: Armored Warfare Mindlink
|
||||
@@ -1,7 +1,7 @@
|
||||
# boosterArmorHpPenalty
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Booster (12 of 39)
|
||||
# Implants from group: Booster (12 of 37)
|
||||
type = "boosterSideEffect"
|
||||
def handler(fit, booster, context):
|
||||
fit.ship.boostItemAttr("armorHP", booster.getModifiedItemAttr("boosterArmorHPPenalty"))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# boosterArmorRepairAmountPenalty
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Booster (9 of 39)
|
||||
# Implants from group: Booster (9 of 37)
|
||||
type = "boosterSideEffect"
|
||||
def handler(fit, booster, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Armor Repair Unit",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# boosterMaxVelocityPenalty
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Booster (12 of 39)
|
||||
# Implants from group: Booster (12 of 37)
|
||||
type = "boosterSideEffect"
|
||||
def handler(fit, booster, context):
|
||||
fit.ship.boostItemAttr("maxVelocity", booster.getModifiedItemAttr("boosterMaxVelocityPenalty"))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# boosterShieldCapacityPenalty
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Booster (12 of 39)
|
||||
# Implants from group: Booster (12 of 37)
|
||||
type = "boosterSideEffect"
|
||||
def handler(fit, booster, context):
|
||||
fit.ship.boostItemAttr("shieldCapacity", booster.getModifiedItemAttr("boosterShieldCapacityPenalty"))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# boosterTurretOptimalRangePenalty
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Booster (9 of 39)
|
||||
# Implants from group: Booster (9 of 37)
|
||||
type = "boosterSideEffect"
|
||||
def handler(fit, booster, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Gunnery"),
|
||||
|
||||
11
eos/effects/informationwarfaremaxtargetrangebonus.py
Normal file
11
eos/effects/informationwarfaremaxtargetrangebonus.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# informationWarfareMaxTargetRangeBonus
|
||||
#
|
||||
# Used by:
|
||||
# Implant: Caldari Navy Warfare Mindlink
|
||||
# Implant: Imperial Navy Warfare Mindlink
|
||||
# Implant: Information Warfare Mindlink
|
||||
type = "gang"
|
||||
gangBoost = "maxTargetRange"
|
||||
gangBonus = "maxTargetRangeBonus"
|
||||
def handler(fit, container, context):
|
||||
fit.ship.boostItemAttr(gangBoost, container.getModifiedItemAttr(gangBonus))
|
||||
@@ -2,7 +2,6 @@
|
||||
#
|
||||
# Used by:
|
||||
# Implant: Caldari Navy Warfare Mindlink
|
||||
# Implant: Imperial Navy Warfare Mindlink
|
||||
# Implant: Information Warfare Mindlink
|
||||
type = "passive"
|
||||
def handler(fit, implant, context):
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# miningForemanMindLinkMiningAmountBonusReplacer
|
||||
#
|
||||
# Used by:
|
||||
# Implant: Mining Foreman Mindlink
|
||||
type = "gang"
|
||||
gangBoost = "miningAmount"
|
||||
gangBonus = "miningAmountBonus"
|
||||
def handler(fit, container, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining"),
|
||||
gangBoost, container.getModifiedItemAttr(gangBonus) * level)
|
||||
@@ -1,7 +1,6 @@
|
||||
# miningYieldGangBonusFixed
|
||||
#
|
||||
# Used by:
|
||||
# Implant: Mining Foreman Mindlink
|
||||
# Skill: Mining Foreman
|
||||
type = "gang"
|
||||
gangBoost = "miningAmount"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# missileSkillRapidLauncherRoF
|
||||
#
|
||||
# Used by:
|
||||
# Implants named like: Cerebral Accelerator (5 of 5)
|
||||
# Implants named like: Cerebral Accelerator (3 of 3)
|
||||
# Implants named like: Zainou 'Deadeye' Rapid Launch RL (6 of 6)
|
||||
# Implant: Whelan Machorin's Ballistic Smartlink
|
||||
# Skill: Missile Launcher Operation
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# overloadSelfThermalHardeningBonus
|
||||
#
|
||||
# Used by:
|
||||
# Variations of module: Armor Thermic Hardener I (39 of 39)
|
||||
# Variations of module: Thermic Dissipation Field I (19 of 19)
|
||||
# Module: Civilian Thermic Dissipation Field
|
||||
# Variations of module: Armor Thermal Hardener I (39 of 39)
|
||||
# Variations of module: Thermal Dissipation Field I (19 of 19)
|
||||
# Module: Civilian Thermal Dissipation Field
|
||||
type = "overheat"
|
||||
def handler(fit, module, context):
|
||||
module.boostItemAttr("thermalDamageResistanceBonus", module.getModifiedItemAttr("overloadHardeningBonus"))
|
||||
@@ -1,9 +1,6 @@
|
||||
# reconOperationsMaxTargetRangeBonusPostPercentMaxTargetRangeGangShips
|
||||
#
|
||||
# Used by:
|
||||
# Implant: Caldari Navy Warfare Mindlink
|
||||
# Implant: Imperial Navy Warfare Mindlink
|
||||
# Implant: Information Warfare Mindlink
|
||||
# Skill: Information Warfare
|
||||
type = "gang"
|
||||
gangBoost = "maxTargetRange"
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
# shieldDefensiveOperationsShieldCapacityBonusPostPercentShieldCapacityGangShips
|
||||
#
|
||||
# Used by:
|
||||
# Implant: Caldari Navy Warfare Mindlink
|
||||
# Implant: Republic Fleet Warfare Mindlink
|
||||
# Implant: Siege Warfare Mindlink
|
||||
# Skill: Siege Warfare
|
||||
type = "gang"
|
||||
gangBoost = "shieldCapacity"
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Devoter
|
||||
# Ship: Gold Magnate
|
||||
# Ship: Impairor
|
||||
# Ship: Phobos
|
||||
# Ship: Silver Magnate
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
fit.ship.boostItemAttr("armorEmDamageResonance", ship.getModifiedItemAttr("rookieArmorResistanceBonus"))
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Devoter
|
||||
# Ship: Gold Magnate
|
||||
# Ship: Impairor
|
||||
# Ship: Phobos
|
||||
# Ship: Silver Magnate
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
fit.ship.boostItemAttr("armorExplosiveDamageResonance", ship.getModifiedItemAttr("rookieArmorResistanceBonus"))
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Devoter
|
||||
# Ship: Gold Magnate
|
||||
# Ship: Impairor
|
||||
# Ship: Phobos
|
||||
# Ship: Silver Magnate
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
fit.ship.boostItemAttr("armorKineticDamageResonance", ship.getModifiedItemAttr("rookieArmorResistanceBonus"))
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Devoter
|
||||
# Ship: Gold Magnate
|
||||
# Ship: Impairor
|
||||
# Ship: Phobos
|
||||
# Ship: Silver Magnate
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
fit.ship.boostItemAttr("armorThermalDamageResonance", ship.getModifiedItemAttr("rookieArmorResistanceBonus"))
|
||||
|
||||
11
eos/effects/siegewarfareshieldcapacitybonusreplacer.py
Normal file
11
eos/effects/siegewarfareshieldcapacitybonusreplacer.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# siegeWarfareShieldCapacityBonusReplacer
|
||||
#
|
||||
# Used by:
|
||||
# Implant: Caldari Navy Warfare Mindlink
|
||||
# Implant: Republic Fleet Warfare Mindlink
|
||||
# Implant: Siege Warfare Mindlink
|
||||
type = "gang"
|
||||
gangBoost = "shieldCapacity"
|
||||
gangBonus = "shieldCapacityBonus"
|
||||
def handler(fit, container, context):
|
||||
fit.ship.boostItemAttr(gangBoost, container.getModifiedItemAttr(gangBonus) * level)
|
||||
@@ -1,9 +1,6 @@
|
||||
# skirmishWarfareAgilityBonus
|
||||
#
|
||||
# Used by:
|
||||
# Implant: Federation Navy Warfare Mindlink
|
||||
# Implant: Republic Fleet Warfare Mindlink
|
||||
# Implant: Skirmish Warfare Mindlink
|
||||
# Skill: Skirmish Warfare
|
||||
type = "gang"
|
||||
gangBoost = "agility"
|
||||
|
||||
11
eos/effects/skirmishwarfareagilitybonusreplacer.py
Normal file
11
eos/effects/skirmishwarfareagilitybonusreplacer.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# skirmishWarfareAgilityBonusReplacer
|
||||
#
|
||||
# Used by:
|
||||
# Implant: Federation Navy Warfare Mindlink
|
||||
# Implant: Republic Fleet Warfare Mindlink
|
||||
# Implant: Skirmish Warfare Mindlink
|
||||
type = "gang"
|
||||
gangBoost = "agility"
|
||||
gangBonus = "agilityBonus"
|
||||
def handler(fit, container, context):
|
||||
fit.ship.boostItemAttr(gangBoost, container.getModifiedItemAttr(gangBonus) * level)
|
||||
@@ -2,6 +2,8 @@
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Coercer
|
||||
# Ship: Gold Magnate
|
||||
# Ship: Silver Magnate
|
||||
type = "passive"
|
||||
def handler(fit, ship, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Small Energy Turret"),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# surgicalStrikeDamageMultiplierBonusPostPercentDamageMultiplierLocationShipModulesRequiringGunnery
|
||||
#
|
||||
# Used by:
|
||||
# Implants named like: Cerebral Accelerator (5 of 5)
|
||||
# Implants named like: Cerebral Accelerator (3 of 3)
|
||||
# Implants named like: Eifyr and Co. 'Gunslinger' Surgical Strike SS (6 of 6)
|
||||
type = "passive"
|
||||
def handler(fit, implant, context):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# thermalShieldCompensationHardeningBonusGroupShieldAmp
|
||||
#
|
||||
# Used by:
|
||||
# Skill: Thermic Shield Compensation
|
||||
# Skill: Thermal Shield Compensation
|
||||
type = "passive"
|
||||
def handler(fit, skill, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Shield Amplifier",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# thermicArmorCompensationHardeningBonusGroupArmorCoating
|
||||
#
|
||||
# Used by:
|
||||
# Skill: Thermic Armor Compensation
|
||||
# Skill: Thermal Armor Compensation
|
||||
type = "passive"
|
||||
def handler(fit, skill, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Armor Coating",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# thermicArmorCompensationHardeningBonusGroupEnergized
|
||||
#
|
||||
# Used by:
|
||||
# Skill: Thermic Armor Compensation
|
||||
# Skill: Thermal Armor Compensation
|
||||
type = "passive"
|
||||
def handler(fit, skill, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Armor Plating Energized",
|
||||
|
||||
@@ -24,6 +24,7 @@ from sqlalchemy.orm import reconstructor
|
||||
from eqBase import EqBase
|
||||
|
||||
import traceback
|
||||
import eos.db
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
@@ -168,7 +169,6 @@ class Item(EqBase):
|
||||
info = getattr(cls, "MOVE_ATTR_INFO", None)
|
||||
if info is None:
|
||||
cls.MOVE_ATTR_INFO = info = []
|
||||
import eos.db
|
||||
for id in cls.MOVE_ATTRS:
|
||||
info.append(eos.db.getAttributeInfo(id))
|
||||
|
||||
@@ -191,6 +191,7 @@ class Item(EqBase):
|
||||
self.__moved = False
|
||||
self.__offensive = None
|
||||
self.__assistive = None
|
||||
self.__overrides = None
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
@@ -210,6 +211,32 @@ class Item(EqBase):
|
||||
|
||||
return False
|
||||
|
||||
@property
|
||||
def overrides(self):
|
||||
if self.__overrides is None:
|
||||
self.__overrides = {}
|
||||
overrides = eos.db.getOverrides(self.ID)
|
||||
for x in overrides:
|
||||
if x.attr.name in self.__attributes:
|
||||
self.__overrides[x.attr.name] = x
|
||||
|
||||
return self.__overrides
|
||||
|
||||
def setOverride(self, attr, value):
|
||||
from eos.saveddata.override import Override
|
||||
if attr.name in self.__overrides:
|
||||
override = self.__overrides.get(attr.name)
|
||||
override.value = value
|
||||
else:
|
||||
override = Override(self, attr, value)
|
||||
self.__overrides[attr.name] = override
|
||||
eos.db.save(override)
|
||||
|
||||
def deleteOverride(self, attr):
|
||||
override = self.__overrides.pop(attr.name, None)
|
||||
eos.db.saveddata_session.delete(override)
|
||||
eos.db.commit()
|
||||
|
||||
@property
|
||||
def requiredSkills(self):
|
||||
if self.__requiredSkills is None:
|
||||
@@ -345,6 +372,12 @@ class Item(EqBase):
|
||||
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return "Item(ID={}, name={}) at {}".format(
|
||||
self.ID, self.name, hex(id(self))
|
||||
)
|
||||
|
||||
|
||||
class MetaData(EqBase):
|
||||
pass
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ class ChargeAttrShortcut(object):
|
||||
return None
|
||||
|
||||
class ModifiedAttributeDict(collections.MutableMapping):
|
||||
|
||||
OVERRIDES = False
|
||||
|
||||
class CalculationPlaceholder():
|
||||
pass
|
||||
|
||||
@@ -51,6 +54,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
self.__modified = {}
|
||||
# Affected by entities
|
||||
self.__affectedBy = {}
|
||||
# Overrides
|
||||
self.__overrides = {}
|
||||
# Dictionaries for various value modification types
|
||||
self.__forced = {}
|
||||
self.__preAssigns = {}
|
||||
@@ -79,6 +84,14 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
self.__original = val
|
||||
self.__modified.clear()
|
||||
|
||||
@property
|
||||
def overrides(self):
|
||||
return self.__overrides
|
||||
|
||||
@overrides.setter
|
||||
def overrides(self, val):
|
||||
self.__overrides = val
|
||||
|
||||
def __getitem__(self, key):
|
||||
# Check if we have final calculated value
|
||||
if key in self.__modified:
|
||||
@@ -99,6 +112,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
del self.__intermediary[key]
|
||||
|
||||
def getOriginal(self, key):
|
||||
if self.OVERRIDES and key in self.__overrides:
|
||||
return self.__overrides.get(key).value
|
||||
val = self.__original.get(key)
|
||||
if val is None:
|
||||
return None
|
||||
|
||||
@@ -58,6 +58,7 @@ class Booster(HandledItem, ItemAttrShortcut):
|
||||
self.__sideEffects = []
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
self.__itemModifiedAttributes.overrides = self.__item.overrides
|
||||
self.__slot = self.__calculateSlot(self.__item)
|
||||
|
||||
for effect in self.__item.effects.itervalues():
|
||||
|
||||
@@ -34,6 +34,7 @@ class Cargo(HandledItem, ItemAttrShortcut):
|
||||
self.amount = 0
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = item.attributes
|
||||
self.__itemModifiedAttributes.overrides = item.overrides
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
@@ -48,6 +49,7 @@ class Cargo(HandledItem, ItemAttrShortcut):
|
||||
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
self.__itemModifiedAttributes.overrides = self.__item.overrides
|
||||
|
||||
@property
|
||||
def itemModifiedAttributes(self):
|
||||
|
||||
46
eos/saveddata/crestchar.py
Normal file
46
eos/saveddata/crestchar.py
Normal file
@@ -0,0 +1,46 @@
|
||||
#===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of eos.
|
||||
#
|
||||
# eos is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# eos 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
#===============================================================================
|
||||
|
||||
import urllib
|
||||
from cStringIO import StringIO
|
||||
|
||||
from sqlalchemy.orm import reconstructor
|
||||
#from tomorrow import threads
|
||||
|
||||
|
||||
class CrestChar(object):
|
||||
|
||||
def __init__(self, id, name, refresh_token=None):
|
||||
self.ID = id
|
||||
self.name = name
|
||||
self.refresh_token = refresh_token
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
pass
|
||||
|
||||
'''
|
||||
@threads(1)
|
||||
def fetchImage(self):
|
||||
url = 'https://image.eveonline.com/character/%d_128.jpg'%self.ID
|
||||
fp = urllib.urlopen(url)
|
||||
data = fp.read()
|
||||
fp.close()
|
||||
self.img = StringIO(data)
|
||||
'''
|
||||
@@ -67,6 +67,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.__miningyield = None
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
self.__itemModifiedAttributes.overrides = self.__item.overrides
|
||||
|
||||
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
||||
chargeID = self.getModifiedItemAttr("entityMissileTypeID")
|
||||
@@ -74,6 +75,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
charge = eos.db.getItem(int(chargeID))
|
||||
self.__charge = charge
|
||||
self.__chargeModifiedAttributes.original = charge.attributes
|
||||
self.__chargeModifiedAttributes.overrides = charge.overrides
|
||||
|
||||
@property
|
||||
def itemModifiedAttributes(self):
|
||||
|
||||
@@ -56,6 +56,7 @@ class Implant(HandledItem, ItemAttrShortcut):
|
||||
""" Build object. Assumes proper and valid item already set """
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
self.__itemModifiedAttributes.overrides = self.__item.overrides
|
||||
self.__slot = self.__calculateSlot(self.__item)
|
||||
|
||||
@property
|
||||
|
||||
@@ -30,6 +30,7 @@ class Mode(ItemAttrShortcut, HandledItem):
|
||||
self.__item = item
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.item.attributes
|
||||
self.__itemModifiedAttributes.overrides = self.item.overrides
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
|
||||
@@ -113,10 +113,13 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
if self.__item:
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
self.__itemModifiedAttributes.overrides = self.__item.overrides
|
||||
self.__hardpoint = self.__calculateHardpoint(self.__item)
|
||||
self.__slot = self.__calculateSlot(self.__item)
|
||||
if self.__charge:
|
||||
self.__chargeModifiedAttributes.original = self.__charge.attributes
|
||||
self.__chargeModifiedAttributes.overrides = self.__charge.overrides
|
||||
|
||||
|
||||
@classmethod
|
||||
def buildEmpty(cls, slot):
|
||||
@@ -283,9 +286,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if charge is not None:
|
||||
self.chargeID = charge.ID
|
||||
self.__chargeModifiedAttributes.original = charge.attributes
|
||||
self.__chargeModifiedAttributes.overrides = charge.overrides
|
||||
else:
|
||||
self.chargeID = None
|
||||
self.__chargeModifiedAttributes.original = None
|
||||
self.__chargeModifiedAttributes.overrides = {}
|
||||
|
||||
self.__itemModifiedAttributes.clear()
|
||||
|
||||
|
||||
59
eos/saveddata/override.py
Normal file
59
eos/saveddata/override.py
Normal file
@@ -0,0 +1,59 @@
|
||||
#===============================================================================
|
||||
# Copyright (C) 2015 Ryan Holmes
|
||||
#
|
||||
# This file is part of eos.
|
||||
#
|
||||
# eos is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# eos 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
#===============================================================================
|
||||
|
||||
from eos.eqBase import EqBase
|
||||
from sqlalchemy.orm import validates, reconstructor
|
||||
import eos.db
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Override(EqBase):
|
||||
|
||||
def __init__(self, item, attr, value):
|
||||
self.itemID = item.ID
|
||||
self.__item = item
|
||||
self.attrID = attr.ID
|
||||
self.__attr = attr
|
||||
self.value = value
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
self.__attr = None
|
||||
self.__item = None
|
||||
|
||||
if self.attrID:
|
||||
self.__attr = eos.db.getAttributeInfo(self.attrID)
|
||||
if self.__attr is None:
|
||||
logger.error("Attribute (id: %d) does not exist", self.attrID)
|
||||
return
|
||||
|
||||
if self.itemID:
|
||||
self.__item = eos.db.getItem(self.itemID)
|
||||
if self.__item is None:
|
||||
logger.error("Item (id: %d) does not exist", self.itemID)
|
||||
return
|
||||
|
||||
@property
|
||||
def attr(self):
|
||||
return self.__attr
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
return self.__item
|
||||
@@ -51,6 +51,7 @@ class Ship(ItemAttrShortcut, HandledItem):
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = dict(self.item.attributes)
|
||||
self.__itemModifiedAttributes.original.update(self.EXTRA_ATTRIBUTES)
|
||||
self.__itemModifiedAttributes.overrides = self.item.overrides
|
||||
|
||||
self.commandBonus = 0
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from eos.gamedata import Attribute, Category, Effect, Group, Icon, Item, MarketG
|
||||
MetaGroup, AttributeInfo, Unit, EffectInfo, MetaType, MetaData, Traits
|
||||
from eos.saveddata.price import Price
|
||||
from eos.saveddata.user import User
|
||||
from eos.saveddata.crestchar import CrestChar
|
||||
from eos.saveddata.damagePattern import DamagePattern
|
||||
from eos.saveddata.targetResists import TargetResists
|
||||
from eos.saveddata.character import Character, Skill
|
||||
@@ -35,4 +36,5 @@ from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.mode import Mode
|
||||
from eos.saveddata.fleet import Fleet, Wing, Squad
|
||||
from eos.saveddata.miscData import MiscData
|
||||
from eos.saveddata.override import Override
|
||||
import eos.db
|
||||
|
||||
@@ -11,7 +11,7 @@ TextTyped, EVT_TEXT = wx.lib.newevent.NewEvent()
|
||||
|
||||
class PFSearchBox(wx.Window):
|
||||
def __init__(self, parent, id = wx.ID_ANY, value = "", pos = wx.DefaultPosition, size = wx.Size(-1,24), style = 0):
|
||||
wx.Window.__init__(self, parent, id, pos, size, style = 0)
|
||||
wx.Window.__init__(self, parent, id, pos, size, style = style)
|
||||
|
||||
self.isSearchButtonVisible = False
|
||||
self.isCancelButtonVisible = False
|
||||
|
||||
@@ -1 +1 @@
|
||||
__all__ = ["pyfaGeneralPreferences","pyfaHTMLExportPreferences","pyfaUpdatePreferences","pyfaNetworkPreferences"]
|
||||
__all__ = ["pyfaGeneralPreferences","pyfaHTMLExportPreferences","pyfaUpdatePreferences","pyfaNetworkPreferences","pyfaCrestPreferences"]
|
||||
|
||||
120
gui/builtinPreferenceViews/pyfaCrestPreferences.py
Normal file
120
gui/builtinPreferenceViews/pyfaCrestPreferences.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import wx
|
||||
|
||||
from gui.preferenceView import PreferenceView
|
||||
from gui.bitmapLoader import BitmapLoader
|
||||
|
||||
import gui.mainFrame
|
||||
import service
|
||||
from service.crest import CrestModes
|
||||
|
||||
class PFCrestPref ( PreferenceView):
|
||||
title = "CREST"
|
||||
|
||||
def populatePanel( self, panel ):
|
||||
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = service.settings.CRESTSettings.getInstance()
|
||||
self.dirtySettings = False
|
||||
dlgWidth = panel.GetParent().GetParent().ClientSize.width
|
||||
mainSizer = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
self.stTitle = wx.StaticText( panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.stTitle.Wrap( -1 )
|
||||
self.stTitle.SetFont( wx.Font( 12, 70, 90, 90, False, wx.EmptyString ) )
|
||||
|
||||
mainSizer.Add( self.stTitle, 0, wx.ALL, 5 )
|
||||
|
||||
self.m_staticline1 = wx.StaticLine( panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL )
|
||||
mainSizer.Add( self.m_staticline1, 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5 )
|
||||
|
||||
self.stInfo = wx.StaticText( panel, wx.ID_ANY, u"Please see the pyfa wiki on GitHub for information regarding these options.", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.stInfo.Wrap(dlgWidth - 50)
|
||||
mainSizer.Add( self.stInfo, 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5 )
|
||||
|
||||
rbSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.rbMode = wx.RadioBox(panel, -1, "Mode", wx.DefaultPosition, wx.DefaultSize, ['Implicit', 'User-supplied details'], 1, wx.RA_SPECIFY_COLS)
|
||||
self.rbServer = wx.RadioBox(panel, -1, "Server", wx.DefaultPosition, wx.DefaultSize, ['Tranquility', 'Singularity'], 1, wx.RA_SPECIFY_COLS)
|
||||
|
||||
self.rbMode.SetSelection(self.settings.get('mode'))
|
||||
self.rbServer.SetSelection(self.settings.get('server'))
|
||||
|
||||
rbSizer.Add(self.rbMode, 1, wx.TOP | wx.RIGHT, 5 )
|
||||
rbSizer.Add(self.rbServer, 1, wx.ALL, 5 )
|
||||
|
||||
self.rbMode.Bind(wx.EVT_RADIOBOX, self.OnModeChange)
|
||||
self.rbServer.Bind(wx.EVT_RADIOBOX, self.OnServerChange)
|
||||
|
||||
mainSizer.Add(rbSizer, 1, wx.ALL|wx.EXPAND, 0)
|
||||
|
||||
detailsTitle = wx.StaticText( panel, wx.ID_ANY, "CREST client details", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
detailsTitle.Wrap( -1 )
|
||||
detailsTitle.SetFont( wx.Font( 12, 70, 90, 90, False, wx.EmptyString ) )
|
||||
|
||||
mainSizer.Add( detailsTitle, 0, wx.ALL, 5 )
|
||||
mainSizer.Add( wx.StaticLine( panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ), 0, wx.EXPAND, 5 )
|
||||
|
||||
|
||||
fgAddrSizer = wx.FlexGridSizer( 2, 2, 0, 0 )
|
||||
fgAddrSizer.AddGrowableCol( 1 )
|
||||
fgAddrSizer.SetFlexibleDirection( wx.BOTH )
|
||||
fgAddrSizer.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
self.stSetID = wx.StaticText( panel, wx.ID_ANY, u"Client ID:", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.stSetID.Wrap( -1 )
|
||||
fgAddrSizer.Add( self.stSetID, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 )
|
||||
|
||||
self.inputClientID = wx.TextCtrl( panel, wx.ID_ANY, self.settings.get('clientID'), wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
|
||||
fgAddrSizer.Add( self.inputClientID, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5 )
|
||||
|
||||
self.stSetSecret = wx.StaticText( panel, wx.ID_ANY, u"Client Secret:", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.stSetSecret.Wrap( -1 )
|
||||
|
||||
fgAddrSizer.Add( self.stSetSecret, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 )
|
||||
|
||||
self.inputClientSecret = wx.TextCtrl( panel, wx.ID_ANY, self.settings.get('clientSecret'), wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
|
||||
fgAddrSizer.Add( self.inputClientSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5 )
|
||||
|
||||
self.btnApply = wx.Button( panel, wx.ID_ANY, u"Save Client Settings", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.btnApply.Bind(wx.EVT_BUTTON, self.OnBtnApply)
|
||||
|
||||
mainSizer.Add( fgAddrSizer, 0, wx.EXPAND, 5)
|
||||
mainSizer.Add( self.btnApply, 0, wx.ALIGN_RIGHT, 5)
|
||||
|
||||
self.ToggleProxySettings(self.settings.get('mode'))
|
||||
|
||||
panel.SetSizer( mainSizer )
|
||||
panel.Layout()
|
||||
|
||||
def OnModeChange(self, event):
|
||||
self.settings.set('mode', event.GetInt())
|
||||
self.ToggleProxySettings(self.settings.get('mode'))
|
||||
service.Crest.restartService()
|
||||
|
||||
def OnServerChange(self, event):
|
||||
self.settings.set('server', event.GetInt())
|
||||
service.Crest.restartService()
|
||||
|
||||
def OnBtnApply(self, event):
|
||||
self.settings.set('clientID', self.inputClientID.GetValue())
|
||||
self.settings.set('clientSecret', self.inputClientSecret.GetValue())
|
||||
sCrest = service.Crest.getInstance()
|
||||
sCrest.delAllCharacters()
|
||||
|
||||
def ToggleProxySettings(self, mode):
|
||||
if mode:
|
||||
self.stSetID.Enable()
|
||||
self.inputClientID.Enable()
|
||||
self.stSetSecret.Enable()
|
||||
self.inputClientSecret.Enable()
|
||||
else:
|
||||
self.stSetID.Disable()
|
||||
self.inputClientID.Disable()
|
||||
self.stSetSecret.Disable()
|
||||
self.inputClientSecret.Disable()
|
||||
|
||||
def getImage(self):
|
||||
return BitmapLoader.getBitmap("eve", "gui")
|
||||
|
||||
PFCrestPref.register()
|
||||
@@ -35,9 +35,6 @@ class CharacterEditor(wx.Frame):
|
||||
size=wx.Size(641, 600), style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.TAB_TRAVERSAL)
|
||||
|
||||
i = wx.IconFromBitmap(BitmapLoader.getBitmap("character_small", "gui"))
|
||||
|
||||
self.mainFrame = parent
|
||||
|
||||
self.SetIcon(i)
|
||||
|
||||
self.disableWin= wx.WindowDisabler(self)
|
||||
|
||||
@@ -25,16 +25,18 @@ class CopySelectDialog(wx.Dialog):
|
||||
copyFormatEftImps = 1
|
||||
copyFormatXml = 2
|
||||
copyFormatDna = 3
|
||||
copyFormatCrest = 4
|
||||
|
||||
def __init__(self, parent):
|
||||
wx.Dialog.__init__(self, parent, id = wx.ID_ANY, title = u"Select a format", size = (-1,-1), style = wx.DEFAULT_DIALOG_STYLE)
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
copyFormats = [u"EFT", u"EFT (Implants)", u"XML", u"DNA"]
|
||||
copyFormats = [u"EFT", u"EFT (Implants)", u"XML", u"DNA", u"CREST"]
|
||||
copyFormatTooltips = {CopySelectDialog.copyFormatEft: u"EFT text format",
|
||||
CopySelectDialog.copyFormatEftImps: u"EFT text format",
|
||||
CopySelectDialog.copyFormatXml: u"EVE native XML format",
|
||||
CopySelectDialog.copyFormatDna: u"A one-line text format"}
|
||||
CopySelectDialog.copyFormatDna: u"A one-line text format",
|
||||
CopySelectDialog.copyFormatCrest: u"A JSON format used for EVE CREST"}
|
||||
selector = wx.RadioBox(self, wx.ID_ANY, label = u"Copy to the clipboard using:", choices = copyFormats, style = wx.RA_SPECIFY_ROWS)
|
||||
selector.Bind(wx.EVT_RADIOBOX, self.Selected)
|
||||
for format, tooltip in copyFormatTooltips.iteritems():
|
||||
|
||||
363
gui/crestFittings.py
Normal file
363
gui/crestFittings.py
Normal file
@@ -0,0 +1,363 @@
|
||||
import time
|
||||
import webbrowser
|
||||
import json
|
||||
import wx
|
||||
|
||||
from wx.lib.pubsub import setupkwargs
|
||||
from wx.lib.pubsub import pub
|
||||
|
||||
import service
|
||||
from service.crest import CrestModes
|
||||
import gui.display as d
|
||||
from eos.types import Cargo
|
||||
from eos.db import getItem
|
||||
|
||||
class CrestFittings(wx.Frame):
|
||||
|
||||
def __init__(self, parent):
|
||||
wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="Browse EVE Fittings", pos=wx.DefaultPosition, size=wx.Size( 550,450 ), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL)
|
||||
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||
|
||||
self.mainFrame = parent
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
sCrest = service.Crest.getInstance()
|
||||
|
||||
characterSelectSizer = wx.BoxSizer( wx.HORIZONTAL )
|
||||
|
||||
if sCrest.settings.get('mode') == CrestModes.IMPLICIT:
|
||||
self.stLogged = wx.StaticText(self, wx.ID_ANY, "Currently logged in as %s"%sCrest.implicitCharacter.name, wx.DefaultPosition, wx.DefaultSize)
|
||||
self.stLogged.Wrap( -1 )
|
||||
|
||||
characterSelectSizer.Add( self.stLogged, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
|
||||
else:
|
||||
self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, [])
|
||||
characterSelectSizer.Add( self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
|
||||
self.updateCharList()
|
||||
|
||||
self.fetchBtn = wx.Button( self, wx.ID_ANY, u"Fetch Fits", wx.DefaultPosition, wx.DefaultSize, 5 )
|
||||
characterSelectSizer.Add( self.fetchBtn, 0, wx.ALL, 5 )
|
||||
mainSizer.Add( characterSelectSizer, 0, wx.EXPAND, 5 )
|
||||
|
||||
self.sl = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL )
|
||||
mainSizer.Add( self.sl, 0, wx.EXPAND |wx.ALL, 5 )
|
||||
|
||||
contentSizer = wx.BoxSizer( wx.HORIZONTAL )
|
||||
browserSizer = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
self.fitTree = FittingsTreeView(self)
|
||||
browserSizer.Add( self.fitTree, 1, wx.ALL|wx.EXPAND, 5 )
|
||||
contentSizer.Add( browserSizer, 1, wx.EXPAND, 0 )
|
||||
fitSizer = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
self.fitView = FitView(self)
|
||||
fitSizer.Add( self.fitView, 1, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
btnSizer = wx.BoxSizer( wx.HORIZONTAL )
|
||||
self.importBtn = wx.Button( self, wx.ID_ANY, u"Import to pyfa", wx.DefaultPosition, wx.DefaultSize, 5 )
|
||||
self.deleteBtn = wx.Button( self, wx.ID_ANY, u"Delete from EVE", wx.DefaultPosition, wx.DefaultSize, 5 )
|
||||
btnSizer.Add( self.importBtn, 1, wx.ALL, 5 )
|
||||
btnSizer.Add( self.deleteBtn, 1, wx.ALL, 5 )
|
||||
fitSizer.Add( btnSizer, 0, wx.EXPAND )
|
||||
|
||||
contentSizer.Add(fitSizer, 1, wx.EXPAND, 0)
|
||||
mainSizer.Add(contentSizer, 1, wx.EXPAND, 5)
|
||||
|
||||
self.fetchBtn.Bind(wx.EVT_BUTTON, self.fetchFittings)
|
||||
self.importBtn.Bind(wx.EVT_BUTTON, self.importFitting)
|
||||
self.deleteBtn.Bind(wx.EVT_BUTTON, self.deleteFitting)
|
||||
|
||||
pub.subscribe(self.ssoLogout, 'logout_success')
|
||||
|
||||
self.statusbar = wx.StatusBar(self)
|
||||
self.statusbar.SetFieldsCount()
|
||||
self.SetStatusBar(self.statusbar)
|
||||
|
||||
self.cacheTimer = wx.Timer(self)
|
||||
self.Bind(wx.EVT_TIMER, self.updateCacheStatus, self.cacheTimer)
|
||||
|
||||
self.SetSizer(mainSizer)
|
||||
self.Layout()
|
||||
|
||||
self.Centre(wx.BOTH)
|
||||
|
||||
def updateCharList(self):
|
||||
sCrest = service.Crest.getInstance()
|
||||
chars = sCrest.getCrestCharacters()
|
||||
|
||||
if len(chars) == 0:
|
||||
self.Close()
|
||||
|
||||
for char in chars:
|
||||
self.charChoice.Append(char.name, char.ID)
|
||||
|
||||
self.charChoice.SetSelection(0)
|
||||
|
||||
def updateCacheStatus(self, event):
|
||||
t = time.gmtime(self.cacheTime-time.time())
|
||||
if t < 0:
|
||||
self.cacheTimer.Stop()
|
||||
self.statusbar.Hide()
|
||||
else:
|
||||
sTime = time.strftime("%H:%M:%S", t)
|
||||
self.statusbar.SetStatusText("Cached for %s"%sTime, 0)
|
||||
|
||||
def ssoLogout(self, message):
|
||||
self.Close()
|
||||
|
||||
def getActiveCharacter(self):
|
||||
sCrest = service.Crest.getInstance()
|
||||
|
||||
if sCrest.settings.get('mode') == CrestModes.IMPLICIT:
|
||||
return sCrest.implicitCharacter.ID
|
||||
|
||||
selection = self.charChoice.GetCurrentSelection()
|
||||
return self.charChoice.GetClientData(selection) if selection is not None else None
|
||||
|
||||
def fetchFittings(self, event):
|
||||
sCrest = service.Crest.getInstance()
|
||||
waitDialog = wx.BusyInfo("Fetching fits, please wait...", parent=self)
|
||||
fittings = sCrest.getFittings(self.getActiveCharacter())
|
||||
self.cacheTime = fittings.get('cached_until')
|
||||
self.updateCacheStatus(None)
|
||||
self.cacheTimer.Start(1000)
|
||||
self.fitTree.populateSkillTree(fittings)
|
||||
del waitDialog
|
||||
|
||||
def importFitting(self, event):
|
||||
selection = self.fitView.fitSelection
|
||||
if not selection:
|
||||
return
|
||||
data = self.fitTree.fittingsTreeCtrl.GetPyData(selection)
|
||||
sFit = service.Fit.getInstance()
|
||||
fits = sFit.importFitFromBuffer(data)
|
||||
self.mainFrame._openAfterImport(fits)
|
||||
|
||||
def deleteFitting(self, event):
|
||||
sCrest = service.Crest.getInstance()
|
||||
selection = self.fitView.fitSelection
|
||||
if not selection:
|
||||
return
|
||||
data = json.loads(self.fitTree.fittingsTreeCtrl.GetPyData(selection))
|
||||
|
||||
dlg = wx.MessageDialog(self,
|
||||
"Do you really want to delete %s (%s) from EVE?"%(data['name'], data['ship']['name']),
|
||||
"Confirm Delete", wx.YES | wx.NO | wx.ICON_QUESTION)
|
||||
|
||||
if dlg.ShowModal() == wx.ID_YES:
|
||||
sCrest.delFitting(self.getActiveCharacter(), data['fittingID'])
|
||||
|
||||
|
||||
class ExportToEve(wx.Frame):
|
||||
|
||||
def __init__(self, parent):
|
||||
wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="Export fit to EVE", pos=wx.DefaultPosition, size=(wx.Size(350,100)), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL)
|
||||
|
||||
self.mainFrame = parent
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||
|
||||
sCrest = service.Crest.getInstance()
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
hSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
if sCrest.settings.get('mode') == CrestModes.IMPLICIT:
|
||||
self.stLogged = wx.StaticText(self, wx.ID_ANY, "Currently logged in as %s"%sCrest.implicitCharacter.name, wx.DefaultPosition, wx.DefaultSize)
|
||||
self.stLogged.Wrap( -1 )
|
||||
|
||||
hSizer.Add( self.stLogged, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
|
||||
else:
|
||||
self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, [])
|
||||
hSizer.Add( self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
|
||||
self.updateCharList()
|
||||
self.charChoice.SetSelection(0)
|
||||
|
||||
self.exportBtn = wx.Button( self, wx.ID_ANY, u"Export Fit", wx.DefaultPosition, wx.DefaultSize, 5 )
|
||||
hSizer.Add( self.exportBtn, 0, wx.ALL, 5 )
|
||||
|
||||
mainSizer.Add( hSizer, 0, wx.EXPAND, 5 )
|
||||
|
||||
self.exportBtn.Bind(wx.EVT_BUTTON, self.exportFitting)
|
||||
|
||||
self.statusbar = wx.StatusBar(self)
|
||||
self.statusbar.SetFieldsCount(2)
|
||||
self.statusbar.SetStatusWidths([100, -1])
|
||||
|
||||
pub.subscribe(self.ssoLogout, 'logout_success')
|
||||
|
||||
self.SetSizer(hSizer)
|
||||
self.SetStatusBar(self.statusbar)
|
||||
self.Layout()
|
||||
|
||||
self.Centre(wx.BOTH)
|
||||
|
||||
def updateCharList(self):
|
||||
sCrest = service.Crest.getInstance()
|
||||
chars = sCrest.getCrestCharacters()
|
||||
|
||||
if len(chars) == 0:
|
||||
self.Close()
|
||||
|
||||
for char in chars:
|
||||
self.charChoice.Append(char.name, char.ID)
|
||||
|
||||
self.charChoice.SetSelection(0)
|
||||
|
||||
def ssoLogout(self, message):
|
||||
self.Close()
|
||||
|
||||
def getActiveCharacter(self):
|
||||
sCrest = service.Crest.getInstance()
|
||||
|
||||
if sCrest.settings.get('mode') == CrestModes.IMPLICIT:
|
||||
return sCrest.implicitCharacter.ID
|
||||
|
||||
selection = self.charChoice.GetCurrentSelection()
|
||||
return self.charChoice.GetClientData(selection) if selection is not None else None
|
||||
|
||||
def exportFitting(self, event):
|
||||
self.statusbar.SetStatusText("", 0)
|
||||
self.statusbar.SetStatusText("Sending request and awaiting response", 1)
|
||||
sCrest = service.Crest.getInstance()
|
||||
|
||||
sFit = service.Fit.getInstance()
|
||||
data = sFit.exportCrest(self.mainFrame.getActiveFit())
|
||||
res = sCrest.postFitting(self.getActiveCharacter(), data)
|
||||
|
||||
self.statusbar.SetStatusText("%d: %s"%(res.status_code, res.reason), 0)
|
||||
try:
|
||||
text = json.loads(res.text)
|
||||
self.statusbar.SetStatusText(text['message'], 1)
|
||||
except ValueError:
|
||||
self.statusbar.SetStatusText("", 1)
|
||||
|
||||
|
||||
class CrestMgmt(wx.Dialog):
|
||||
|
||||
def __init__( self, parent ):
|
||||
wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = "CREST Character Management", pos = wx.DefaultPosition, size = wx.Size( 550,250 ), style = wx.DEFAULT_DIALOG_STYLE )
|
||||
|
||||
mainSizer = wx.BoxSizer( wx.HORIZONTAL )
|
||||
|
||||
self.lcCharacters = wx.ListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT)
|
||||
|
||||
self.lcCharacters.InsertColumn(0, heading='Character')
|
||||
self.lcCharacters.InsertColumn(1, heading='Refresh Token')
|
||||
|
||||
self.popCharList()
|
||||
|
||||
mainSizer.Add( self.lcCharacters, 1, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
btnSizer = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
self.addBtn = wx.Button( self, wx.ID_ANY, u"Add Character", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
btnSizer.Add( self.addBtn, 0, wx.ALL | wx.EXPAND, 5 )
|
||||
|
||||
self.deleteBtn = wx.Button( self, wx.ID_ANY, u"Revoke Character", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
btnSizer.Add( self.deleteBtn, 0, wx.ALL | wx.EXPAND, 5 )
|
||||
|
||||
mainSizer.Add( btnSizer, 0, wx.EXPAND, 5 )
|
||||
|
||||
self.addBtn.Bind(wx.EVT_BUTTON, self.addChar)
|
||||
self.deleteBtn.Bind(wx.EVT_BUTTON, self.delChar)
|
||||
|
||||
pub.subscribe(self.ssoLogin, 'login_success')
|
||||
|
||||
self.SetSizer( mainSizer )
|
||||
self.Layout()
|
||||
|
||||
self.Centre( wx.BOTH )
|
||||
|
||||
def ssoLogin(self, type):
|
||||
self.popCharList()
|
||||
|
||||
def popCharList(self):
|
||||
sCrest = service.Crest.getInstance()
|
||||
chars = sCrest.getCrestCharacters()
|
||||
|
||||
self.lcCharacters.DeleteAllItems()
|
||||
|
||||
for index, char in enumerate(chars):
|
||||
self.lcCharacters.InsertStringItem(index, char.name)
|
||||
self.lcCharacters.SetStringItem(index, 1, char.refresh_token)
|
||||
self.lcCharacters.SetItemData(index, char.ID)
|
||||
|
||||
self.lcCharacters.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
||||
self.lcCharacters.SetColumnWidth(1, wx.LIST_AUTOSIZE)
|
||||
|
||||
def addChar(self, event):
|
||||
sCrest = service.Crest.getInstance()
|
||||
uri = sCrest.startServer()
|
||||
webbrowser.open(uri)
|
||||
|
||||
def delChar(self, event):
|
||||
item = self.lcCharacters.GetFirstSelected()
|
||||
charID = self.lcCharacters.GetItemData(item)
|
||||
sCrest = service.Crest.getInstance()
|
||||
sCrest.delCrestCharacter(charID)
|
||||
self.popCharList()
|
||||
|
||||
|
||||
class FittingsTreeView(wx.Panel):
|
||||
def __init__(self, parent):
|
||||
wx.Panel.__init__(self, parent, id=wx.ID_ANY)
|
||||
self.parent = parent
|
||||
pmainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
tree = self.fittingsTreeCtrl = wx.TreeCtrl(self, wx.ID_ANY, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT)
|
||||
pmainSizer.Add(tree, 1, wx.EXPAND | wx.ALL, 0)
|
||||
|
||||
self.root = tree.AddRoot("Fits")
|
||||
self.populateSkillTree(None)
|
||||
|
||||
self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.displayFit)
|
||||
|
||||
self.SetSizer(pmainSizer)
|
||||
|
||||
self.Layout()
|
||||
|
||||
def populateSkillTree(self, data):
|
||||
if data is None:
|
||||
return
|
||||
root = self.root
|
||||
tree = self.fittingsTreeCtrl
|
||||
tree.DeleteChildren(root)
|
||||
|
||||
dict = {}
|
||||
fits = data['items']
|
||||
for fit in fits:
|
||||
if fit['ship']['name'] not in dict:
|
||||
dict[fit['ship']['name']] = []
|
||||
dict[fit['ship']['name']].append(fit)
|
||||
|
||||
for name, fits in dict.iteritems():
|
||||
shipID = tree.AppendItem(root, name)
|
||||
for fit in fits:
|
||||
fitId = tree.AppendItem(shipID, fit['name'])
|
||||
tree.SetPyData(fitId, json.dumps(fit))
|
||||
|
||||
tree.SortChildren(root)
|
||||
|
||||
def displayFit(self, event):
|
||||
selection = self.fittingsTreeCtrl.GetSelection()
|
||||
fit = json.loads(self.fittingsTreeCtrl.GetPyData(selection))
|
||||
list = []
|
||||
|
||||
for item in fit['items']:
|
||||
try:
|
||||
cargo = Cargo(getItem(item['type']['id']))
|
||||
cargo.amount = item['quantity']
|
||||
list.append(cargo)
|
||||
except:
|
||||
pass
|
||||
|
||||
self.parent.fitView.fitSelection = selection
|
||||
self.parent.fitView.update(list)
|
||||
|
||||
|
||||
class FitView(d.Display):
|
||||
DEFAULT_COLS = ["Base Icon",
|
||||
"Base Name"]
|
||||
|
||||
def __init__(self, parent):
|
||||
d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL)
|
||||
self.fitSelection = None
|
||||
@@ -80,7 +80,7 @@ class ItemStatsDialog(wx.Dialog):
|
||||
itemImg = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
if itemImg is not None:
|
||||
self.SetIcon(wx.IconFromBitmap(itemImg))
|
||||
self.SetTitle("%s: %s" % ("%s Stats" % itmContext if itmContext is not None else "Stats", item.name))
|
||||
self.SetTitle("%s: %s%s" % ("%s Stats" % itmContext if itmContext is not None else "Stats", item.name, " (%d)"%item.ID if config.debug else ""))
|
||||
|
||||
self.SetMinSize((300, 200))
|
||||
if "wxGTK" in wx.PlatformInfo: # GTK has huge tab widgets, give it a bit more room
|
||||
|
||||
@@ -30,6 +30,7 @@ from wx.lib.wordwrap import wordwrap
|
||||
import service
|
||||
import config
|
||||
import threading
|
||||
import webbrowser
|
||||
|
||||
import gui.aboutData
|
||||
import gui.chromeTabs
|
||||
@@ -44,6 +45,7 @@ from gui.multiSwitch import MultiSwitch
|
||||
from gui.statsPane import StatsPane
|
||||
from gui.shipBrowser import ShipBrowser, FitSelected, ImportSelected, Stage3Selected
|
||||
from gui.characterEditor import CharacterEditor, SaveCharacterAs
|
||||
from gui.crestFittings import CrestFittings, ExportToEve, CrestMgmt
|
||||
from gui.characterSelection import CharacterSelection
|
||||
from gui.patternEditor import DmgPatternEditorDlg
|
||||
from gui.resistsEditor import ResistsEditorDlg
|
||||
@@ -53,10 +55,18 @@ from gui.copySelectDialog import CopySelectDialog
|
||||
from gui.utils.clipboard import toClipboard, fromClipboard
|
||||
from gui.fleetBrowser import FleetBrowser
|
||||
from gui.updateDialog import UpdateDialog
|
||||
from gui.propertyEditor import AttributeEditor
|
||||
from gui.builtinViews import *
|
||||
|
||||
# import this to access override setting
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||
|
||||
from time import gmtime, strftime
|
||||
|
||||
from service.crest import CrestModes
|
||||
|
||||
from wx.lib.pubsub import setupkwargs
|
||||
from wx.lib.pubsub import pub
|
||||
|
||||
#dummy panel(no paint no erasebk)
|
||||
class PFPanel(wx.Panel):
|
||||
@@ -101,8 +111,8 @@ class MainFrame(wx.Frame):
|
||||
return cls.__instance if cls.__instance is not None else MainFrame()
|
||||
|
||||
def __init__(self):
|
||||
title="pyfa %s%s - Python Fitting Assistant"%(config.version, "" if config.tag.lower() != 'git' else " (git)")
|
||||
wx.Frame.__init__(self, None, wx.ID_ANY, title)
|
||||
self.title="pyfa %s%s - Python Fitting Assistant"%(config.version, "" if config.tag.lower() != 'git' else " (git)")
|
||||
wx.Frame.__init__(self, None, wx.ID_ANY, self.title)
|
||||
|
||||
MainFrame.__instance = self
|
||||
|
||||
@@ -193,6 +203,12 @@ class MainFrame(wx.Frame):
|
||||
self.sUpdate = service.Update.getInstance()
|
||||
self.sUpdate.CheckUpdate(self.ShowUpdateBox)
|
||||
|
||||
pub.subscribe(self.onSSOLogin, 'login_success')
|
||||
pub.subscribe(self.onSSOLogout, 'logout_success')
|
||||
|
||||
self.titleTimer = wx.Timer(self)
|
||||
self.Bind(wx.EVT_TIMER, self.updateTitle, self.titleTimer)
|
||||
|
||||
def ShowUpdateBox(self, release):
|
||||
dlg = UpdateDialog(self, release)
|
||||
dlg.ShowModal()
|
||||
@@ -329,6 +345,10 @@ class MainFrame(wx.Frame):
|
||||
dlg=CharacterEditor(self)
|
||||
dlg.Show()
|
||||
|
||||
def showAttrEditor(self, event):
|
||||
dlg=AttributeEditor(self)
|
||||
dlg.Show()
|
||||
|
||||
def showTargetResistsEditor(self, event):
|
||||
dlg=ResistsEditorDlg(self)
|
||||
dlg.ShowModal()
|
||||
@@ -370,10 +390,10 @@ class MainFrame(wx.Frame):
|
||||
dlg.ShowModal()
|
||||
|
||||
def goWiki(self, event):
|
||||
wx.LaunchDefaultBrowser('https://github.com/DarkFenX/Pyfa/wiki')
|
||||
webbrowser.open('https://github.com/DarkFenX/Pyfa/wiki')
|
||||
|
||||
def goForums(self, event):
|
||||
wx.LaunchDefaultBrowser('https://forums.eveonline.com/default.aspx?g=posts&t=247609')
|
||||
webbrowser.open('https://forums.eveonline.com/default.aspx?g=posts&t=247609')
|
||||
|
||||
def registerMenu(self):
|
||||
menuBar = self.GetMenuBar()
|
||||
@@ -417,6 +437,18 @@ class MainFrame(wx.Frame):
|
||||
# Save current character
|
||||
self.Bind(wx.EVT_MENU, self.revertChar, id = menuBar.revertCharId)
|
||||
|
||||
# Browse fittings
|
||||
self.Bind(wx.EVT_MENU, self.eveFittings, id = menuBar.eveFittingsId)
|
||||
# Export to EVE
|
||||
self.Bind(wx.EVT_MENU, self.exportToEve, id = menuBar.exportToEveId)
|
||||
# Login to EVE
|
||||
self.Bind(wx.EVT_MENU, self.ssoLogin, id = menuBar.ssoLoginId)
|
||||
|
||||
# Open attribute editor
|
||||
self.Bind(wx.EVT_MENU, self.showAttrEditor, id = menuBar.attrEditorId)
|
||||
# Toggle Overrides
|
||||
self.Bind(wx.EVT_MENU, self.toggleOverrides, id = menuBar.toggleOverridesId)
|
||||
|
||||
#Clipboard exports
|
||||
self.Bind(wx.EVT_MENU, self.exportToClipboard, id=wx.ID_COPY)
|
||||
|
||||
@@ -480,6 +512,49 @@ class MainFrame(wx.Frame):
|
||||
atable = wx.AcceleratorTable(actb)
|
||||
self.SetAcceleratorTable(atable)
|
||||
|
||||
def eveFittings(self, event):
|
||||
dlg=CrestFittings(self)
|
||||
dlg.Show()
|
||||
|
||||
def updateTitle(self, event):
|
||||
sCrest = service.Crest.getInstance()
|
||||
char = sCrest.implicitCharacter
|
||||
if char:
|
||||
t = time.gmtime(char.eve.expires-time.time())
|
||||
sTime = time.strftime("%H:%M:%S", t if t >= 0 else 0)
|
||||
newTitle = "%s | %s - %s"%(self.title, char.name, sTime)
|
||||
self.SetTitle(newTitle)
|
||||
|
||||
def onSSOLogin(self, type):
|
||||
if type == 0:
|
||||
self.titleTimer.Start(1000)
|
||||
|
||||
def onSSOLogout(self, message):
|
||||
self.titleTimer.Stop()
|
||||
self.SetTitle(self.title)
|
||||
|
||||
def ssoLogin(self, event):
|
||||
sCrest = service.Crest.getInstance()
|
||||
if sCrest.settings.get('mode') == CrestModes.IMPLICIT:
|
||||
if sCrest.implicitCharacter is not None:
|
||||
sCrest.logout()
|
||||
else:
|
||||
uri = sCrest.startServer()
|
||||
webbrowser.open(uri)
|
||||
else:
|
||||
dlg=CrestMgmt(self)
|
||||
dlg.Show()
|
||||
|
||||
def exportToEve(self, event):
|
||||
dlg=ExportToEve(self)
|
||||
dlg.Show()
|
||||
|
||||
def toggleOverrides(self, event):
|
||||
ModifiedAttributeDict.OVERRIDES = not ModifiedAttributeDict.OVERRIDES
|
||||
wx.PostEvent(self, GE.FitChanged(fitID=self.getActiveFit()))
|
||||
menu = self.GetMenuBar()
|
||||
menu.SetLabel(menu.toggleOverridesId, "Turn Overrides Off" if ModifiedAttributeDict.OVERRIDES else "Turn Overrides On")
|
||||
|
||||
def saveChar(self, event):
|
||||
sChr = service.Character.getInstance()
|
||||
charID = self.charSelection.getActiveCharacter()
|
||||
@@ -542,6 +617,10 @@ class MainFrame(wx.Frame):
|
||||
sFit = service.Fit.getInstance()
|
||||
toClipboard(sFit.exportDna(self.getActiveFit()))
|
||||
|
||||
def clipboardCrest(self):
|
||||
sFit = service.Fit.getInstance()
|
||||
toClipboard(sFit.exportCrest(self.getActiveFit()))
|
||||
|
||||
def clipboardXml(self):
|
||||
sFit = service.Fit.getInstance()
|
||||
toClipboard(sFit.exportXml(None, self.getActiveFit()))
|
||||
@@ -559,14 +638,15 @@ class MainFrame(wx.Frame):
|
||||
CopySelectDict = {CopySelectDialog.copyFormatEft: self.clipboardEft,
|
||||
CopySelectDialog.copyFormatEftImps: self.clipboardEftImps,
|
||||
CopySelectDialog.copyFormatXml: self.clipboardXml,
|
||||
CopySelectDialog.copyFormatDna: self.clipboardDna}
|
||||
CopySelectDialog.copyFormatDna: self.clipboardDna,
|
||||
CopySelectDialog.copyFormatCrest: self.clipboardCrest}
|
||||
dlg = CopySelectDialog(self)
|
||||
dlg.ShowModal()
|
||||
selected = dlg.GetSelected()
|
||||
try:
|
||||
CopySelectDict[selected]()
|
||||
except:
|
||||
pass
|
||||
|
||||
CopySelectDict[selected]()
|
||||
|
||||
|
||||
dlg.Destroy()
|
||||
|
||||
def exportSkillsNeeded(self, event):
|
||||
|
||||
@@ -24,6 +24,10 @@ import gui.mainFrame
|
||||
import gui.graphFrame
|
||||
import gui.globalEvents as GE
|
||||
import service
|
||||
from service.crest import CrestModes
|
||||
|
||||
from wx.lib.pubsub import setupkwargs
|
||||
from wx.lib.pubsub import pub
|
||||
|
||||
class MainMenuBar(wx.MenuBar):
|
||||
def __init__(self):
|
||||
@@ -40,9 +44,16 @@ class MainMenuBar(wx.MenuBar):
|
||||
self.saveCharId = wx.NewId()
|
||||
self.saveCharAsId = wx.NewId()
|
||||
self.revertCharId = wx.NewId()
|
||||
self.eveFittingsId = wx.NewId()
|
||||
self.exportToEveId = wx.NewId()
|
||||
self.ssoLoginId = wx.NewId()
|
||||
self.attrEditorId = wx.NewId()
|
||||
self.toggleOverridesId = wx.NewId()
|
||||
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
self.sCrest = service.Crest.getInstance()
|
||||
|
||||
wx.MenuBar.__init__(self)
|
||||
|
||||
# File menu
|
||||
@@ -78,6 +89,9 @@ class MainMenuBar(wx.MenuBar):
|
||||
editMenu.Append(self.saveCharId, "Save Character")
|
||||
editMenu.Append(self.saveCharAsId, "Save Character As...")
|
||||
editMenu.Append(self.revertCharId, "Revert Character")
|
||||
editMenu.AppendSeparator()
|
||||
editMenu.Append(self.toggleOverridesId, "Turn Overrides On")
|
||||
|
||||
# Character menu
|
||||
windowMenu = wx.Menu()
|
||||
self.Append(windowMenu, "&Window")
|
||||
@@ -102,6 +116,24 @@ class MainMenuBar(wx.MenuBar):
|
||||
preferencesItem.SetBitmap(BitmapLoader.getBitmap("preferences_small", "gui"))
|
||||
windowMenu.AppendItem(preferencesItem)
|
||||
|
||||
# CREST Menu
|
||||
crestMenu = wx.Menu()
|
||||
self.Append(crestMenu, "&CREST")
|
||||
if self.sCrest.settings.get('mode') != CrestModes.IMPLICIT:
|
||||
crestMenu.Append(self.ssoLoginId, "Manage Characters")
|
||||
else:
|
||||
crestMenu.Append(self.ssoLoginId, "Login to EVE")
|
||||
crestMenu.Append(self.eveFittingsId, "Browse EVE Fittings")
|
||||
crestMenu.Append(self.exportToEveId, "Export To EVE")
|
||||
|
||||
if self.sCrest.settings.get('mode') == CrestModes.IMPLICIT or len(self.sCrest.getCrestCharacters()) == 0:
|
||||
self.Enable(self.eveFittingsId, False)
|
||||
self.Enable(self.exportToEveId, False)
|
||||
|
||||
attrItem = wx.MenuItem(windowMenu, self.attrEditorId, "Attribute Overrides\tCTRL+A")
|
||||
attrItem.SetBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui"))
|
||||
windowMenu.AppendItem(attrItem)
|
||||
|
||||
# Help menu
|
||||
helpMenu = wx.Menu()
|
||||
self.Append(helpMenu, "&Help")
|
||||
@@ -114,6 +146,9 @@ class MainMenuBar(wx.MenuBar):
|
||||
helpMenu.Append( self.mainFrame.widgetInspectMenuID, "Open Widgets Inspect tool", "Open Widgets Inspect tool")
|
||||
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||
pub.subscribe(self.ssoLogin, 'login_success')
|
||||
pub.subscribe(self.ssoLogout, 'logout_success')
|
||||
pub.subscribe(self.updateCrest, 'crest_changed')
|
||||
|
||||
def fitChanged(self, event):
|
||||
enable = event.fitID is not None
|
||||
@@ -131,3 +166,25 @@ class MainMenuBar(wx.MenuBar):
|
||||
self.Enable(self.revertCharId, char.isDirty)
|
||||
|
||||
event.Skip()
|
||||
|
||||
def ssoLogin(self, type):
|
||||
if self.sCrest.settings.get('mode') == CrestModes.IMPLICIT:
|
||||
self.SetLabel(self.ssoLoginId, "Logout Character")
|
||||
self.Enable(self.eveFittingsId, True)
|
||||
self.Enable(self.exportToEveId, True)
|
||||
|
||||
def ssoLogout(self, message):
|
||||
if self.sCrest.settings.get('mode') == CrestModes.IMPLICIT:
|
||||
self.SetLabel(self.ssoLoginId, "Login to EVE")
|
||||
self.Enable(self.eveFittingsId, False)
|
||||
self.Enable(self.exportToEveId, False)
|
||||
|
||||
def updateCrest(self, message):
|
||||
bool = self.sCrest.settings.get('mode') == CrestModes.IMPLICIT or len(self.sCrest.getCrestCharacters()) == 0
|
||||
self.Enable(self.eveFittingsId, not bool)
|
||||
self.Enable(self.exportToEveId, not bool)
|
||||
if self.sCrest.settings.get('mode') == CrestModes.IMPLICIT:
|
||||
self.SetLabel(self.ssoLoginId, "Login to EVE")
|
||||
else:
|
||||
self.SetLabel(self.ssoLoginId, "Manage Characters")
|
||||
|
||||
|
||||
@@ -103,8 +103,8 @@ class MarketBrowser(wx.Panel):
|
||||
self.marketView.jump(item)
|
||||
|
||||
class SearchBox(SBox.PFSearchBox):
|
||||
def __init__(self, parent):
|
||||
SBox.PFSearchBox.__init__(self, parent)
|
||||
def __init__(self, parent, **kwargs):
|
||||
SBox.PFSearchBox.__init__(self, parent, **kwargs)
|
||||
cancelBitmap = BitmapLoader.getBitmap("fit_delete_small","gui")
|
||||
searchBitmap = BitmapLoader.getBitmap("fsearch_small","gui")
|
||||
self.SetSearchBitmap(searchBitmap)
|
||||
|
||||
264
gui/propertyEditor.py
Normal file
264
gui/propertyEditor.py
Normal file
@@ -0,0 +1,264 @@
|
||||
import wx
|
||||
import wx.propgrid as wxpg
|
||||
|
||||
import gui.PFSearchBox as SBox
|
||||
from gui.marketBrowser import SearchBox
|
||||
import gui.display as d
|
||||
import gui.globalEvents as GE
|
||||
from gui.bitmapLoader import BitmapLoader
|
||||
import service
|
||||
import csv
|
||||
import eos.db
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class AttributeEditor( wx.Frame ):
|
||||
|
||||
def __init__( self, parent ):
|
||||
wx.Frame.__init__(self, parent, wx.ID_ANY, title="Attribute Editor", pos=wx.DefaultPosition,
|
||||
size=wx.Size(650, 600), style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.TAB_TRAVERSAL)
|
||||
|
||||
i = wx.IconFromBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui"))
|
||||
self.SetIcon(i)
|
||||
|
||||
self.mainFrame = parent
|
||||
|
||||
menubar = wx.MenuBar()
|
||||
fileMenu = wx.Menu()
|
||||
fileImport = fileMenu.Append(wx.ID_ANY, 'Import', 'Import overrides')
|
||||
fileExport = fileMenu.Append(wx.ID_ANY, 'Export', 'Import overrides')
|
||||
fileClear = fileMenu.Append(wx.ID_ANY, 'Clear All', 'Clear all overrides')
|
||||
|
||||
menubar.Append(fileMenu, '&File')
|
||||
self.SetMenuBar(menubar)
|
||||
|
||||
self.Bind(wx.EVT_MENU, self.OnImport, fileImport)
|
||||
self.Bind(wx.EVT_MENU, self.OnExport, fileExport)
|
||||
self.Bind(wx.EVT_MENU, self.OnClear, fileClear)
|
||||
|
||||
|
||||
i = wx.IconFromBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui"))
|
||||
self.SetIcon(i)
|
||||
|
||||
self.mainFrame = parent
|
||||
self.panel = panel = wx.Panel(self, wx.ID_ANY)
|
||||
|
||||
mainSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
leftSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
leftPanel = wx.Panel(panel, wx.ID_ANY, style=wx.DOUBLE_BORDER if 'wxMSW' in wx.PlatformInfo else wx.SIMPLE_BORDER)
|
||||
|
||||
self.searchBox = SearchBox(leftPanel)
|
||||
self.itemView = ItemView(leftPanel)
|
||||
|
||||
leftSizer.Add(self.searchBox, 0, wx.EXPAND)
|
||||
leftSizer.Add(self.itemView, 1, wx.EXPAND)
|
||||
|
||||
leftPanel.SetSizer(leftSizer)
|
||||
mainSizer.Add(leftPanel, 1, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
rightSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.btnRemoveOverrides = wx.Button( panel, wx.ID_ANY, u"Remove Overides for Item", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.pg = AttributeGrid(panel)
|
||||
rightSizer.Add(self.pg, 1, wx.ALL|wx.EXPAND, 5)
|
||||
rightSizer.Add(self.btnRemoveOverrides, 0, wx.ALL | wx.EXPAND, 5 )
|
||||
self.btnRemoveOverrides.Bind(wx.EVT_BUTTON, self.pg.removeOverrides)
|
||||
self.btnRemoveOverrides.Enable(False)
|
||||
|
||||
mainSizer.Add(rightSizer, 1, wx.EXPAND)
|
||||
|
||||
panel.SetSizer(mainSizer)
|
||||
mainSizer.SetSizeHints(panel)
|
||||
|
||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
sizer.Add(panel, 1, wx.EXPAND)
|
||||
self.SetSizer(sizer)
|
||||
self.SetAutoLayout(True)
|
||||
|
||||
self.Bind(wx.EVT_CLOSE, self.OnClose)
|
||||
|
||||
def OnClose(self, event):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is not None:
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
self.Destroy()
|
||||
|
||||
def OnImport(self, event):
|
||||
dlg = wx.FileDialog(self, "Import pyfa override file",
|
||||
wildcard = "pyfa override file (*.csv)|*.csv",
|
||||
style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
|
||||
if (dlg.ShowModal() == wx.ID_OK):
|
||||
path = dlg.GetPath()
|
||||
with open(path, 'rb') as csvfile:
|
||||
spamreader = csv.reader(csvfile)
|
||||
for row in spamreader:
|
||||
itemID, attrID, value = row
|
||||
item = eos.db.getItem(int(itemID))
|
||||
attr = eos.db.getAttributeInfo(int(attrID))
|
||||
item.setOverride(attr, float(value))
|
||||
self.itemView.updateItems(True)
|
||||
|
||||
def OnExport(self, event):
|
||||
sMkt = service.Market.getInstance()
|
||||
items = sMkt.getItemsWithOverrides()
|
||||
defaultFile = "pyfa_overrides.csv"
|
||||
|
||||
dlg = wx.FileDialog(self, "Save Overrides As...",
|
||||
wildcard = "pyfa overrides (*.csv)|*.csv",
|
||||
style = wx.FD_SAVE,
|
||||
defaultFile=defaultFile)
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
path = dlg.GetPath()
|
||||
with open(path, 'wb') as csvfile:
|
||||
writer = csv.writer(csvfile)
|
||||
for item in items:
|
||||
for key, override in item.overrides.iteritems():
|
||||
writer.writerow([item.ID, override.attrID, override.value])
|
||||
|
||||
def OnClear(self, event):
|
||||
dlg = wx.MessageDialog(self,
|
||||
"Are you sure you want to delete all overrides?",
|
||||
"Confirm Delete", wx.YES | wx.NO | wx.ICON_EXCLAMATION)
|
||||
|
||||
if dlg.ShowModal() == wx.ID_YES:
|
||||
sMkt = service.Market.getInstance()
|
||||
items = sMkt.getItemsWithOverrides()
|
||||
# We can't just delete overrides, as loaded items will still have
|
||||
# them assigned. Deleting them from the database won't propagate
|
||||
# them due to the eve/user database disconnect. We must loop through
|
||||
# all items that have overrides and remove them
|
||||
for item in items:
|
||||
for _, x in item.overrides.items():
|
||||
item.deleteOverride(x.attr)
|
||||
self.itemView.updateItems(True)
|
||||
self.pg.Clear()
|
||||
|
||||
# This is literally a stripped down version of the market.
|
||||
class ItemView(d.Display):
|
||||
DEFAULT_COLS = ["Base Icon",
|
||||
"Base Name",
|
||||
"attr:power,,,True",
|
||||
"attr:cpu,,,True"]
|
||||
|
||||
def __init__(self, parent):
|
||||
d.Display.__init__(self, parent)
|
||||
sMkt = service.Market.getInstance()
|
||||
|
||||
self.things = sMkt.getItemsWithOverrides()
|
||||
self.items = self.things
|
||||
|
||||
self.searchBox = parent.Parent.Parent.searchBox
|
||||
# Bind search actions
|
||||
self.searchBox.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch)
|
||||
self.searchBox.Bind(SBox.EVT_SEARCH_BTN, self.scheduleSearch)
|
||||
self.searchBox.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch)
|
||||
self.searchBox.Bind(SBox.EVT_TEXT, self.scheduleSearch)
|
||||
|
||||
self.update(self.items)
|
||||
|
||||
def clearSearch(self, event=None):
|
||||
if event:
|
||||
self.searchBox.Clear()
|
||||
self.items = self.things
|
||||
self.update(self.items)
|
||||
|
||||
def updateItems(self, updateDisplay=False):
|
||||
sMkt = service.Market.getInstance()
|
||||
self.things = sMkt.getItemsWithOverrides()
|
||||
self.items = self.things
|
||||
if updateDisplay:
|
||||
self.update(self.things)
|
||||
|
||||
def scheduleSearch(self, event=None):
|
||||
sMkt = service.Market.getInstance()
|
||||
|
||||
search = self.searchBox.GetLineText(0)
|
||||
# Make sure we do not count wildcard as search symbol
|
||||
realsearch = search.replace("*", "")
|
||||
# Show nothing if query is too short
|
||||
if len(realsearch) < 3:
|
||||
self.clearSearch()
|
||||
return
|
||||
|
||||
sMkt.searchItems(search, self.populateSearch, False)
|
||||
|
||||
def populateSearch(self, items):
|
||||
self.items = list(items)
|
||||
self.update(items)
|
||||
|
||||
|
||||
class AttributeGrid(wxpg.PropertyGrid):
|
||||
|
||||
def __init__(self, parent):
|
||||
wxpg.PropertyGrid.__init__(self, parent, style=wxpg.PG_HIDE_MARGIN|wxpg.PG_HIDE_CATEGORIES|wxpg.PG_BOLD_MODIFIED|wxpg.PG_TOOLTIPS)
|
||||
self.SetExtraStyle(wxpg.PG_EX_HELP_AS_TOOLTIPS)
|
||||
|
||||
self.item = None
|
||||
|
||||
self.itemView = parent.Parent.itemView
|
||||
|
||||
self.btn = parent.Parent.btnRemoveOverrides
|
||||
|
||||
self.Bind( wxpg.EVT_PG_CHANGED, self.OnPropGridChange )
|
||||
self.Bind( wxpg.EVT_PG_SELECTED, self.OnPropGridSelect )
|
||||
self.Bind( wxpg.EVT_PG_RIGHT_CLICK, self.OnPropGridRightClick )
|
||||
|
||||
self.itemView.Bind(wx.EVT_LIST_ITEM_SELECTED, self.itemActivated)
|
||||
self.itemView.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemActivated)
|
||||
|
||||
def itemActivated(self, event):
|
||||
self.Clear()
|
||||
self.btn.Enable(True)
|
||||
sel = event.EventObject.GetFirstSelected()
|
||||
self.item = item = self.itemView.items[sel]
|
||||
|
||||
for key in sorted(item.attributes.keys()):
|
||||
override = item.overrides.get(key, None)
|
||||
default = item.attributes[key].value
|
||||
if override and override.value != default:
|
||||
prop = wxpg.FloatProperty(key, value=override.value)
|
||||
prop.SetModifiedStatus(True)
|
||||
else:
|
||||
prop = wxpg.FloatProperty(key, value=default)
|
||||
|
||||
prop.SetClientData(item.attributes[key]) # set this so that we may access it later
|
||||
prop.SetHelpString("%s\n%s"%(item.attributes[key].displayName or key, "Default Value: %0.3f"%default))
|
||||
self.Append(prop)
|
||||
|
||||
def removeOverrides(self, event):
|
||||
if self.item is None:
|
||||
return
|
||||
|
||||
for _, x in self.item.overrides.items():
|
||||
self.item.deleteOverride(x.attr)
|
||||
self.itemView.updateItems(True)
|
||||
self.ClearModifiedStatus()
|
||||
self.itemView.Select(self.itemView.GetFirstSelected(), on=False)
|
||||
self.Clear()
|
||||
|
||||
def Clear(self):
|
||||
self.item = None
|
||||
self.btn.Enable(False)
|
||||
wxpg.PropertyGrid.Clear(self)
|
||||
|
||||
def OnPropGridChange(self, event):
|
||||
p = event.GetProperty()
|
||||
attr = p.GetClientData()
|
||||
if p.GetValue() == attr.value:
|
||||
self.item.deleteOverride(attr)
|
||||
p.SetModifiedStatus(False)
|
||||
else:
|
||||
self.item.setOverride(attr, p.GetValue())
|
||||
|
||||
self.itemView.updateItems()
|
||||
|
||||
logger.debug('%s changed to "%s"' % (p.GetName(), p.GetValueAsString()))
|
||||
|
||||
def OnPropGridSelect(self, event):
|
||||
pass
|
||||
|
||||
def OnPropGridRightClick(self, event):
|
||||
pass
|
||||
BIN
imgs/gui/eve.png
Normal file
BIN
imgs/gui/eve.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
@@ -103,7 +103,7 @@ def main(old, new, groups=True, effects=True, attributes=True, renames=True):
|
||||
if implementedtag:
|
||||
print("\n[{0}] \"{1}\"\n[{2}] \"{3}\"".format(geteffst(couple[0]), couple[0], geteffst(couple[1]), couple[1]))
|
||||
else:
|
||||
print("\n\"{0}\"\n\"{1}\"".format(couple[0], couple[1]))
|
||||
print(" \"{0}\": \"{1}\",".format(couple[0], couple[1]))
|
||||
|
||||
groupcats = {}
|
||||
def getgroupcat(grp):
|
||||
|
||||
@@ -62,27 +62,25 @@ if not args.nojson:
|
||||
pickle_miner = ResourcePickleMiner(rvr)
|
||||
trans = Translator(pickle_miner)
|
||||
bulkdata_miner = BulkdataMiner(rvr, trans)
|
||||
staticcache_miner = StaticdataCacheMiner(eve_path, trans)
|
||||
|
||||
staticcache_miner = ResourceStaticCacheMiner(rvr, trans)
|
||||
miners = (
|
||||
MetadataMiner(eve_path),
|
||||
bulkdata_miner,
|
||||
TraitMiner(staticcache_miner, bulkdata_miner, trans),
|
||||
SqliteMiner(eve_path, trans),
|
||||
staticcache_miner,
|
||||
#CachedCallsMiner(rvr, trans),
|
||||
TraitMiner(staticcache_miner, bulkdata_miner, trans),
|
||||
SqliteMiner(rvr.paths.root, trans),
|
||||
CachedCallsMiner(rvr, trans),
|
||||
pickle_miner
|
||||
)
|
||||
|
||||
|
||||
writers = (
|
||||
JsonWriter(dump_path, indent=2),
|
||||
)
|
||||
|
||||
list = "dgmexpressions,dgmattribs,dgmeffects,dgmtypeattribs,dgmtypeeffects,"\
|
||||
"dgmunits,icons,invcategories,invgroups,invmetagroups,invmetatypes,"\
|
||||
"invtypes,mapbulk_marketGroups,phbmetadata,phbtraits,fsdTypeOverrides"\
|
||||
"evegroups,evetypes"
|
||||
"invtypes,mapbulk_marketGroups,phbmetadata,phbtraits,fsdTypeOverrides,"\
|
||||
"evegroups,evetypes,evecategories"
|
||||
|
||||
FlowManager(miners, writers).run(list, "multi")
|
||||
|
||||
|
||||
@@ -10,3 +10,6 @@ from service.update import Update
|
||||
from service.price import Price
|
||||
from service.network import Network
|
||||
from service.eveapi import EVEAPIConnection, ParseXML
|
||||
from service.crest import Crest
|
||||
from service.server import StoppableHTTPServer, AuthHandler
|
||||
from service.pycrest import EVE
|
||||
|
||||
174
service/conversions/releaseParallax.py
Normal file
174
service/conversions/releaseParallax.py
Normal file
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
Conversion pack for Parallax renames
|
||||
"""
|
||||
|
||||
CONVERSIONS = {
|
||||
# Renamed items
|
||||
"Basic Thermic Plating": "Basic Thermal Plating",
|
||||
"Thermic Plating I": "Thermal Plating I",
|
||||
"Thermic Plating II": "Thermal Plating II",
|
||||
"Basic Thermic Dissipation Amplifier": "Basic Thermal Dissipation Amplifier",
|
||||
"Thermic Dissipation Field I": "Thermal Dissipation Field I",
|
||||
"Thermic Dissipation Field II": "Thermal Dissipation Field II",
|
||||
"Thermic Dissipation Amplifier I": "Thermal Dissipation Amplifier I",
|
||||
"Thermic Dissipation Amplifier II": "Thermal Dissipation Amplifier II",
|
||||
"Supplemental Thermic Dissipation Amplifier": "Supplemental Thermal Dissipation Amplifier",
|
||||
"Upgraded Thermic Dissipation Amplifier I": "Upgraded Thermal Dissipation Amplifier I",
|
||||
"Limited Thermic Dissipation Field I": "Limited Thermal Dissipation Field I",
|
||||
"Basic Energized Thermic Membrane": "Basic Energized Thermal Membrane",
|
||||
"Energized Thermic Membrane I": "Energized Thermal Membrane I",
|
||||
"Energized Thermic Membrane II": "Energized Thermal Membrane II",
|
||||
"Armor Thermic Hardener I": "Armor Thermal Hardener I",
|
||||
"Thermic Shield Compensation": "Thermal Shield Compensation",
|
||||
"Armor Thermic Hardener II": "Armor Thermal Hardener II",
|
||||
"Dread Guristas Thermic Dissipation Field": "Dread Guristas Thermal Dissipation Field",
|
||||
"True Sansha Armor Thermic Hardener": "True Sansha Armor Thermal Hardener",
|
||||
"Dark Blood Armor Thermic Hardener": "Dark Blood Armor Thermal Hardener",
|
||||
"Domination Armor Thermic Hardener": "Domination Armor Thermal Hardener",
|
||||
"Domination Thermic Dissipation Field": "Domination Thermal Dissipation Field",
|
||||
"Domination Thermic Plating": "Domination Thermal Plating",
|
||||
"True Sansha Thermic Plating": "True Sansha Thermal Plating",
|
||||
"Dark Blood Thermic Plating": "Dark Blood Thermal Plating",
|
||||
"Domination Thermic Dissipation Amplifier": "Domination Thermal Dissipation Amplifier",
|
||||
"Dread Guristas Thermic Dissipation Amplifier": "Dread Guristas Thermal Dissipation Amplifier",
|
||||
"Shadow Serpentis Thermic Plating": "Shadow Serpentis Thermal Plating",
|
||||
"Shadow Serpentis Armor Thermic Hardener": "Shadow Serpentis Armor Thermal Hardener",
|
||||
"Dark Blood Energized Thermic Membrane": "Dark Blood Energized Thermal Membrane",
|
||||
"True Sansha Energized Thermic Membrane": "True Sansha Energized Thermal Membrane",
|
||||
"Shadow Serpentis Energized Thermic Membrane": "Shadow Serpentis Energized Thermal Membrane",
|
||||
"Mizuro's Modified Thermic Plating": "Mizuro's Modified Thermal Plating",
|
||||
"Gotan's Modified Thermic Plating": "Gotan's Modified Thermal Plating",
|
||||
"Hakim's Modified Thermic Dissipation Amplifier": "Hakim's Modified Thermal Dissipation Amplifier",
|
||||
"Tobias' Modified Thermic Dissipation Amplifier": "Tobias' Modified Thermal Dissipation Amplifier",
|
||||
"Kaikka's Modified Thermic Dissipation Amplifier": "Kaikka's Modified Thermal Dissipation Amplifier",
|
||||
"Thon's Modified Thermic Dissipation Amplifier": "Thon's Modified Thermal Dissipation Amplifier",
|
||||
"Vepas' Modified Thermic Dissipation Amplifier": "Vepas' Modified Thermal Dissipation Amplifier",
|
||||
"Estamel's Modified Thermic Dissipation Amplifier": "Estamel's Modified Thermal Dissipation Amplifier",
|
||||
"Kaikka's Modified Thermic Dissipation Field": "Kaikka's Modified Thermal Dissipation Field",
|
||||
"Thon's Modified Thermic Dissipation Field": "Thon's Modified Thermal Dissipation Field",
|
||||
"Vepas's Modified Thermic Dissipation Field": "Vepas's Modified Thermal Dissipation Field",
|
||||
"Estamel's Modified Thermic Dissipation Field": "Estamel's Modified Thermal Dissipation Field",
|
||||
"Brokara's Modified Thermic Plating": "Brokara's Modified Thermal Plating",
|
||||
"Tairei's Modified Thermic Plating": "Tairei's Modified Thermal Plating",
|
||||
"Selynne's Modified Thermic Plating": "Selynne's Modified Thermal Plating",
|
||||
"Raysere's Modified Thermic Plating": "Raysere's Modified Thermal Plating",
|
||||
"Vizan's Modified Thermic Plating": "Vizan's Modified Thermal Plating",
|
||||
"Ahremen's Modified Thermic Plating": "Ahremen's Modified Thermal Plating",
|
||||
"Chelm's Modified Thermic Plating": "Chelm's Modified Thermal Plating",
|
||||
"Draclira's Modified Thermic Plating": "Draclira's Modified Thermal Plating",
|
||||
"Brokara's Modified Energized Thermic Membrane": "Brokara's Modified Energized Thermal Membrane",
|
||||
"Tairei's Modified Energized Thermic Membrane": "Tairei's Modified Energized Thermal Membrane",
|
||||
"Selynne's Modified Energized Thermic Membrane": "Selynne's Modified Energized Thermal Membrane",
|
||||
"Raysere's Modified Energized Thermic Membrane": "Raysere's Modified Energized Thermal Membrane",
|
||||
"Vizan's Modified Energized Thermic Membrane": "Vizan's Modified Energized Thermal Membrane",
|
||||
"Ahremen's Modified Energized Thermic Membrane": "Ahremen's Modified Energized Thermal Membrane",
|
||||
"Chelm's Modified Energized Thermic Membrane": "Chelm's Modified Energized Thermal Membrane",
|
||||
"Draclira's Modified Energized Thermic Membrane": "Draclira's Modified Energized Thermal Membrane",
|
||||
"Brokara's Modified Armor Thermic Hardener": "Brokara's Modified Armor Thermal Hardener",
|
||||
"Tairei's Modified Armor Thermic Hardener": "Tairei's Modified Armor Thermal Hardener",
|
||||
"Selynne's Modified Armor Thermic Hardener": "Selynne's Modified Armor Thermal Hardener",
|
||||
"Raysere's Modified Armor Thermic Hardener": "Raysere's Modified Armor Thermal Hardener",
|
||||
"Vizan's Modified Armor Thermic Hardener": "Vizan's Modified Armor Thermal Hardener",
|
||||
"Ahremen's Modified Armor Thermic Hardener": "Ahremen's Modified Armor Thermal Hardener",
|
||||
"Chelm's Modified Armor Thermic Hardener": "Chelm's Modified Armor Thermal Hardener",
|
||||
"Draclira's Modified Armor Thermic Hardener": "Draclira's Modified Armor Thermal Hardener",
|
||||
"Brynn's Modified Thermic Plating": "Brynn's Modified Thermal Plating",
|
||||
"Tuvan's Modified Thermic Plating": "Tuvan's Modified Thermal Plating",
|
||||
"Setele's Modified Thermic Plating": "Setele's Modified Thermal Plating",
|
||||
"Cormack's Modified Thermic Plating": "Cormack's Modified Thermal Plating",
|
||||
"Brynn's Modified Energized Thermic Membrane": "Brynn's Modified Energized Thermal Membrane",
|
||||
"Tuvan's Modified Energized Thermic Membrane": "Tuvan's Modified Energized Thermal Membrane",
|
||||
"Setele's Modified Energized Thermic Membrane": "Setele's Modified Energized Thermal Membrane",
|
||||
"Cormack's Modified Energized Thermic Membrane": "Cormack's Modified Energized Thermal Membrane",
|
||||
"Brynn's Modified Armor Thermic Hardener": "Brynn's Modified Armor Thermal Hardener",
|
||||
"Tuvan's Modified Armor Thermic Hardener": "Tuvan's Modified Armor Thermal Hardener",
|
||||
"Setele's Modified Armor Thermic Hardener": "Setele's Modified Armor Thermal Hardener",
|
||||
"Cormack's Modified Armor Thermic Hardener": "Cormack's Modified Armor Thermal Hardener",
|
||||
"Shaqil's Modified Energized Thermic Membrane": "Shaqil's Modified Energized Thermal Membrane",
|
||||
"Imperial Navy Thermic Plating": "Imperial Navy Thermal Plating",
|
||||
"Republic Fleet Thermic Plating": "Republic Fleet Thermal Plating",
|
||||
"Imperial Navy Armor Thermic Hardener": "Imperial Navy Armor Thermal Hardener",
|
||||
"Republic Fleet Armor Thermic Hardener": "Republic Fleet Armor Thermal Hardener",
|
||||
"Imperial Navy Energized Thermic Membrane": "Imperial Navy Energized Thermal Membrane",
|
||||
"Federation Navy Energized Thermic Membrane": "Federation Navy Energized Thermal Membrane",
|
||||
"Caldari Navy Thermic Dissipation Amplifier": "Caldari Navy Thermal Dissipation Amplifier",
|
||||
"Republic Fleet Thermic Dissipation Amplifier": "Republic Fleet Thermal Dissipation Amplifier",
|
||||
"Upgraded Thermic Plating I": "Upgraded Thermal Plating I",
|
||||
"Limited Thermic Plating I": "Limited Thermal Plating I",
|
||||
"Experimental Thermic Plating I": "Experimental Thermal Plating I",
|
||||
"Prototype Thermic Plating I": "Prototype Thermal Plating I",
|
||||
"Upgraded Armor Thermic Hardener I": "Upgraded Armor Thermal Hardener I",
|
||||
"Limited Armor Thermic Hardener I": "Limited Armor Thermal Hardener I",
|
||||
"Experimental Armor Thermic Hardener I": "Experimental Armor Thermal Hardener I",
|
||||
"Prototype Armor Thermic Hardener I": "Prototype Armor Thermal Hardener I",
|
||||
"Upgraded Energized Thermic Membrane I": "Upgraded Energized Thermal Membrane I",
|
||||
"Limited Energized Thermic Membrane I": "Limited Energized Thermal Membrane I",
|
||||
"Experimental Energized Thermic Membrane I": "Experimental Energized Thermal Membrane I",
|
||||
"Prototype Energized Thermic Membrane I": "Prototype Energized Thermal Membrane I",
|
||||
"Caldari Navy Thermic Dissipation Field": "Caldari Navy Thermal Dissipation Field",
|
||||
"Ammatar Navy Armor Thermic Hardener": "Ammatar Navy Armor Thermal Hardener",
|
||||
"Ammatar Navy Energized Thermic Membrane": "Ammatar Navy Energized Thermal Membrane",
|
||||
"Federation Navy Thermic Plating": "Federation Navy Thermal Plating",
|
||||
"Federation Navy Armor Thermic Hardener": "Federation Navy Armor Thermal Hardener",
|
||||
"Corpii C-Type Thermic Plating": "Corpii C-Type Thermal Plating",
|
||||
"Centii C-Type Thermic Plating": "Centii C-Type Thermal Plating",
|
||||
"Corpii B-Type Thermic Plating": "Corpii B-Type Thermal Plating",
|
||||
"Centii B-Type Thermic Plating": "Centii B-Type Thermal Plating",
|
||||
"Corpii A-Type Thermic Plating": "Corpii A-Type Thermal Plating",
|
||||
"Centii A-Type Thermic Plating": "Centii A-Type Thermal Plating",
|
||||
"Coreli C-Type Thermic Plating": "Coreli C-Type Thermal Plating",
|
||||
"Coreli B-Type Thermic Plating": "Coreli B-Type Thermal Plating",
|
||||
"Coreli A-Type Thermic Plating": "Coreli A-Type Thermal Plating",
|
||||
"Corelum C-Type Energized Thermic Membrane": "Corelum C-Type Energized Thermal Membrane",
|
||||
"Corelum B-Type Energized Thermic Membrane": "Corelum B-Type Energized Thermal Membrane",
|
||||
"Corelum A-Type Energized Thermic Membrane": "Corelum A-Type Energized Thermal Membrane",
|
||||
"Corpum C-Type Energized Thermic Membrane": "Corpum C-Type Energized Thermal Membrane",
|
||||
"Centum C-Type Energized Thermic Membrane": "Centum C-Type Energized Thermal Membrane",
|
||||
"Corpum B-Type Energized Thermic Membrane": "Corpum B-Type Energized Thermal Membrane",
|
||||
"Centum B-Type Energized Thermic Membrane": "Centum B-Type Energized Thermal Membrane",
|
||||
"Corpum A-Type Energized Thermic Membrane": "Corpum A-Type Energized Thermal Membrane",
|
||||
"Centum A-Type Energized Thermic Membrane": "Centum A-Type Energized Thermal Membrane",
|
||||
"Corpus C-Type Armor Thermic Hardener": "Corpus C-Type Armor Thermal Hardener",
|
||||
"Centus C-Type Armor Thermic Hardener": "Centus C-Type Armor Thermal Hardener",
|
||||
"Corpus B-Type Armor Thermic Hardener": "Corpus B-Type Armor Thermal Hardener",
|
||||
"Centus B-Type Armor Thermic Hardener": "Centus B-Type Armor Thermal Hardener",
|
||||
"Corpus A-Type Armor Thermic Hardener": "Corpus A-Type Armor Thermal Hardener",
|
||||
"Centus A-Type Armor Thermic Hardener": "Centus A-Type Armor Thermal Hardener",
|
||||
"Corpus X-Type Armor Thermic Hardener": "Corpus X-Type Armor Thermal Hardener",
|
||||
"Centus X-Type Armor Thermic Hardener": "Centus X-Type Armor Thermal Hardener",
|
||||
"Core C-Type Armor Thermic Hardener": "Core C-Type Armor Thermal Hardener",
|
||||
"Core B-Type Armor Thermic Hardener": "Core B-Type Armor Thermal Hardener",
|
||||
"Core A-Type Armor Thermic Hardener": "Core A-Type Armor Thermal Hardener",
|
||||
"Core X-Type Armor Thermic Hardener": "Core X-Type Armor Thermal Hardener",
|
||||
"Pithum C-Type Thermic Dissipation Amplifier": "Pithum C-Type Thermal Dissipation Amplifier",
|
||||
"Pithum B-Type Thermic Dissipation Amplifier": "Pithum B-Type Thermal Dissipation Amplifier",
|
||||
"Pithum A-Type Thermic Dissipation Amplifier": "Pithum A-Type Thermal Dissipation Amplifier",
|
||||
"Gistum C-Type Thermic Dissipation Amplifier": "Gistum C-Type Thermal Dissipation Amplifier",
|
||||
"Gistum B-Type Thermic Dissipation Amplifier": "Gistum B-Type Thermal Dissipation Amplifier",
|
||||
"Gistum A-Type Thermic Dissipation Amplifier": "Gistum A-Type Thermal Dissipation Amplifier",
|
||||
"Gist C-Type Thermic Dissipation Field": "Gist C-Type Thermal Dissipation Field",
|
||||
"Pith C-Type Thermic Dissipation Field": "Pith C-Type Thermal Dissipation Field",
|
||||
"Gist B-Type Thermic Dissipation Field": "Gist B-Type Thermal Dissipation Field",
|
||||
"Pith B-Type Thermic Dissipation Field": "Pith B-Type Thermal Dissipation Field",
|
||||
"Gist A-Type Thermic Dissipation Field": "Gist A-Type Thermal Dissipation Field",
|
||||
"Pith A-Type Thermic Dissipation Field": "Pith A-Type Thermal Dissipation Field",
|
||||
"Gist X-Type Thermic Dissipation Field": "Gist X-Type Thermal Dissipation Field",
|
||||
"Pith X-Type Thermic Dissipation Field": "Pith X-Type Thermal Dissipation Field",
|
||||
"'High Noon' Thermic Dissipation Amplifier": "'High Noon' Thermal Dissipation Amplifier",
|
||||
"'Desert Heat' Thermic Dissipation Field": "'Desert Heat' Thermal Dissipation Field",
|
||||
"Thermic Armor Compensation": "Thermal Armor Compensation",
|
||||
"'Moonshine' Energized Thermic Membrane I": "'Moonshine' Energized Thermal Membrane I",
|
||||
"Large Anti-Thermic Pump I": "Large Anti-Thermal Pump I",
|
||||
"Large Anti-Thermic Pump II": "Large Anti-Thermal Pump II",
|
||||
"Khanid Navy Armor Thermic Hardener": "Khanid Navy Armor Thermal Hardener",
|
||||
"Khanid Navy Energized Thermic Membrane": "Khanid Navy Energized Thermal Membrane",
|
||||
"Khanid Navy Thermic Plating": "Khanid Navy Thermal Plating",
|
||||
"Civilian Thermic Dissipation Field": "Civilian Thermal Dissipation Field",
|
||||
"Small Anti-Thermic Pump I": "Small Anti-Thermal Pump I",
|
||||
"Medium Anti-Thermic Pump I": "Medium Anti-Thermal Pump I",
|
||||
"Capital Anti-Thermic Pump I": "Capital Anti-Thermal Pump I",
|
||||
"Small Anti-Thermic Pump II": "Small Anti-Thermal Pump II",
|
||||
"Medium Anti-Thermic Pump II": "Medium Anti-Thermal Pump II",
|
||||
"Capital Anti-Thermic Pump II": "Capital Anti-Thermal Pump II",
|
||||
"Ammatar Navy Thermic Plating": "Ammatar Navy Thermal Plating",
|
||||
}
|
||||
203
service/crest.py
Normal file
203
service/crest.py
Normal file
@@ -0,0 +1,203 @@
|
||||
import thread
|
||||
import config
|
||||
import logging
|
||||
import threading
|
||||
import copy
|
||||
import uuid
|
||||
import wx
|
||||
import time
|
||||
|
||||
from wx.lib.pubsub import pub
|
||||
|
||||
import eos.db
|
||||
from eos.enum import Enum
|
||||
from eos.types import CrestChar
|
||||
import service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Servers(Enum):
|
||||
TQ = 0
|
||||
SISI = 1
|
||||
|
||||
class CrestModes(Enum):
|
||||
IMPLICIT = 0
|
||||
USER = 1
|
||||
|
||||
class Crest():
|
||||
|
||||
clientIDs = {
|
||||
Servers.TQ: 'f9be379951c046339dc13a00e6be7704',
|
||||
Servers.SISI: 'af87365240d644f7950af563b8418bad'
|
||||
}
|
||||
|
||||
# @todo: move this to settings
|
||||
clientCallback = 'http://localhost:6461'
|
||||
clientTest = True
|
||||
|
||||
_instance = None
|
||||
@classmethod
|
||||
def getInstance(cls):
|
||||
if cls._instance == None:
|
||||
cls._instance = Crest()
|
||||
|
||||
return cls._instance
|
||||
|
||||
@classmethod
|
||||
def restartService(cls):
|
||||
# This is here to reseed pycrest values when changing preferences
|
||||
# We first stop the server n case one is running, as creating a new
|
||||
# instance doesn't do this.
|
||||
if cls._instance.httpd:
|
||||
cls._instance.stopServer()
|
||||
cls._instance = Crest()
|
||||
wx.CallAfter(pub.sendMessage, 'crest_changed', message=None)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
self.settings = service.settings.CRESTSettings.getInstance()
|
||||
self.scopes = ['characterFittingsRead', 'characterFittingsWrite']
|
||||
|
||||
# these will be set when needed
|
||||
self.httpd = None
|
||||
self.state = None
|
||||
self.ssoTimer = None
|
||||
|
||||
# Base EVE connection that is copied to all characters
|
||||
self.eve = service.pycrest.EVE(
|
||||
client_id=self.settings.get('clientID') if self.settings.get('mode') == CrestModes.USER else self.clientIDs.get(self.settings.get('server')),
|
||||
api_key=self.settings.get('clientSecret') if self.settings.get('mode') == CrestModes.USER else None,
|
||||
redirect_uri=self.clientCallback,
|
||||
testing=self.isTestServer
|
||||
)
|
||||
|
||||
self.implicitCharacter = None
|
||||
|
||||
# The database cache does not seem to be working for some reason. Use
|
||||
# this as a temporary measure
|
||||
self.charCache = {}
|
||||
pub.subscribe(self.handleLogin, 'sso_login')
|
||||
|
||||
@property
|
||||
def isTestServer(self):
|
||||
return self.settings.get('server') == Servers.SISI
|
||||
|
||||
def delCrestCharacter(self, charID):
|
||||
char = eos.db.getCrestCharacter(charID)
|
||||
eos.db.remove(char)
|
||||
wx.CallAfter(pub.sendMessage, 'crest_delete', message=None)
|
||||
|
||||
def delAllCharacters(self):
|
||||
chars = eos.db.getCrestCharacters()
|
||||
for char in chars:
|
||||
eos.db.remove(char)
|
||||
self.charCache = {}
|
||||
wx.CallAfter(pub.sendMessage, 'crest_delete', message=None)
|
||||
|
||||
def getCrestCharacters(self):
|
||||
chars = eos.db.getCrestCharacters()
|
||||
return chars
|
||||
|
||||
def getCrestCharacter(self, charID):
|
||||
'''
|
||||
Get character, and modify to include the eve connection
|
||||
'''
|
||||
if self.settings.get('mode') == CrestModes.IMPLICIT:
|
||||
if self.implicitCharacter.ID != charID:
|
||||
raise ValueError("CharacterID does not match currently logged in character.")
|
||||
return self.implicitCharacter
|
||||
|
||||
if charID in self.charCache:
|
||||
return self.charCache.get(charID)
|
||||
|
||||
char = eos.db.getCrestCharacter(charID)
|
||||
if char and not hasattr(char, "eve"):
|
||||
char.eve = copy.deepcopy(self.eve)
|
||||
char.eve.temptoken_authorize(refresh_token=char.refresh_token)
|
||||
self.charCache[charID] = char
|
||||
return char
|
||||
|
||||
def getFittings(self, charID):
|
||||
char = self.getCrestCharacter(charID)
|
||||
return char.eve.get('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID)
|
||||
|
||||
def postFitting(self, charID, json):
|
||||
#@todo: new fitting ID can be recovered from Location header, ie: Location -> https://api-sisi.testeveonline.com/characters/1611853631/fittings/37486494/
|
||||
char = self.getCrestCharacter(charID)
|
||||
return char.eve.post('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID, data=json)
|
||||
|
||||
def delFitting(self, charID, fittingID):
|
||||
char = self.getCrestCharacter(charID)
|
||||
return char.eve.delete('https://api-sisi.testeveonline.com/characters/%d/fittings/%d/'%(char.ID, fittingID))
|
||||
|
||||
def logout(self):
|
||||
logging.debug("Character logout")
|
||||
self.implicitCharacter = None
|
||||
wx.CallAfter(pub.sendMessage, 'logout_success', message=None)
|
||||
|
||||
def stopServer(self):
|
||||
logging.debug("Stopping Server")
|
||||
self.httpd.stop()
|
||||
self.httpd = None
|
||||
|
||||
def startServer(self):
|
||||
logging.debug("Starting server")
|
||||
if self.httpd:
|
||||
self.stopServer()
|
||||
time.sleep(1) # we need this to ensure that the previous get_request finishes, and then the socket will close
|
||||
self.httpd = service.StoppableHTTPServer(('', 6461), service.AuthHandler)
|
||||
thread.start_new_thread(self.httpd.serve, ())
|
||||
|
||||
self.state = str(uuid.uuid4())
|
||||
return self.eve.auth_uri(scopes=self.scopes, state=self.state)
|
||||
|
||||
def handleLogin(self, message):
|
||||
if not message:
|
||||
return
|
||||
|
||||
if message['state'][0] != self.state:
|
||||
logger.warn("OAUTH state mismatch")
|
||||
return
|
||||
|
||||
logger.debug("Handling CREST login with: %s"%message)
|
||||
|
||||
if 'access_token' in message: # implicit
|
||||
eve = copy.deepcopy(self.eve)
|
||||
eve.temptoken_authorize(
|
||||
access_token=message['access_token'][0],
|
||||
expires_in=int(message['expires_in'][0])
|
||||
)
|
||||
self.ssoTimer = threading.Timer(int(message['expires_in'][0]), self.logout)
|
||||
self.ssoTimer.start()
|
||||
|
||||
eve()
|
||||
info = eve.whoami()
|
||||
|
||||
logger.debug("Got character info: %s" % info)
|
||||
|
||||
self.implicitCharacter = CrestChar(info['CharacterID'], info['CharacterName'])
|
||||
self.implicitCharacter.eve = eve
|
||||
#self.implicitCharacter.fetchImage()
|
||||
|
||||
wx.CallAfter(pub.sendMessage, 'login_success', type=CrestModes.IMPLICIT)
|
||||
elif 'code' in message:
|
||||
eve = copy.deepcopy(self.eve)
|
||||
eve.authorize(message['code'][0])
|
||||
eve()
|
||||
info = eve.whoami()
|
||||
|
||||
logger.debug("Got character info: %s" % info)
|
||||
|
||||
# check if we have character already. If so, simply replace refresh_token
|
||||
char = self.getCrestCharacter(int(info['CharacterID']))
|
||||
if char:
|
||||
char.refresh_token = eve.refresh_token
|
||||
else:
|
||||
char = CrestChar(info['CharacterID'], info['CharacterName'], eve.refresh_token)
|
||||
char.eve = eve
|
||||
self.charCache[int(info['CharacterID'])] = char
|
||||
eos.db.save(char)
|
||||
|
||||
wx.CallAfter(pub.sendMessage, 'login_success', type=CrestModes.USER)
|
||||
|
||||
self.stopServer()
|
||||
@@ -820,6 +820,10 @@ class Fit(object):
|
||||
fit = eos.db.getFit(fitID)
|
||||
return Port.exportDna(fit)
|
||||
|
||||
def exportCrest(self, fitID, callback=None):
|
||||
fit = eos.db.getFit(fitID)
|
||||
return Port.exportCrest(fit, callback)
|
||||
|
||||
def exportXml(self, callback=None, *fitIDs):
|
||||
fits = map(lambda fitID: eos.db.getFit(fitID), fitIDs)
|
||||
return Port.exportXml(callback, *fits)
|
||||
|
||||
@@ -116,12 +116,15 @@ class SearchWorkerThread(threading.Thread):
|
||||
while self.searchRequest is None:
|
||||
cv.wait()
|
||||
|
||||
request, callback = self.searchRequest
|
||||
request, callback, filterOn = self.searchRequest
|
||||
self.searchRequest = None
|
||||
cv.release()
|
||||
sMkt = Market.getInstance()
|
||||
# Rely on category data provided by eos as we don't hardcode them much in service
|
||||
filter = eos.types.Category.name.in_(sMkt.SEARCH_CATEGORIES)
|
||||
if filterOn:
|
||||
# Rely on category data provided by eos as we don't hardcode them much in service
|
||||
filter = eos.types.Category.name.in_(sMkt.SEARCH_CATEGORIES)
|
||||
else:
|
||||
filter=None
|
||||
results = eos.db.searchItems(request, where=filter,
|
||||
join=(eos.types.Item.group, eos.types.Group.category),
|
||||
eager=("icon", "group.category", "metaGroup", "metaGroup.parent"))
|
||||
@@ -133,9 +136,9 @@ class SearchWorkerThread(threading.Thread):
|
||||
items.add(item)
|
||||
wx.CallAfter(callback, items)
|
||||
|
||||
def scheduleSearch(self, text, callback):
|
||||
def scheduleSearch(self, text, callback, filterOn=True):
|
||||
self.cv.acquire()
|
||||
self.searchRequest = (text, callback)
|
||||
self.searchRequest = (text, callback, filterOn)
|
||||
self.cv.notify()
|
||||
self.cv.release()
|
||||
|
||||
@@ -665,9 +668,16 @@ class Market():
|
||||
ships.add(item)
|
||||
return ships
|
||||
|
||||
def searchItems(self, name, callback):
|
||||
def searchItems(self, name, callback, filterOn=True):
|
||||
"""Find items according to given text pattern"""
|
||||
self.searchWorkerThread.scheduleSearch(name, callback)
|
||||
self.searchWorkerThread.scheduleSearch(name, callback, filterOn)
|
||||
|
||||
def getItemsWithOverrides(self):
|
||||
overrides = eos.db.getAllOverrides()
|
||||
items = set()
|
||||
for x in overrides:
|
||||
items.add(x.item)
|
||||
return list(items)
|
||||
|
||||
def directAttrRequest(self, items, attribs):
|
||||
try:
|
||||
|
||||
102
service/port.py
102
service/port.py
@@ -25,6 +25,9 @@ from eos.types import State, Slot, Module, Cargo, Fit, Ship, Drone, Implant, Boo
|
||||
import service
|
||||
import wx
|
||||
import logging
|
||||
import config
|
||||
import collections
|
||||
import json
|
||||
|
||||
logger = logging.getLogger("pyfa.service.port")
|
||||
|
||||
@@ -34,9 +37,63 @@ except ImportError:
|
||||
from utils.compat import OrderedDict
|
||||
|
||||
EFT_SLOT_ORDER = [Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM]
|
||||
INV_FLAGS = {
|
||||
Slot.LOW: 11,
|
||||
Slot.MED: 19,
|
||||
Slot.HIGH: 27,
|
||||
Slot.RIG: 92,
|
||||
Slot.SUBSYSTEM: 125}
|
||||
|
||||
class Port(object):
|
||||
"""Service which houses all import/export format functions"""
|
||||
@classmethod
|
||||
def exportCrest(cls, ofit, callback=None):
|
||||
# A few notes:
|
||||
# max fit name length is 50 characters
|
||||
# Most keys are created simply because they are required, but bogus data is okay
|
||||
|
||||
nested_dict = lambda: collections.defaultdict(nested_dict)
|
||||
fit = nested_dict()
|
||||
sCrest = service.Crest.getInstance()
|
||||
eve = sCrest.eve
|
||||
|
||||
# max length is 50 characters
|
||||
name = ofit.name[:47] + '...' if len(ofit.name) > 50 else ofit.name
|
||||
fit['name'] = name
|
||||
fit['ship']['href'] = "%stypes/%d/"%(eve._authed_endpoint, ofit.ship.item.ID)
|
||||
fit['ship']['id'] = ofit.ship.item.ID
|
||||
fit['ship']['name'] = ''
|
||||
|
||||
fit['description'] = "<pyfa:%d />"%ofit.ID
|
||||
fit['items'] = []
|
||||
|
||||
slotNum = {}
|
||||
for module in ofit.modules:
|
||||
if module.isEmpty:
|
||||
continue
|
||||
|
||||
item = nested_dict()
|
||||
slot = module.slot
|
||||
|
||||
if slot == Slot.SUBSYSTEM:
|
||||
# Order of subsystem matters based on this attr. See GH issue #130
|
||||
slot = int(module.getModifiedItemAttr("subSystemSlot"))
|
||||
item['flag'] = slot
|
||||
else:
|
||||
if not slot in slotNum:
|
||||
slotNum[slot] = INV_FLAGS[slot]
|
||||
|
||||
item['flag'] = slotNum[slot]
|
||||
slotNum[slot] += 1
|
||||
|
||||
item['quantity'] = 1
|
||||
item['type']['href'] = "%stypes/%d/"%(eve._authed_endpoint, module.item.ID)
|
||||
item['type']['id'] = module.item.ID
|
||||
item['type']['name'] = ''
|
||||
|
||||
fit['items'].append(item)
|
||||
|
||||
return json.dumps(fit)
|
||||
|
||||
@classmethod
|
||||
def importAuto(cls, string, path=None, activeFit=None, callback=None, encoding=None):
|
||||
@@ -48,6 +105,10 @@ class Port(object):
|
||||
if re.match("<", firstLine):
|
||||
return "XML", cls.importXml(string, callback, encoding)
|
||||
|
||||
# If JSON-style start, parse os CREST/JSON
|
||||
if firstLine[0] == '{':
|
||||
return "JSON", (cls.importCrest(string),)
|
||||
|
||||
# If we've got source file name which is used to describe ship name
|
||||
# and first line contains something like [setup name], detect as eft config file
|
||||
if re.match("\[.*\]", firstLine) and path is not None:
|
||||
@@ -63,6 +124,47 @@ class Port(object):
|
||||
# Use DNA format for all other cases
|
||||
return "DNA", (cls.importDna(string),)
|
||||
|
||||
@staticmethod
|
||||
def importCrest(str):
|
||||
fit = json.loads(str)
|
||||
sMkt = service.Market.getInstance()
|
||||
|
||||
f = Fit()
|
||||
f.name = fit['name']
|
||||
|
||||
try:
|
||||
f.ship = Ship(sMkt.getItem(fit['ship']['id']))
|
||||
except:
|
||||
return None
|
||||
|
||||
items = fit['items']
|
||||
items.sort(key=lambda k: k['flag'])
|
||||
for module in items:
|
||||
try:
|
||||
item = sMkt.getItem(module['type']['id'], eager="group.category")
|
||||
if item.category.name == "Drone":
|
||||
d = Drone(item)
|
||||
d.amount = module['quantity']
|
||||
f.drones.append(d)
|
||||
elif item.category.name == "Charge":
|
||||
c = Cargo(item)
|
||||
c.amount = module['quantity']
|
||||
f.cargo.append(c)
|
||||
else:
|
||||
try:
|
||||
m = Module(item)
|
||||
# When item can't be added to any slot (unknown item or just charge), ignore it
|
||||
except ValueError:
|
||||
continue
|
||||
if m.isValidState(State.ACTIVE):
|
||||
m.state = State.ACTIVE
|
||||
|
||||
f.modules.append(m)
|
||||
except:
|
||||
continue
|
||||
|
||||
return f
|
||||
|
||||
@staticmethod
|
||||
def importDna(string):
|
||||
sMkt = service.Market.getInstance()
|
||||
|
||||
13
service/pycrest/__init__.py
Normal file
13
service/pycrest/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import logging
|
||||
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
logger = logging.getLogger('pycrest')
|
||||
logger.addHandler(NullHandler())
|
||||
|
||||
version = "0.0.1"
|
||||
|
||||
from .eve import EVE
|
||||
24
service/pycrest/compat.py
Normal file
24
service/pycrest/compat.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import sys
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3: # pragma: no cover
|
||||
string_types = str,
|
||||
text_type = str
|
||||
binary_type = bytes
|
||||
else: # pragma: no cover
|
||||
string_types = basestring,
|
||||
text_type = unicode
|
||||
binary_type = str
|
||||
|
||||
|
||||
def text_(s, encoding='latin-1', errors='strict'): # pragma: no cover
|
||||
if isinstance(s, binary_type):
|
||||
return s.decode(encoding, errors)
|
||||
return s
|
||||
|
||||
|
||||
def bytes_(s, encoding='latin-1', errors='strict'): # pragma: no cover
|
||||
if isinstance(s, text_type):
|
||||
return s.encode(encoding, errors)
|
||||
return s
|
||||
2
service/pycrest/errors.py
Normal file
2
service/pycrest/errors.py
Normal file
@@ -0,0 +1,2 @@
|
||||
class APIException(Exception):
|
||||
pass
|
||||
322
service/pycrest/eve.py
Normal file
322
service/pycrest/eve.py
Normal file
@@ -0,0 +1,322 @@
|
||||
import os
|
||||
import base64
|
||||
import time
|
||||
import zlib
|
||||
|
||||
import requests
|
||||
|
||||
from . import version
|
||||
from compat import bytes_, text_
|
||||
from errors import APIException
|
||||
from weak_ciphers import WeakCiphersAdapter
|
||||
|
||||
try:
|
||||
from urllib.parse import urlparse, urlunparse, parse_qsl
|
||||
except ImportError: # pragma: no cover
|
||||
from urlparse import urlparse, urlunparse, parse_qsl
|
||||
|
||||
try:
|
||||
import pickle
|
||||
except ImportError: # pragma: no cover
|
||||
import cPickle as pickle
|
||||
|
||||
try:
|
||||
from urllib.parse import quote
|
||||
except ImportError: # pragma: no cover
|
||||
from urllib import quote
|
||||
import logging
|
||||
import re
|
||||
|
||||
logger = logging.getLogger("pycrest.eve")
|
||||
cache_re = re.compile(r'max-age=([0-9]+)')
|
||||
|
||||
|
||||
class APICache(object):
|
||||
def put(self, key, value):
|
||||
raise NotImplementedError
|
||||
|
||||
def get(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
def invalidate(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class FileCache(APICache):
|
||||
def __init__(self, path):
|
||||
self._cache = {}
|
||||
self.path = path
|
||||
if not os.path.isdir(self.path):
|
||||
os.mkdir(self.path, 0o700)
|
||||
|
||||
def _getpath(self, key):
|
||||
return os.path.join(self.path, str(hash(key)) + '.cache')
|
||||
|
||||
def put(self, key, value):
|
||||
with open(self._getpath(key), 'wb') as f:
|
||||
f.write(zlib.compress(pickle.dumps(value, -1)))
|
||||
self._cache[key] = value
|
||||
|
||||
def get(self, key):
|
||||
if key in self._cache:
|
||||
return self._cache[key]
|
||||
|
||||
try:
|
||||
with open(self._getpath(key), 'rb') as f:
|
||||
return pickle.loads(zlib.decompress(f.read()))
|
||||
except IOError as ex:
|
||||
if ex.errno == 2: # file does not exist (yet)
|
||||
return None
|
||||
else:
|
||||
raise
|
||||
|
||||
def invalidate(self, key):
|
||||
self._cache.pop(key, None)
|
||||
|
||||
try:
|
||||
os.unlink(self._getpath(key))
|
||||
except OSError as ex:
|
||||
if ex.errno == 2: # does not exist
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
class DictCache(APICache):
|
||||
def __init__(self):
|
||||
self._dict = {}
|
||||
|
||||
def get(self, key):
|
||||
return self._dict.get(key, None)
|
||||
|
||||
def put(self, key, value):
|
||||
self._dict[key] = value
|
||||
|
||||
def invalidate(self, key):
|
||||
self._dict.pop(key, None)
|
||||
|
||||
|
||||
class APIConnection(object):
|
||||
def __init__(self, additional_headers=None, user_agent=None, cache_dir=None, cache=None):
|
||||
# Set up a Requests Session
|
||||
session = requests.Session()
|
||||
if additional_headers is None:
|
||||
additional_headers = {}
|
||||
if user_agent is None:
|
||||
user_agent = "PyCrest/{0}".format(version)
|
||||
session.headers.update({
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "application/json",
|
||||
})
|
||||
session.headers.update(additional_headers)
|
||||
session.mount('https://public-crest.eveonline.com',
|
||||
WeakCiphersAdapter())
|
||||
self._session = session
|
||||
if cache:
|
||||
if isinstance(cache, APICache):
|
||||
self.cache = cache # Inherit from parents
|
||||
elif isinstance(cache, type):
|
||||
self.cache = cache() # Instantiate a new cache
|
||||
elif cache_dir:
|
||||
self.cache_dir = cache_dir
|
||||
self.cache = FileCache(self.cache_dir)
|
||||
else:
|
||||
self.cache = DictCache()
|
||||
|
||||
def get(self, resource, params=None):
|
||||
logger.debug('Getting resource %s', resource)
|
||||
if params is None:
|
||||
params = {}
|
||||
|
||||
# remove params from resource URI (needed for paginated stuff)
|
||||
parsed_uri = urlparse(resource)
|
||||
qs = parsed_uri.query
|
||||
resource = urlunparse(parsed_uri._replace(query=''))
|
||||
prms = {}
|
||||
for tup in parse_qsl(qs):
|
||||
prms[tup[0]] = tup[1]
|
||||
|
||||
# params supplied to self.get() override parsed params
|
||||
for key in params:
|
||||
prms[key] = params[key]
|
||||
|
||||
# check cache
|
||||
key = (resource, frozenset(self._session.headers.items()), frozenset(prms.items()))
|
||||
cached = self.cache.get(key)
|
||||
if cached and cached['cached_until'] > time.time():
|
||||
logger.debug('Cache hit for resource %s (params=%s)', resource, prms)
|
||||
return cached
|
||||
elif cached:
|
||||
logger.debug('Cache stale for resource %s (params=%s)', resource, prms)
|
||||
self.cache.invalidate(key)
|
||||
else:
|
||||
logger.debug('Cache miss for resource %s (params=%s', resource, prms)
|
||||
|
||||
logger.debug('Getting resource %s (params=%s)', resource, prms)
|
||||
res = self._session.get(resource, params=prms)
|
||||
if res.status_code != 200:
|
||||
raise APIException("Got unexpected status code from server: %i" % res.status_code)
|
||||
|
||||
ret = res.json()
|
||||
|
||||
# cache result
|
||||
expires = self._get_expires(res)
|
||||
if expires > 0:
|
||||
ret.update({'cached_until': time.time() + expires})
|
||||
self.cache.put(key, ret)
|
||||
|
||||
return ret
|
||||
|
||||
def _get_expires(self, response):
|
||||
if 'Cache-Control' not in response.headers:
|
||||
return 0
|
||||
if any([s in response.headers['Cache-Control'] for s in ['no-cache', 'no-store']]):
|
||||
return 0
|
||||
match = cache_re.search(response.headers['Cache-Control'])
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
return 0
|
||||
|
||||
|
||||
class EVE(APIConnection):
|
||||
def __init__(self, **kwargs):
|
||||
self.api_key = kwargs.pop('api_key', None)
|
||||
self.client_id = kwargs.pop('client_id', None)
|
||||
self.redirect_uri = kwargs.pop('redirect_uri', None)
|
||||
if kwargs.pop('testing', False):
|
||||
self._public_endpoint = "http://public-crest-sisi.testeveonline.com/"
|
||||
self._authed_endpoint = "https://api-sisi.testeveonline.com/"
|
||||
self._image_server = "https://image.testeveonline.com/"
|
||||
self._oauth_endpoint = "https://sisilogin.testeveonline.com/oauth"
|
||||
else:
|
||||
self._public_endpoint = "https://public-crest.eveonline.com/"
|
||||
self._authed_endpoint = "https://crest-tq.eveonline.com/"
|
||||
self._image_server = "https://image.eveonline.com/"
|
||||
self._oauth_endpoint = "https://login.eveonline.com/oauth"
|
||||
self._endpoint = self._public_endpoint
|
||||
self._cache = {}
|
||||
self._data = None
|
||||
self.token = None
|
||||
self.refresh_token = None
|
||||
self.expires = None
|
||||
APIConnection.__init__(self, **kwargs)
|
||||
|
||||
def __call__(self):
|
||||
if not self._data:
|
||||
self._data = APIObject(self.get(self._endpoint), self)
|
||||
return self._data
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._data.__getattr__(item)
|
||||
|
||||
def auth_uri(self, scopes=None, state=None):
|
||||
s = [] if not scopes else scopes
|
||||
grant_type = "token" if self.api_key is None else "code"
|
||||
|
||||
return "%s/authorize?response_type=%s&redirect_uri=%s&client_id=%s%s%s" % (
|
||||
self._oauth_endpoint,
|
||||
grant_type,
|
||||
self.redirect_uri,
|
||||
self.client_id,
|
||||
"&scope=%s" % '+'.join(s) if scopes else '',
|
||||
"&state=%s" % state if state else ''
|
||||
)
|
||||
|
||||
def _authorize(self, params):
|
||||
auth = text_(base64.b64encode(bytes_("%s:%s" % (self.client_id, self.api_key))))
|
||||
headers = {"Authorization": "Basic %s" % auth}
|
||||
res = self._session.post("%s/token" % self._oauth_endpoint, params=params, headers=headers)
|
||||
if res.status_code != 200:
|
||||
raise APIException("Got unexpected status code from API: %i" % res.status_code)
|
||||
return res.json()
|
||||
|
||||
def set_auth_values(self, res):
|
||||
self.__class__ = AuthedConnection
|
||||
self.token = res['access_token']
|
||||
self.refresh_token = res['refresh_token']
|
||||
self.expires = int(time.time()) + res['expires_in']
|
||||
self._endpoint = self._authed_endpoint
|
||||
self._session.headers.update({"Authorization": "Bearer %s" % self.token})
|
||||
|
||||
def authorize(self, code):
|
||||
res = self._authorize(params={"grant_type": "authorization_code", "code": code})
|
||||
self.set_auth_values(res)
|
||||
|
||||
def refr_authorize(self, refresh_token):
|
||||
res = self._authorize(params={"grant_type": "refresh_token", "refresh_token": refresh_token})
|
||||
self.set_auth_values(res)
|
||||
|
||||
def temptoken_authorize(self, access_token=None, expires_in=0, refresh_token=None):
|
||||
self.set_auth_values({'access_token': access_token,
|
||||
'refresh_token': refresh_token,
|
||||
'expires_in': expires_in})
|
||||
|
||||
|
||||
class AuthedConnection(EVE):
|
||||
|
||||
def __call__(self):
|
||||
if not self._data:
|
||||
self._data = APIObject(self.get(self._endpoint), self)
|
||||
return self._data
|
||||
|
||||
def whoami(self):
|
||||
#if 'whoami' not in self._cache:
|
||||
# print "Setting this whoami cache"
|
||||
# self._cache['whoami'] = self.get("%s/verify" % self._oauth_endpoint)
|
||||
return self.get("%s/verify" % self._oauth_endpoint)
|
||||
|
||||
def get(self, resource, params=None):
|
||||
if self.refresh_token and int(time.time()) >= self.expires:
|
||||
self.refr_authorize(self.refresh_token)
|
||||
return super(self.__class__, self).get(resource, params)
|
||||
|
||||
def post(self, resource, data, params=None):
|
||||
if self.refresh_token and int(time.time()) >= self.expires:
|
||||
self.refr_authorize(self.refresh_token)
|
||||
return self._session.post(resource, data=data, params=params)
|
||||
|
||||
def delete(self, resource, params=None):
|
||||
if self.refresh_token and int(time.time()) >= self.expires:
|
||||
self.refr_authorize(self.refresh_token)
|
||||
return self._session.delete(resource, params=params)
|
||||
|
||||
class APIObject(object):
|
||||
def __init__(self, parent, connection):
|
||||
self._dict = {}
|
||||
self.connection = connection
|
||||
for k, v in parent.items():
|
||||
if type(v) is dict:
|
||||
self._dict[k] = APIObject(v, connection)
|
||||
elif type(v) is list:
|
||||
self._dict[k] = self._wrap_list(v)
|
||||
else:
|
||||
self._dict[k] = v
|
||||
|
||||
def _wrap_list(self, list_):
|
||||
new = []
|
||||
for item in list_:
|
||||
if type(item) is dict:
|
||||
new.append(APIObject(item, self.connection))
|
||||
elif type(item) is list:
|
||||
new.append(self._wrap_list(item))
|
||||
else:
|
||||
new.append(item)
|
||||
return new
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item in self._dict:
|
||||
return self._dict[item]
|
||||
raise AttributeError(item)
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
# Caching is now handled by APIConnection
|
||||
if 'href' in self._dict:
|
||||
return APIObject(self.connection.get(self._dict['href'], params=kwargs), self.connection)
|
||||
else:
|
||||
return self
|
||||
|
||||
def __str__(self): # pragma: no cover
|
||||
return self._dict.__str__()
|
||||
|
||||
def __repr__(self): # pragma: no cover
|
||||
return self._dict.__repr__()
|
||||
140
service/pycrest/weak_ciphers.py
Normal file
140
service/pycrest/weak_ciphers.py
Normal file
@@ -0,0 +1,140 @@
|
||||
import datetime
|
||||
import ssl
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from requests.adapters import HTTPAdapter
|
||||
|
||||
try:
|
||||
from requests.packages import urllib3
|
||||
from requests.packages.urllib3.util import ssl_
|
||||
|
||||
from requests.packages.urllib3.exceptions import (
|
||||
SystemTimeWarning,
|
||||
SecurityWarning,
|
||||
)
|
||||
from requests.packages.urllib3.packages.ssl_match_hostname import \
|
||||
match_hostname
|
||||
except:
|
||||
import urllib3
|
||||
from urllib3.util import ssl_
|
||||
|
||||
from urllib3.exceptions import (
|
||||
SystemTimeWarning,
|
||||
SecurityWarning,
|
||||
)
|
||||
from urllib3.packages.ssl_match_hostname import \
|
||||
match_hostname
|
||||
|
||||
|
||||
|
||||
|
||||
class WeakCiphersHTTPSConnection(
|
||||
urllib3.connection.VerifiedHTTPSConnection): # pragma: no cover
|
||||
|
||||
# Python versions >=2.7.9 and >=3.4.1 do not (by default) allow ciphers
|
||||
# with MD5. Unfortunately, the CREST public server _only_ supports
|
||||
# TLS_RSA_WITH_RC4_128_MD5 (as of 5 Jan 2015). The cipher list below is
|
||||
# nearly identical except for allowing that cipher as a last resort (and
|
||||
# excluding export versions of ciphers).
|
||||
DEFAULT_CIPHERS = (
|
||||
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:'
|
||||
'ECDH+HIGH:DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:'
|
||||
'RSA+3DES:ECDH+RC4:DH+RC4:RSA+RC4:!aNULL:!eNULL:!EXP:-MD5:RSA+RC4+MD5'
|
||||
)
|
||||
|
||||
def __init__(self, host, port, ciphers=None, **kwargs):
|
||||
self.ciphers = ciphers if ciphers is not None else self.DEFAULT_CIPHERS
|
||||
super(WeakCiphersHTTPSConnection, self).__init__(host, port, **kwargs)
|
||||
|
||||
def connect(self):
|
||||
# Yup, copied in VerifiedHTTPSConnection.connect just to change the
|
||||
# default cipher list.
|
||||
|
||||
# Add certificate verification
|
||||
conn = self._new_conn()
|
||||
|
||||
resolved_cert_reqs = ssl_.resolve_cert_reqs(self.cert_reqs)
|
||||
resolved_ssl_version = ssl_.resolve_ssl_version(self.ssl_version)
|
||||
|
||||
hostname = self.host
|
||||
if getattr(self, '_tunnel_host', None):
|
||||
# _tunnel_host was added in Python 2.6.3
|
||||
# (See: http://hg.python.org/cpython/rev/0f57b30a152f)
|
||||
|
||||
self.sock = conn
|
||||
# Calls self._set_hostport(), so self.host is
|
||||
# self._tunnel_host below.
|
||||
self._tunnel()
|
||||
# Mark this connection as not reusable
|
||||
self.auto_open = 0
|
||||
|
||||
# Override the host with the one we're requesting data from.
|
||||
hostname = self._tunnel_host
|
||||
|
||||
is_time_off = datetime.date.today() < urllib3.connection.RECENT_DATE
|
||||
if is_time_off:
|
||||
warnings.warn((
|
||||
'System time is way off (before {0}). This will probably '
|
||||
'lead to SSL verification errors').format(
|
||||
urllib3.connection.RECENT_DATE),
|
||||
SystemTimeWarning
|
||||
)
|
||||
|
||||
# Wrap socket using verification with the root certs in
|
||||
# trusted_root_certs
|
||||
self.sock = ssl_.ssl_wrap_socket(conn, self.key_file, self.cert_file,
|
||||
cert_reqs=resolved_cert_reqs,
|
||||
ca_certs=self.ca_certs,
|
||||
server_hostname=hostname,
|
||||
ssl_version=resolved_ssl_version,
|
||||
ciphers=self.ciphers)
|
||||
|
||||
if self.assert_fingerprint:
|
||||
ssl_.assert_fingerprint(self.sock.getpeercert(binary_form=True),
|
||||
self.assert_fingerprint)
|
||||
elif resolved_cert_reqs != ssl.CERT_NONE \
|
||||
and self.assert_hostname is not False:
|
||||
cert = self.sock.getpeercert()
|
||||
if not cert.get('subjectAltName', ()):
|
||||
warnings.warn((
|
||||
'Certificate has no `subjectAltName`, falling back to check for a `commonName` for now. '
|
||||
'This feature is being removed by major browsers and deprecated by RFC 2818. '
|
||||
'(See https://github.com/shazow/urllib3/issues/497 for details.)'),
|
||||
SecurityWarning
|
||||
)
|
||||
match_hostname(cert, self.assert_hostname or hostname)
|
||||
|
||||
self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED
|
||||
or self.assert_fingerprint is not None)
|
||||
|
||||
|
||||
class WeakCiphersHTTPSConnectionPool(
|
||||
urllib3.connectionpool.HTTPSConnectionPool):
|
||||
|
||||
ConnectionCls = WeakCiphersHTTPSConnection
|
||||
|
||||
|
||||
class WeakCiphersPoolManager(urllib3.poolmanager.PoolManager):
|
||||
|
||||
def _new_pool(self, scheme, host, port):
|
||||
if scheme == 'https':
|
||||
return WeakCiphersHTTPSConnectionPool(host, port,
|
||||
**(self.connection_pool_kw))
|
||||
return super(WeakCiphersPoolManager, self)._new_pool(scheme, host,
|
||||
port)
|
||||
|
||||
|
||||
class WeakCiphersAdapter(HTTPAdapter):
|
||||
""""Transport adapter" that allows us to use TLS_RSA_WITH_RC4_128_MD5."""
|
||||
|
||||
def init_poolmanager(self, connections, maxsize, block=False,
|
||||
**pool_kwargs):
|
||||
# Rewrite of the requests.adapters.HTTPAdapter.init_poolmanager method
|
||||
# to use WeakCiphersPoolManager instead of urllib3's PoolManager
|
||||
self._pool_connections = connections
|
||||
self._pool_maxsize = maxsize
|
||||
self._pool_block = block
|
||||
|
||||
self.poolmanager = WeakCiphersPoolManager(num_pools=connections,
|
||||
maxsize=maxsize, block=block, strict=True, **pool_kwargs)
|
||||
101
service/server.py
Normal file
101
service/server.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import BaseHTTPServer
|
||||
import urlparse
|
||||
import socket
|
||||
import thread
|
||||
import wx
|
||||
|
||||
from wx.lib.pubsub import setupkwargs
|
||||
from wx.lib.pubsub import pub
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
HTML = '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
Done. Please close this window.
|
||||
<script type="text/javascript">
|
||||
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);
|
||||
|
||||
if (token){
|
||||
var redirect = window.location.origin.concat('/?', window.location.hash.substr(1));
|
||||
window.location = redirect;
|
||||
}
|
||||
else {
|
||||
console.log("do nothing");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
# https://github.com/fuzzysteve/CREST-Market-Downloader/
|
||||
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)
|
||||
|
||||
wx.CallAfter(pub.sendMessage, 'sso_login', message=parts)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
return
|
||||
|
||||
# http://code.activestate.com/recipes/425210-simple-stoppable-server-using-socket-timeout/
|
||||
class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
|
||||
|
||||
def server_bind(self):
|
||||
BaseHTTPServer.HTTPServer.server_bind(self)
|
||||
# Allow listening for 60 seconds
|
||||
sec = 60
|
||||
|
||||
self.socket.settimeout(0.5)
|
||||
self.max_tries = sec / self.socket.gettimeout()
|
||||
self.tries = 0
|
||||
self.run = True
|
||||
|
||||
def get_request(self):
|
||||
while self.run:
|
||||
try:
|
||||
sock, addr = self.socket.accept()
|
||||
sock.settimeout(None)
|
||||
return (sock, addr)
|
||||
except socket.timeout:
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.run = False
|
||||
self.server_close()
|
||||
|
||||
def handle_timeout(self):
|
||||
logger.debug("Number of tries: %d"%self.tries)
|
||||
self.tries += 1
|
||||
if self.tries == self.max_tries:
|
||||
logger.debug("Server timed out waiting for connection")
|
||||
self.stop()
|
||||
|
||||
def serve(self):
|
||||
while self.run:
|
||||
try:
|
||||
self.handle_request()
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
httpd = StoppableHTTPServer(('', 6461), AuthHandler)
|
||||
thread.start_new_thread(httpd.serve, ())
|
||||
raw_input("Press <RETURN> to stop server\n")
|
||||
httpd.stop()
|
||||
|
||||
@@ -263,4 +263,30 @@ class UpdateSettings():
|
||||
def set(self, type, value):
|
||||
self.serviceUpdateSettings[type] = value
|
||||
|
||||
class CRESTSettings():
|
||||
_instance = None
|
||||
|
||||
@classmethod
|
||||
def getInstance(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = CRESTSettings()
|
||||
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# mode
|
||||
# 0 - Implicit authentication
|
||||
# 1 - User-supplied client details
|
||||
serviceCRESTDefaultSettings = {"mode": 0, "server": 0, "clientID": "", "clientSecret": ""}
|
||||
|
||||
self.serviceCRESTSettings = SettingsProvider.getInstance().getSettings("pyfaServiceCRESTSettings", serviceCRESTDefaultSettings)
|
||||
|
||||
def get(self, type):
|
||||
return self.serviceCRESTSettings[type]
|
||||
|
||||
def set(self, type, value):
|
||||
self.serviceCRESTSettings[type] = value
|
||||
|
||||
|
||||
# @todo: migrate fit settings (from fit service) here?
|
||||
|
||||
3
setup.py
3
setup.py
@@ -4,11 +4,12 @@ Distribution builder for pyfa.
|
||||
Windows executable: python setup.py build
|
||||
Windows executable + installer: python setup.py bdist_msi
|
||||
"""
|
||||
import requests.certs
|
||||
|
||||
# The modules that contain the bulk of teh source
|
||||
packages = ['eos', 'gui', 'service', 'utils']
|
||||
# Extra files that will be copied into the root directory
|
||||
include_files = ['eve.db', 'LICENSE', 'README.md']
|
||||
include_files = ['eve.db', 'LICENSE', 'README.md', (requests.certs.where(),'cacert.pem')]
|
||||
# this is read by dist.py to package the icons
|
||||
icon_dirs = ['gui', 'icons', 'renders']
|
||||
|
||||
|
||||
Reference in New Issue
Block a user