Merge branch 'master' into charImplants
Conflicts: gui/characterEditor.py
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
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",
|
||||
}
|
||||
219
service/crest.py
Normal file
219
service/crest.py
Normal 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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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 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__()
|
||||
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)
|
||||
99
service/server.py
Normal file
99
service/server.py
Normal 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()
|
||||
|
||||
@@ -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?
|
||||
|
||||
Reference in New Issue
Block a user