Merge branch 'master' into charImplants

Conflicts:
	gui/characterEditor.py
This commit is contained in:
blitzmann
2015-11-08 21:19:04 -05:00
1885 changed files with 3742 additions and 928 deletions

View File

@@ -10,3 +10,9 @@ from service.update import Update
from service.price import Price
from service.network import Network
from service.eveapi import EVEAPIConnection, ParseXML
import wx
if not 'wxMac' in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3,0)):
from service.pycrest import EVE
from service.server import StoppableHTTPServer, AuthHandler
from service.crest import Crest

View File

@@ -7,7 +7,7 @@ item's name. The name of the file is usually arbitrary unless it's used in logic
elsewhere (in which case can be accessed with packs[name])
"""
import os
import pkgutil
# init parent dict
all = {}
@@ -15,10 +15,10 @@ all = {}
# init container to store the separate conversion packs in case we need them
packs = {}
for filename in os.listdir(os.path.dirname(__file__)):
basename, extension = filename.rsplit('.', 1)
if extension == "py" and basename not in ("__init__",):
conversionPack = __import__("%s.%s"%(__name__, basename), fromlist=True)
all.update(conversionPack.CONVERSIONS)
packs[basename] = conversionPack.CONVERSIONS
prefix = __name__ + "."
for importer, modname, ispkg in pkgutil.iter_modules(__path__, prefix):
conversionPack = __import__(modname, fromlist="dummy")
all.update(conversionPack.CONVERSIONS)
modname_tail = modname.rsplit('.', 1)[-1]
packs[modname_tail] = conversionPack.CONVERSIONS

View 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",
}

219
service/crest.py Normal file
View File

@@ -0,0 +1,219 @@
import wx
import thread
import logging
import threading
import copy
import uuid
import time
import eos.db
from eos.enum import Enum
from eos.types import CrestChar
import service
import gui.globalEvents as GE
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()
cls._instance.mainFrame.updateCrestMenus(type=cls._instance.settings.get('mode'))
return cls._instance
def __init__(self):
"""
A note on login/logout events: the character login events happen
whenever a characters is logged into via the SSO, regardless of mod.
However, the mode should be send as an argument. Similarily,
the Logout even happens whenever the character is deleted for either
mode. The mode is sent as an argument, as well as the umber of
characters still in the cache (if USER mode)
"""
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 = {}
# need these here to post events
import gui.mainFrame # put this here to avoid loop
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
@property
def isTestServer(self):
return self.settings.get('server') == Servers.SISI
def delCrestCharacter(self, charID):
char = eos.db.getCrestCharacter(charID)
del self.charCache[char.ID]
eos.db.remove(char)
wx.PostEvent(self.mainFrame, GE.SsoLogout(type=CrestModes.USER, numChars=len(self.charCache)))
def delAllCharacters(self):
chars = eos.db.getCrestCharacters()
for char in chars:
eos.db.remove(char)
self.charCache = {}
wx.PostEvent(self.mainFrame, GE.SsoLogout(type=CrestModes.USER, numChars=0))
def getCrestCharacters(self):
chars = eos.db.getCrestCharacters()
# I really need to figure out that DB cache problem, this is ridiculous
chars2 = [self.getCrestCharacter(char.ID) for char in chars]
return chars2
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('%scharacters/%d/fittings/'%(char.eve._authed_endpoint,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('%scharacters/%d/fittings/'%(char.eve._authed_endpoint,char.ID), data=json)
def delFitting(self, charID, fittingID):
char = self.getCrestCharacter(charID)
return char.eve.delete('%scharacters/%d/fittings/%d/'%(char.eve._authed_endpoint, char.ID, fittingID))
def logout(self):
"""Logout of implicit character"""
logging.debug("Character logout")
self.implicitCharacter = None
wx.PostEvent(self.mainFrame, GE.SsoLogout(type=self.settings.get('mode')))
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.handleLogin,))
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.PostEvent(self.mainFrame, GE.SsoLogin(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.PostEvent(self.mainFrame, GE.SsoLogin(type=CrestModes.USER))
self.stopServer()

View File

@@ -181,7 +181,8 @@ class Fit(object):
# refresh any fits this fit is projected onto. Otherwise, if we have
# already loaded those fits, they will not reflect the changes
for projection in fit.projectedOnto.values():
eos.db.saveddata_session.refresh(projection.victim_fit)
if projection.victim_fit in eos.db.saveddata_session: # GH issue #359
eos.db.saveddata_session.refresh(projection.victim_fit)
def copyFit(self, fitID):
fit = eos.db.getFit(fitID)
@@ -223,14 +224,19 @@ class Fit(object):
eos.db.commit()
self.recalc(fit, withBoosters=True)
def getFit(self, fitID, projected = False):
def getFit(self, fitID, projected=False, basic=False):
''' Gets fit from database, and populates fleet data.
Projected is a recursion flag that is set to reduce recursions into projected fits
Basic is a flag to simply return the fit without any other processing
'''
if fitID is None:
return None
fit = eos.db.getFit(fitID)
if basic:
return fit
inited = getattr(fit, "inited", None)
if inited is None or inited is False:
@@ -814,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)

View File

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

View File

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

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

View File

@@ -0,0 +1,2 @@
class APIException(Exception):
pass

322
service/pycrest/eve.py Normal file
View 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 requests.adapters import HTTPAdapter
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',
HTTPAdapter())
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__()

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

99
service/server.py Normal file
View File

@@ -0,0 +1,99 @@
import BaseHTTPServer
import urlparse
import socket
import thread
import wx
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(self.server.callback, 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
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, callback):
self.callback = callback
while self.run:
try:
self.handle_request()
except TypeError:
pass
self.server_close()
if __name__ == "__main__":
httpd = StoppableHTTPServer(('', 6461), AuthHandler)
thread.start_new_thread(httpd.serve, ())
raw_input("Press <RETURN> to stop server\n")
httpd.stop()

View File

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