From 11c3859270a76ca748038ec9fb72707775ff0413 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 16 Oct 2015 15:39:40 -0400 Subject: [PATCH 01/41] Added small server script to be used as CREST callback. This can listen for and print the authorization code. --- server.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 server.py diff --git a/server.py b/server.py new file mode 100644 index 000000000..dc5b9ac63 --- /dev/null +++ b/server.py @@ -0,0 +1,24 @@ +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +import urlparse + +class RequestHandler(BaseHTTPRequestHandler): + + def __init__(self, *args, **kwargs): + BaseHTTPRequestHandler.__init__(self, *args, **kwargs) + + def do_GET(self): + bits = urlparse.urlparse(self.path) + print urlparse.parse_qs(bits.query) + + +def main(): + try: + server = HTTPServer(('', 6461), RequestHandler) + print('Test server running...') + server.serve_forever() + except KeyboardInterrupt: + print "Closing socket" + server.socket.close() + +if __name__ == '__main__': + main() From 94995685e9061c620841962d0c91c40ed3d404fd Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 17 Oct 2015 21:41:24 -0400 Subject: [PATCH 02/41] Added pycrest with a few fixes. --- pycrest/__init__.py | 13 ++ pycrest/compat.py | 24 +++ pycrest/errors.py | 2 + pycrest/eve.py | 331 ++++++++++++++++++++++++++++++++++++++++ pycrest/weak_ciphers.py | 140 +++++++++++++++++ 5 files changed, 510 insertions(+) create mode 100644 pycrest/__init__.py create mode 100644 pycrest/compat.py create mode 100644 pycrest/errors.py create mode 100644 pycrest/eve.py create mode 100644 pycrest/weak_ciphers.py diff --git a/pycrest/__init__.py b/pycrest/__init__.py new file mode 100644 index 000000000..97244c957 --- /dev/null +++ b/pycrest/__init__.py @@ -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 \ No newline at end of file diff --git a/pycrest/compat.py b/pycrest/compat.py new file mode 100644 index 000000000..06320069b --- /dev/null +++ b/pycrest/compat.py @@ -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 \ No newline at end of file diff --git a/pycrest/errors.py b/pycrest/errors.py new file mode 100644 index 000000000..33b9ca9ae --- /dev/null +++ b/pycrest/errors.py @@ -0,0 +1,2 @@ +class APIException(Exception): + pass \ No newline at end of file diff --git a/pycrest/eve.py b/pycrest/eve.py new file mode 100644 index 000000000..bc7495c71 --- /dev/null +++ b/pycrest/eve.py @@ -0,0 +1,331 @@ +import os +import base64 +import requests +import time +import zlib +from pycrest import version +from pycrest.compat import bytes_, text_ +from pycrest.errors import APIException +from pycrest.weak_ciphers import WeakCiphersAdapter + +try: + from urllib.parse import urlparse, urlunparse, parse_qsl +except ImportError: # pragma: no cover + from urlparse import urlparse, urlunparse, parse_qsl + +try: + import pickle +except ImportError: # pragma: no cover + import cPickle as pickle + +try: + from urllib.parse import quote +except ImportError: # pragma: no cover + from urllib import quote +import logging +import re + +logger = logging.getLogger("pycrest.eve") +cache_re = re.compile(r'max-age=([0-9]+)') + + +class APICache(object): + def put(self, key, value): + raise NotImplementedError + + def get(self, key): + raise NotImplementedError + + def invalidate(self, key): + raise NotImplementedError + + +class FileCache(APICache): + def __init__(self, path): + self._cache = {} + self.path = path + if not os.path.isdir(self.path): + os.mkdir(self.path, 0o700) + + def _getpath(self, key): + return os.path.join(self.path, str(hash(key)) + '.cache') + + def put(self, key, value): + with open(self._getpath(key), 'wb') as f: + f.write(zlib.compress(pickle.dumps(value, -1))) + self._cache[key] = value + + def get(self, key): + if key in self._cache: + return self._cache[key] + + try: + with open(self._getpath(key), 'rb') as f: + return pickle.loads(zlib.decompress(f.read())) + except IOError as ex: + if ex.errno == 2: # file does not exist (yet) + return None + else: + raise + + def invalidate(self, key): + self._cache.pop(key, None) + + try: + os.unlink(self._getpath(key)) + except OSError as ex: + if ex.errno == 2: # does not exist + pass + else: + raise + + +class DictCache(APICache): + def __init__(self): + self._dict = {} + + def get(self, key): + return self._dict.get(key, None) + + def put(self, key, value): + self._dict[key] = value + + def invalidate(self, key): + self._dict.pop(key, None) + + +class APIConnection(object): + def __init__(self, additional_headers=None, user_agent=None, cache_dir=None, cache=None): + # Set up a Requests Session + session = requests.Session() + if additional_headers is None: + additional_headers = {} + if user_agent is None: + user_agent = "PyCrest/{0}".format(version) + session.headers.update({ + "User-Agent": user_agent, + "Accept": "application/json", + }) + session.headers.update(additional_headers) + session.mount('https://public-crest.eveonline.com', + WeakCiphersAdapter()) + self._session = session + if cache: + if isinstance(cache, APICache): + self.cache = cache # Inherit from parents + elif isinstance(cache, type): + self.cache = cache() # Instantiate a new cache + elif cache_dir: + self.cache_dir = cache_dir + self.cache = FileCache(self.cache_dir) + else: + self.cache = DictCache() + + def get(self, resource, params=None): + print resource, params + 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['expires'] > time.time(): + logger.debug('Cache hit for resource %s (params=%s)', resource, prms) + return cached['payload'] + 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 + key = (resource, frozenset(self._session.headers.items()), frozenset(prms.items())) + expires = self._get_expires(res) + if expires > 0: + self.cache.put(key, {'expires': time.time() + expires, 'payload': 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 + 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 + return "%s/authorize?response_type=code&redirect_uri=%s&client_id=%s%s%s" % ( + self._oauth_endpoint, + quote(self.redirect_uri, safe=''), + 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 authorize(self, code): + res = self._authorize(params={"grant_type": "authorization_code", "code": code}) + return AuthedConnection(res, + self._authed_endpoint, + self._oauth_endpoint, + self.client_id, + self.api_key, + cache=self.cache) + + def refr_authorize(self, refresh_token): + res = self._authorize(params={"grant_type": "refresh_token", "refresh_token": refresh_token}) + return AuthedConnection({'access_token': res['access_token'], + 'refresh_token': refresh_token, + 'expires_in': res['expires_in']}, + self._authed_endpoint, + self._oauth_endpoint, + self.client_id, + self.api_key, + cache=self.cache) + + def temptoken_authorize(self, access_token, expires_in, refresh_token): + return AuthedConnection({'access_token': access_token, + 'refresh_token': refresh_token, + 'expires_in': expires_in}, + self._authed_endpoint, + self._oauth_endpoint, + self.client_id, + self.api_key, + cache=self.cache) + + +class AuthedConnection(EVE): + def __init__(self, res, endpoint, oauth_endpoint, client_id=None, api_key=None, **kwargs): + EVE.__init__(self, **kwargs) + self.client_id = client_id + self.api_key = api_key + self.token = res['access_token'] + self.refresh_token = res['refresh_token'] + self.expires = int(time.time()) + res['expires_in'] + self._oauth_endpoint = oauth_endpoint + self._endpoint = endpoint + self._session.headers.update({"Authorization": "Bearer %s" % self.token}) + + 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: + self._cache['whoami'] = self.get("%s/verify" % self._oauth_endpoint) + return self._cache['whoami'] + + def refresh(self): + res = self._authorize(params={"grant_type": "refresh_token", "refresh_token": self.refresh_token}) + self.token = res['access_token'] + self.expires = int(time.time()) + res['expires_in'] + self._session.headers.update({"Authorization": "Bearer %s" % self.token}) + return self # for backwards compatibility + + def get(self, resource, params=None): + if int(time.time()) >= self.expires: + self.refresh() + return super(self.__class__, self).get(resource, 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__() diff --git a/pycrest/weak_ciphers.py b/pycrest/weak_ciphers.py new file mode 100644 index 000000000..03de8321a --- /dev/null +++ b/pycrest/weak_ciphers.py @@ -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) From d013a41079b9f76b1e60631e65f6b678cdfed066 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 17 Oct 2015 21:47:55 -0400 Subject: [PATCH 03/41] Added CREST client data (for SISI), and rudimentary database support. Valid data has to be manually entered at this time, but the loading of the crest character works and uses pycrest correctly --- config.py | 5 +++++ eos/db/__init__.py | 2 +- eos/db/saveddata/__init__.py | 2 +- eos/db/saveddata/queries.py | 8 +++++++- eos/types.py | 1 + 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index 2a5071f5e..f298e5775 100644 --- a/config.py +++ b/config.py @@ -24,6 +24,11 @@ expansionName = "Vanguard" expansionVersion = "1.0" evemonMinVersion = "4081" +# CREST client information +clientID = '554727742a354f62ad9dfb34a188abc2' +clientSecret = 'fyCksblVC4AHafeYI9XOcV44xi0AOnMLV8tEU45M' +clientCallback = 'http://localhost:6461' + pyfaPath = None savePath = None saveDB = None diff --git a/eos/db/__init__.py b/eos/db/__init__.py index 8867d63c5..9455dec37 100644 --- a/eos/db/__init__.py +++ b/eos/db/__init__.py @@ -75,7 +75,7 @@ from eos.db.saveddata.queries import getUser, getCharacter, getFit, getFitsWithS getFitList, getFleetList, getFleet, save, remove, commit, add, \ getCharactersForUser, getMiscData, getSquadsIDsWithFitID, getWing, \ getSquad, getBoosterFits, getProjectedFits, getTargetResistsList, getTargetResists,\ - clearPrices, countAllFits + clearPrices, countAllFits, getCrestCharacters #If using in memory saveddata, you'll want to reflect it so the data structure is good. if config.saveddata_connectionstring == "sqlite:///:memory:": diff --git a/eos/db/saveddata/__init__.py b/eos/db/saveddata/__init__.py index 31e71c01a..c0677230d 100644 --- a/eos/db/saveddata/__init__.py +++ b/eos/db/saveddata/__init__.py @@ -1,3 +1,3 @@ -__all__ = ["character", "fit", "module", "user", "skill", "price", +__all__ = ["character", "fit", "module", "user", "crest", "skill", "price", "booster", "drone", "implant", "fleet", "damagePattern", "miscData", "targetResists"] diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py index b71f6b98a..68af39398 100644 --- a/eos/db/saveddata/queries.py +++ b/eos/db/saveddata/queries.py @@ -19,7 +19,7 @@ from eos.db.util import processEager, processWhere from eos.db import saveddata_session, sd_lock -from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad, TargetResists +from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad, TargetResists, Crest from eos.db.saveddata.fleet import squadmembers_table from eos.db.saveddata.fit import projectedFits_table from sqlalchemy.sql import and_ @@ -416,6 +416,12 @@ def getProjectedFits(fitID): else: raise TypeError("Need integer as argument") +def getCrestCharacters(eager=None): + eager = processEager(eager) + with sd_lock: + characters = saveddata_session.query(Crest).options(*eager).all() + return characters + def removeInvalid(fits): invalids = [f for f in fits if f.isInvalid] diff --git a/eos/types.py b/eos/types.py index 6e98749d2..e4f1fd5e9 100644 --- a/eos/types.py +++ b/eos/types.py @@ -21,6 +21,7 @@ from eos.gamedata import Attribute, Category, Effect, Group, Icon, Item, MarketG MetaGroup, AttributeInfo, Unit, EffectInfo, MetaType, MetaData, Traits from eos.saveddata.price import Price from eos.saveddata.user import User +from eos.saveddata.crest import Crest from eos.saveddata.damagePattern import DamagePattern from eos.saveddata.targetResists import TargetResists from eos.saveddata.character import Character, Skill From c4246c0d5020d6d415d848e908ba4082705ed106 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 18 Oct 2015 00:21:01 -0400 Subject: [PATCH 04/41] Added CREST service and first pass of fitting resource browsing. I want to completely rework this eventually, but right now just focused on getting a rough draft going. --- eos/db/__init__.py | 2 +- eos/db/saveddata/queries.py | 18 +++++ gui/crestFittings.py | 127 ++++++++++++++++++++++++++++++++++++ gui/mainFrame.py | 8 ++- gui/mainMenuBar.py | 7 ++ service/__init__.py | 1 + 6 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 gui/crestFittings.py diff --git a/eos/db/__init__.py b/eos/db/__init__.py index 9455dec37..807ce950f 100644 --- a/eos/db/__init__.py +++ b/eos/db/__init__.py @@ -75,7 +75,7 @@ from eos.db.saveddata.queries import getUser, getCharacter, getFit, getFitsWithS getFitList, getFleetList, getFleet, save, remove, commit, add, \ getCharactersForUser, getMiscData, getSquadsIDsWithFitID, getWing, \ getSquad, getBoosterFits, getProjectedFits, getTargetResistsList, getTargetResists,\ - clearPrices, countAllFits, getCrestCharacters + clearPrices, countAllFits, getCrestCharacters, getCrestCharacter #If using in memory saveddata, you'll want to reflect it so the data structure is good. if config.saveddata_connectionstring == "sqlite:///:memory:": diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py index 68af39398..c467153c3 100644 --- a/eos/db/saveddata/queries.py +++ b/eos/db/saveddata/queries.py @@ -422,6 +422,24 @@ def getCrestCharacters(eager=None): characters = saveddata_session.query(Crest).options(*eager).all() return characters +@cachedQuery(Crest, 1, "lookfor") +def getCrestCharacter(lookfor, eager=None): + if isinstance(lookfor, int): + if eager is None: + with sd_lock: + character = saveddata_session.query(Crest).get(lookfor) + else: + eager = processEager(eager) + with sd_lock: + character = saveddata_session.query(Crest).options(*eager).filter(Crest.ID == lookfor).first() + elif isinstance(lookfor, basestring): + eager = processEager(eager) + with sd_lock: + character = saveddata_session.query(Crest).options(*eager).filter(Crest.name == lookfor).first() + else: + raise TypeError("Need integer or string as argument") + return character + def removeInvalid(fits): invalids = [f for f in fits if f.isInvalid] diff --git a/gui/crestFittings.py b/gui/crestFittings.py new file mode 100644 index 000000000..45f2c4df8 --- /dev/null +++ b/gui/crestFittings.py @@ -0,0 +1,127 @@ +import wx +import service +import gui.display as d +from eos.types import Cargo +from eos.db import getItem + +class CrestFittings(wx.Frame): + + def __init__(self, parent): + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=wx.DefaultPosition, size=wx.Size( 550,450 ), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) + + self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) + + mainSizer = wx.BoxSizer(wx.VERTICAL) + sCrest = service.Crest.getInstance() + + self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) + chars = sCrest.getCrestCharacters() + for char in chars: + self.charChoice.Append(char.name, char.ID) + + characterSelectSizer = wx.BoxSizer( wx.HORIZONTAL ) + self.charChoice.SetSelection(0) + + + characterSelectSizer.Add( self.charChoice, 1, wx.ALL, 5 ) + self.fetchBtn = wx.Button( self, wx.ID_ANY, u"Fetch Fits", wx.DefaultPosition, wx.DefaultSize, 5 ) + characterSelectSizer.Add( self.fetchBtn, 0, wx.ALL, 5 ) + mainSizer.Add( characterSelectSizer, 0, wx.EXPAND, 5 ) + + self.sl = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) + mainSizer.Add( self.sl, 0, wx.EXPAND |wx.ALL, 5 ) + + contentSizer = wx.BoxSizer( wx.HORIZONTAL ) + browserSizer = wx.BoxSizer( wx.VERTICAL ) + + self.fitTree = FittingsTreeView(self) + browserSizer.Add( self.fitTree, 1, wx.ALL|wx.EXPAND, 5 ) + contentSizer.Add( browserSizer, 1, wx.EXPAND, 0 ) + fitSizer = wx.BoxSizer( wx.VERTICAL ) + + self.fitView = FitView(self) + self.importBtn = wx.Button( self, wx.ID_ANY, u"Import", wx.DefaultPosition, wx.DefaultSize, 5 ) + fitSizer.Add( self.fitView, 1, wx.ALL|wx.EXPAND, 5 ) + fitSizer.Add( self.importBtn, 0, wx.ALL|wx.EXPAND, 5 ) + + contentSizer.Add(fitSizer, 1, wx.EXPAND, 0) + mainSizer.Add(contentSizer, 1, wx.EXPAND, 5) + + self.fetchBtn.Bind(wx.EVT_BUTTON, self.fetchFittings) + + self.SetSizer(mainSizer) + self.Layout() + + self.Centre(wx.BOTH) + + def getActiveCharacter(self): + selection = self.charChoice.GetCurrentSelection() + return self.charChoice.GetClientData(selection) if selection is not None else None + + def fetchFittings(self, event): + sCrest = service.Crest.getInstance() + fittings = sCrest.getFittings(self.getActiveCharacter()) + self.fitTree.populateSkillTree(fittings) + + +class FittingsTreeView(wx.Panel): + def __init__(self, parent): + wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, style=wx.TAB_TRAVERSAL) + self.parent = parent + pmainSizer = wx.BoxSizer(wx.VERTICAL) + + tree = self.fittingsTreeCtrl = wx.TreeCtrl(self, wx.ID_ANY, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT) + pmainSizer.Add(tree, 1, wx.EXPAND | wx.ALL, 0) + + self.root = tree.AddRoot("Fits") + self.populateSkillTree(None) + + self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.displayFit) + + self.SetSizer(pmainSizer) + + self.Layout() + + def populateSkillTree(self, json): + if json is None: + return + root = self.root + tree = self.fittingsTreeCtrl + tree.DeleteChildren(root) + + dict = {} + fits = json['items'] + for fit in fits: + if fit['ship']['name'] not in dict: + dict[fit['ship']['name']] = [] + dict[fit['ship']['name']].append(fit) + + for name, fits in dict.iteritems(): + shipID = tree.AppendItem(root, name) + for fit in fits: + fitId = tree.AppendItem(shipID, fit['name']) + tree.SetPyData(fitId, fit['items']) + + tree.SortChildren(root) + + def displayFit(self, event): + selection = self.fittingsTreeCtrl.GetSelection() + items = self.fittingsTreeCtrl.GetPyData(selection) + list = [] + + for item in items: + cargo = Cargo(getItem(item['type']['id'])) + cargo.amount = item['quantity'] + list.append(cargo) + self.parent.fitView.populate(list) + self.parent.fitView.refresh(list) + +class FitView(d.Display): + DEFAULT_COLS = ["Base Icon", + "Base Name"] + + def __init__(self, parent): + d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL) + + #self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem) + #self.Bind(wx.EVT_KEY_UP, self.kbEvent) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index cb90ce0cc..2ce0413c1 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -44,6 +44,7 @@ from gui.multiSwitch import MultiSwitch from gui.statsPane import StatsPane from gui.shipBrowser import ShipBrowser, FitSelected, ImportSelected, Stage3Selected from gui.characterEditor import CharacterEditor, SaveCharacterAs +from gui.crestFittings import CrestFittings from gui.characterSelection import CharacterSelection from gui.patternEditor import DmgPatternEditorDlg from gui.resistsEditor import ResistsEditorDlg @@ -57,7 +58,6 @@ from gui.builtinViews import * from time import gmtime, strftime - #dummy panel(no paint no erasebk) class PFPanel(wx.Panel): def __init__(self,parent): @@ -416,6 +416,8 @@ class MainFrame(wx.Frame): self.Bind(wx.EVT_MENU, self.saveCharAs, id = menuBar.saveCharAsId) # Save current character self.Bind(wx.EVT_MENU, self.revertChar, id = menuBar.revertCharId) + # Browse fittings + self.Bind(wx.EVT_MENU, self.eveFittings, id = menuBar.eveFittingsId) #Clipboard exports self.Bind(wx.EVT_MENU, self.exportToClipboard, id=wx.ID_COPY) @@ -480,6 +482,10 @@ class MainFrame(wx.Frame): atable = wx.AcceleratorTable(actb) self.SetAcceleratorTable(atable) + def eveFittings(self, event): + dlg=CrestFittings(self) + dlg.Show() + def saveChar(self, event): sChr = service.Character.getInstance() charID = self.charSelection.getActiveCharacter() diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 7c971ae4d..76ea60f75 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -40,6 +40,7 @@ class MainMenuBar(wx.MenuBar): self.saveCharId = wx.NewId() self.saveCharAsId = wx.NewId() self.revertCharId = wx.NewId() + self.eveFittingsId = wx.NewId() self.mainFrame = gui.mainFrame.MainFrame.getInstance() @@ -102,6 +103,12 @@ class MainMenuBar(wx.MenuBar): preferencesItem.SetBitmap(BitmapLoader.getBitmap("preferences_small", "gui")) windowMenu.AppendItem(preferencesItem) + # CREST Menu + crestMenu = wx.Menu() + self.Append(crestMenu, "&CREST") + eveFittings = wx.MenuItem(crestMenu, self.eveFittingsId, "Browse EVE Fittings") + crestMenu.AppendItem(eveFittings) + # Help menu helpMenu = wx.Menu() self.Append(helpMenu, "&Help") diff --git a/service/__init__.py b/service/__init__.py index 8e9e9f6f5..00e0e1153 100644 --- a/service/__init__.py +++ b/service/__init__.py @@ -10,3 +10,4 @@ from service.update import Update from service.price import Price from service.network import Network from service.eveapi import EVEAPIConnection, ParseXML +from service.crest import Crest From 9269c544348f0309a382ad3f69d71200a5d39b9f Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 18 Oct 2015 01:33:39 -0400 Subject: [PATCH 05/41] Implement exporting in CREST format --- config.py | 11 ++++++++ gui/copySelectDialog.py | 6 ++-- gui/mainFrame.py | 15 ++++++---- service/fit.py | 4 +++ service/port.py | 61 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 7 deletions(-) diff --git a/config.py b/config.py index f298e5775..e0b4ba0c0 100644 --- a/config.py +++ b/config.py @@ -1,5 +1,6 @@ import os import sys +import pycrest # TODO: move all logging back to pyfa.py main loop # We moved it here just to avoid rebuilding windows skeleton for now (any change to pyfa.py needs it) @@ -28,6 +29,16 @@ evemonMinVersion = "4081" clientID = '554727742a354f62ad9dfb34a188abc2' clientSecret = 'fyCksblVC4AHafeYI9XOcV44xi0AOnMLV8tEU45M' clientCallback = 'http://localhost:6461' +clientTest = True + +# There are times we will need to access the base object outside of CREST calls +# (for example when exporting we need the correct href of the server) +# This will probably move elsewhere eventually +pycrest_eve = pycrest.EVE( + client_id=clientID, + api_key=clientSecret, + redirect_uri=clientCallback, + testing=clientTest) pyfaPath = None savePath = None diff --git a/gui/copySelectDialog.py b/gui/copySelectDialog.py index 0faf850cb..14ee68703 100644 --- a/gui/copySelectDialog.py +++ b/gui/copySelectDialog.py @@ -25,16 +25,18 @@ class CopySelectDialog(wx.Dialog): copyFormatEftImps = 1 copyFormatXml = 2 copyFormatDna = 3 + copyFormatCrest = 4 def __init__(self, parent): wx.Dialog.__init__(self, parent, id = wx.ID_ANY, title = u"Select a format", size = (-1,-1), style = wx.DEFAULT_DIALOG_STYLE) mainSizer = wx.BoxSizer(wx.VERTICAL) - copyFormats = [u"EFT", u"EFT (Implants)", u"XML", u"DNA"] + copyFormats = [u"EFT", u"EFT (Implants)", u"XML", u"DNA", u"CREST"] copyFormatTooltips = {CopySelectDialog.copyFormatEft: u"EFT text format", CopySelectDialog.copyFormatEftImps: u"EFT text format", CopySelectDialog.copyFormatXml: u"EVE native XML format", - CopySelectDialog.copyFormatDna: u"A one-line text format"} + CopySelectDialog.copyFormatDna: u"A one-line text format", + CopySelectDialog.copyFormatCrest: u"A JSON format used for EVE CREST"} selector = wx.RadioBox(self, wx.ID_ANY, label = u"Copy to the clipboard using:", choices = copyFormats, style = wx.RA_SPECIFY_ROWS) selector.Bind(wx.EVT_RADIOBOX, self.Selected) for format, tooltip in copyFormatTooltips.iteritems(): diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 2ce0413c1..d2747626a 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -548,6 +548,10 @@ class MainFrame(wx.Frame): sFit = service.Fit.getInstance() toClipboard(sFit.exportDna(self.getActiveFit())) + def clipboardCrest(self): + sFit = service.Fit.getInstance() + toClipboard(sFit.exportCrest(self.getActiveFit())) + def clipboardXml(self): sFit = service.Fit.getInstance() toClipboard(sFit.exportXml(None, self.getActiveFit())) @@ -565,14 +569,15 @@ class MainFrame(wx.Frame): CopySelectDict = {CopySelectDialog.copyFormatEft: self.clipboardEft, CopySelectDialog.copyFormatEftImps: self.clipboardEftImps, CopySelectDialog.copyFormatXml: self.clipboardXml, - CopySelectDialog.copyFormatDna: self.clipboardDna} + CopySelectDialog.copyFormatDna: self.clipboardDna, + CopySelectDialog.copyFormatCrest: self.clipboardCrest} dlg = CopySelectDialog(self) dlg.ShowModal() selected = dlg.GetSelected() - try: - CopySelectDict[selected]() - except: - pass + + CopySelectDict[selected]() + + dlg.Destroy() def exportSkillsNeeded(self, event): diff --git a/service/fit.py b/service/fit.py index b93d3b0b2..efbf95b8b 100644 --- a/service/fit.py +++ b/service/fit.py @@ -820,6 +820,10 @@ class Fit(object): fit = eos.db.getFit(fitID) return Port.exportDna(fit) + def exportCrest(self, fitID, callback=None): + fit = eos.db.getFit(fitID) + return Port.exportCrest(fit, callback) + def exportXml(self, callback=None, *fitIDs): fits = map(lambda fitID: eos.db.getFit(fitID), fitIDs) return Port.exportXml(callback, *fits) diff --git a/service/port.py b/service/port.py index 23721945f..8a12ecc4f 100644 --- a/service/port.py +++ b/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,62 @@ 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): + print "export" + nested_dict = lambda: collections.defaultdict(nested_dict) + fit = nested_dict() + + eve = config.pycrest_eve + + fit['name'] = ofit.name + fit['ship']['href'] = "%stypes/%d/"%(eve._endpoint, ofit.ship.item.ID) + fit['ship']['id'] = ofit.ship.item.ID + fit['ship']['name'] = ofit.ship.item.name + + fit['description'] = ""%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 + item['quantity'] = 1 + item['type']['href'] = "%stypes/%d/"%(eve._endpoint, module.item.ID) + item['type']['id'] = module.item.ID + item['type']['name'] = module.item.name + else: + if not slot in slotNum: + slotNum[slot] = INV_FLAGS[slot] + + item['flag'] = slotNum[slot] + item['quantity'] = 1 + item['type']['href'] = "%stypes/%d/"%(eve._endpoint, module.item.ID) + item['type']['id'] = module.item.ID + item['type']['name'] = module.item.name + + slotNum[slot] += 1 + fit['items'].append(item) + + print json.dumps(fit) + pass @classmethod def importAuto(cls, string, path=None, activeFit=None, callback=None, encoding=None): @@ -63,6 +119,11 @@ class Port(object): # Use DNA format for all other cases return "DNA", (cls.importDna(string),) + @staticmethod + def importCrest(json): + pass + + @staticmethod def importDna(string): sMkt = service.Market.getInstance() From 23dbb59f3f871887a2d9b835b10cceeff2c84e47 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 18 Oct 2015 01:46:41 -0400 Subject: [PATCH 06/41] Couple fixes --- service/port.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/service/port.py b/service/port.py index 8a12ecc4f..14b14fa3c 100644 --- a/service/port.py +++ b/service/port.py @@ -48,16 +48,20 @@ class Port(object): """Service which houses all import/export format functions""" @classmethod def exportCrest(cls, ofit, callback=None): - print "export" + # 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() eve = config.pycrest_eve - - fit['name'] = ofit.name + # 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._endpoint, ofit.ship.item.ID) - fit['ship']['id'] = ofit.ship.item.ID - fit['ship']['name'] = ofit.ship.item.name + fit['ship']['id'] = 0 + fit['ship']['name'] = '' fit['description'] = ""%ofit.ID fit['items'] = [] @@ -76,8 +80,8 @@ class Port(object): item['flag'] = slot item['quantity'] = 1 item['type']['href'] = "%stypes/%d/"%(eve._endpoint, module.item.ID) - item['type']['id'] = module.item.ID - item['type']['name'] = module.item.name + item['type']['id'] = 0 + item['type']['name'] = '' else: if not slot in slotNum: slotNum[slot] = INV_FLAGS[slot] @@ -85,14 +89,13 @@ class Port(object): item['flag'] = slotNum[slot] item['quantity'] = 1 item['type']['href'] = "%stypes/%d/"%(eve._endpoint, module.item.ID) - item['type']['id'] = module.item.ID - item['type']['name'] = module.item.name + item['type']['id'] = 0 + item['type']['name'] = '' slotNum[slot] += 1 fit['items'].append(item) - print json.dumps(fit) - pass + return json.dumps(fit) @classmethod def importAuto(cls, string, path=None, activeFit=None, callback=None, encoding=None): From b1729095a0740ffd0bbe6e7a7bcd17a448a46696 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 18 Oct 2015 17:13:19 -0400 Subject: [PATCH 07/41] Export fitting to EVE, fix json bugs, and include forgotten CREST service. --- gui/crestFittings.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ gui/mainFrame.py | 8 ++++++- gui/mainMenuBar.py | 5 ++-- service/crest.py | 34 +++++++++++++++++++++++++++ service/port.py | 19 +++++++-------- 5 files changed, 108 insertions(+), 14 deletions(-) create mode 100644 service/crest.py diff --git a/gui/crestFittings.py b/gui/crestFittings.py index 45f2c4df8..581df7839 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -3,6 +3,7 @@ import service import gui.display as d from eos.types import Cargo from eos.db import getItem +import json class CrestFittings(wx.Frame): @@ -64,6 +65,61 @@ class CrestFittings(wx.Frame): self.fitTree.populateSkillTree(fittings) +class ExportToEve(wx.Frame): + + def __init__(self, parent): + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=wx.DefaultPosition, size=(wx.Size(500,100)), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) + + self.mainFrame = parent + self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) + + sCrest = service.Crest.getInstance() + + self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) + chars = sCrest.getCrestCharacters() + for char in chars: + self.charChoice.Append(char.name, char.ID) + + mainSizer = wx.BoxSizer( wx.HORIZONTAL ) + self.charChoice.SetSelection(0) + + mainSizer.Add( self.charChoice, 1, wx.ALL, 5 ) + self.exportBtn = wx.Button( self, wx.ID_ANY, u"Export Fit", wx.DefaultPosition, wx.DefaultSize, 5 ) + mainSizer.Add( self.exportBtn, 0, wx.ALL, 5 ) + + self.exportBtn.Bind(wx.EVT_BUTTON, self.exportFitting) + + self.statusbar = wx.StatusBar(self) + self.statusbar.SetFieldsCount(2) + self.statusbar.SetStatusWidths([100, -1]) + + + self.SetSizer(mainSizer) + self.SetStatusBar(self.statusbar) + self.Layout() + + self.Centre(wx.BOTH) + + def getActiveCharacter(self): + selection = self.charChoice.GetCurrentSelection() + return self.charChoice.GetClientData(selection) if selection is not None else None + + def exportFitting(self, event): + self.statusbar.SetStatusText("", 0) + self.statusbar.SetStatusText("Sending request and awaiting response", 1) + sCrest = service.Crest.getInstance() + + sFit = service.Fit.getInstance() + data = sFit.exportCrest(self.mainFrame.getActiveFit()) + res = sCrest.postFitting(self.getActiveCharacter(), data) + + self.statusbar.SetStatusText("%d: %s"%(res.status_code, res.reason), 0) + try: + text = json.loads(res.text) + self.statusbar.SetStatusText(text['message'], 1) + except ValueError: + self.statusbar.SetStatusText("", 1) + class FittingsTreeView(wx.Panel): def __init__(self, parent): wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, style=wx.TAB_TRAVERSAL) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index d2747626a..377f2c3f8 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -44,7 +44,7 @@ from gui.multiSwitch import MultiSwitch from gui.statsPane import StatsPane from gui.shipBrowser import ShipBrowser, FitSelected, ImportSelected, Stage3Selected from gui.characterEditor import CharacterEditor, SaveCharacterAs -from gui.crestFittings import CrestFittings +from gui.crestFittings import CrestFittings, ExportToEve from gui.characterSelection import CharacterSelection from gui.patternEditor import DmgPatternEditorDlg from gui.resistsEditor import ResistsEditorDlg @@ -418,6 +418,8 @@ class MainFrame(wx.Frame): self.Bind(wx.EVT_MENU, self.revertChar, id = menuBar.revertCharId) # Browse fittings self.Bind(wx.EVT_MENU, self.eveFittings, id = menuBar.eveFittingsId) + # Export to EVE + self.Bind(wx.EVT_MENU, self.exportToEve, id = menuBar.exportToEveId) #Clipboard exports self.Bind(wx.EVT_MENU, self.exportToClipboard, id=wx.ID_COPY) @@ -486,6 +488,10 @@ class MainFrame(wx.Frame): dlg=CrestFittings(self) dlg.Show() + def exportToEve(self, event): + dlg=ExportToEve(self) + dlg.Show() + def saveChar(self, event): sChr = service.Character.getInstance() charID = self.charSelection.getActiveCharacter() diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 76ea60f75..069437c31 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -41,6 +41,7 @@ class MainMenuBar(wx.MenuBar): self.saveCharAsId = wx.NewId() self.revertCharId = wx.NewId() self.eveFittingsId = wx.NewId() + self.exportToEveId = wx.NewId() self.mainFrame = gui.mainFrame.MainFrame.getInstance() @@ -106,8 +107,8 @@ class MainMenuBar(wx.MenuBar): # CREST Menu crestMenu = wx.Menu() self.Append(crestMenu, "&CREST") - eveFittings = wx.MenuItem(crestMenu, self.eveFittingsId, "Browse EVE Fittings") - crestMenu.AppendItem(eveFittings) + crestMenu.Append(self.eveFittingsId, "Browse EVE Fittings") + crestMenu.Append(self.exportToEveId, "Export To EVE") # Help menu helpMenu = wx.Menu() diff --git a/service/crest.py b/service/crest.py new file mode 100644 index 000000000..f77829edd --- /dev/null +++ b/service/crest.py @@ -0,0 +1,34 @@ +import eos.db +import config + +class Crest(): + + _instance = None + @classmethod + def getInstance(cls): + if cls._instance == None: + cls._instance = Crest() + + return cls._instance + + def __init__(self): + + pass + + def getCrestCharacters(self): + return eos.db.getCrestCharacters() + + def getCrestCharacter(self, charID): + return eos.db.getCrestCharacter(charID) + + def getFittings(self, charID): + char = self.getCrestCharacter(charID) + char.auth() + return char.eve.get('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID) + + def postFitting(self, charID, json): + char = self.getCrestCharacter(charID) + char.auth() + print char.eve.token + res = char.eve._session.post('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID, data=json) + return res diff --git a/service/port.py b/service/port.py index 14b14fa3c..1cfbbf297 100644 --- a/service/port.py +++ b/service/port.py @@ -59,8 +59,8 @@ class Port(object): # 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._endpoint, ofit.ship.item.ID) - fit['ship']['id'] = 0 + fit['ship']['href'] = "%stypes/%d/"%(eve._authed_endpoint, ofit.ship.item.ID) + fit['ship']['id'] = ofit.ship.item.ID fit['ship']['name'] = '' fit['description'] = ""%ofit.ID @@ -78,21 +78,18 @@ class Port(object): # Order of subsystem matters based on this attr. See GH issue #130 slot = int(module.getModifiedItemAttr("subSystemSlot")) item['flag'] = slot - item['quantity'] = 1 - item['type']['href'] = "%stypes/%d/"%(eve._endpoint, module.item.ID) - item['type']['id'] = 0 - item['type']['name'] = '' else: if not slot in slotNum: slotNum[slot] = INV_FLAGS[slot] item['flag'] = slotNum[slot] - item['quantity'] = 1 - item['type']['href'] = "%stypes/%d/"%(eve._endpoint, module.item.ID) - item['type']['id'] = 0 - item['type']['name'] = '' - 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) From 972df6cad340bcbdcf319f0b452aeac11571316a Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 18 Oct 2015 23:38:21 -0400 Subject: [PATCH 08/41] Add support for importing CREST fittings from clipboard and through EVE Fittings browser --- gui/crestFittings.py | 37 +++++++++++++++++++++++++------------ gui/mainMenuBar.py | 1 + service/port.py | 44 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/gui/crestFittings.py b/gui/crestFittings.py index 581df7839..a58ba2e5c 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -12,6 +12,7 @@ class CrestFittings(wx.Frame): self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) + self.mainFrame = parent mainSizer = wx.BoxSizer(wx.VERTICAL) sCrest = service.Crest.getInstance() @@ -49,6 +50,7 @@ class CrestFittings(wx.Frame): mainSizer.Add(contentSizer, 1, wx.EXPAND, 5) self.fetchBtn.Bind(wx.EVT_BUTTON, self.fetchFittings) + self.importBtn.Bind(wx.EVT_BUTTON, self.importFitting) self.SetSizer(mainSizer) self.Layout() @@ -64,6 +66,13 @@ class CrestFittings(wx.Frame): fittings = sCrest.getFittings(self.getActiveCharacter()) self.fitTree.populateSkillTree(fittings) + def importFitting(self, event): + selection = self.fitView.fitSelection + data = self.fitTree.fittingsTreeCtrl.GetPyData(selection) + sFit = service.Fit.getInstance() + fits = sFit.importFitFromBuffer(data) + self.mainFrame._openAfterImport(fits) + class ExportToEve(wx.Frame): @@ -138,15 +147,15 @@ class FittingsTreeView(wx.Panel): self.Layout() - def populateSkillTree(self, json): - if json is None: + def populateSkillTree(self, data): + if data is None: return root = self.root tree = self.fittingsTreeCtrl tree.DeleteChildren(root) dict = {} - fits = json['items'] + fits = data['items'] for fit in fits: if fit['ship']['name'] not in dict: dict[fit['ship']['name']] = [] @@ -156,21 +165,25 @@ class FittingsTreeView(wx.Panel): shipID = tree.AppendItem(root, name) for fit in fits: fitId = tree.AppendItem(shipID, fit['name']) - tree.SetPyData(fitId, fit['items']) + print type(fit) + tree.SetPyData(fitId, json.dumps(fit)) tree.SortChildren(root) def displayFit(self, event): selection = self.fittingsTreeCtrl.GetSelection() - items = self.fittingsTreeCtrl.GetPyData(selection) + fit = json.loads(self.fittingsTreeCtrl.GetPyData(selection)) list = [] - for item in items: - cargo = Cargo(getItem(item['type']['id'])) - cargo.amount = item['quantity'] - list.append(cargo) - self.parent.fitView.populate(list) - self.parent.fitView.refresh(list) + for item in fit['items']: + try: + cargo = Cargo(getItem(item['type']['id'])) + cargo.amount = item['quantity'] + list.append(cargo) + except: + pass + self.parent.fitView.fitSelection = selection + self.parent.fitView.update(list) class FitView(d.Display): DEFAULT_COLS = ["Base Icon", @@ -178,6 +191,6 @@ class FitView(d.Display): def __init__(self, parent): d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL) - + self.fitSelection = None #self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem) #self.Bind(wx.EVT_KEY_UP, self.kbEvent) diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 069437c31..a54568ee7 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -25,6 +25,7 @@ import gui.graphFrame import gui.globalEvents as GE import service + class MainMenuBar(wx.MenuBar): def __init__(self): self.characterEditorId = wx.NewId() diff --git a/service/port.py b/service/port.py index 1cfbbf297..6951a3765 100644 --- a/service/port.py +++ b/service/port.py @@ -104,6 +104,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: @@ -120,9 +124,45 @@ class Port(object): return "DNA", (cls.importDna(string),) @staticmethod - def importCrest(json): - pass + 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): From 8151debfe139364266aa764e8c325857c481aa69 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Mon, 19 Oct 2015 20:09:39 -0400 Subject: [PATCH 09/41] Can now login to SSO from pyfa, which completes basic integration. This is very crude, will be refined. --- gui/crestFittings.py | 45 +++++++++++++++++++++++++++++++++++++++++++- gui/mainFrame.py | 8 +++++++- gui/mainMenuBar.py | 2 ++ service/crest.py | 10 ++++++++-- 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/gui/crestFittings.py b/gui/crestFittings.py index a58ba2e5c..a89ffb925 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -1,9 +1,16 @@ +import json +import thread + import wx +from wx.lib.pubsub import pub + import service import gui.display as d from eos.types import Cargo from eos.db import getItem -import json +from service.server import * +import config + class CrestFittings(wx.Frame): @@ -129,6 +136,42 @@ class ExportToEve(wx.Frame): except ValueError: self.statusbar.SetStatusText("", 1) +class CrestLogin(wx.Frame): + + def __init__(self, parent): + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="Login", pos=wx.DefaultPosition, size=wx.Size( -1,-1 ), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) + + self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) + + self.mainFrame = parent + sCrest = service.Crest.getInstance() + + mainSizer = wx.BoxSizer( wx.HORIZONTAL ) + self.loginBtn = wx.Button( self, wx.ID_ANY, u"Login via SSO", wx.DefaultPosition, wx.DefaultSize, 5 ) + mainSizer.Add( self.loginBtn, 0, wx.ALL, 5 ) + + self.loginBtn.Bind(wx.EVT_BUTTON, self.startServer) + + self.SetSizer(mainSizer) + self.Layout() + + pub.subscribe(self.sso_login, 'sso_login') + self.Centre(wx.BOTH) + + def sso_login(self, message): + self.httpd.stop() + con = config.pycrest_eve.authorize(message) + sCrest = service.Crest.getInstance() + sCrest.newChar(con) + self.Close() + + def startServer(self, event): + self.httpd = StoppableHTTPServer(('', 6461), AuthHandler) + thread.start_new_thread(self.httpd.serve, ()) + uri = config.pycrest_eve.auth_uri(scopes=['characterFittingsRead', 'characterFittingsWrite']) + wx.LaunchDefaultBrowser(uri) + + class FittingsTreeView(wx.Panel): def __init__(self, parent): wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, style=wx.TAB_TRAVERSAL) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 377f2c3f8..499219e37 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -44,7 +44,7 @@ from gui.multiSwitch import MultiSwitch from gui.statsPane import StatsPane from gui.shipBrowser import ShipBrowser, FitSelected, ImportSelected, Stage3Selected from gui.characterEditor import CharacterEditor, SaveCharacterAs -from gui.crestFittings import CrestFittings, ExportToEve +from gui.crestFittings import CrestFittings, ExportToEve, CrestLogin from gui.characterSelection import CharacterSelection from gui.patternEditor import DmgPatternEditorDlg from gui.resistsEditor import ResistsEditorDlg @@ -420,6 +420,8 @@ class MainFrame(wx.Frame): self.Bind(wx.EVT_MENU, self.eveFittings, id = menuBar.eveFittingsId) # Export to EVE self.Bind(wx.EVT_MENU, self.exportToEve, id = menuBar.exportToEveId) + # Login to EVE + self.Bind(wx.EVT_MENU, self.ssoLogin, id = menuBar.ssoLoginId) #Clipboard exports self.Bind(wx.EVT_MENU, self.exportToClipboard, id=wx.ID_COPY) @@ -488,6 +490,10 @@ class MainFrame(wx.Frame): dlg=CrestFittings(self) dlg.Show() + def ssoLogin(self, event): + dlg=CrestLogin(self) + dlg.Show() + def exportToEve(self, event): dlg=ExportToEve(self) dlg.Show() diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index a54568ee7..1af6e88e5 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -43,6 +43,7 @@ class MainMenuBar(wx.MenuBar): self.revertCharId = wx.NewId() self.eveFittingsId = wx.NewId() self.exportToEveId = wx.NewId() + self.ssoLoginId = wx.NewId() self.mainFrame = gui.mainFrame.MainFrame.getInstance() @@ -108,6 +109,7 @@ class MainMenuBar(wx.MenuBar): # CREST Menu crestMenu = wx.Menu() self.Append(crestMenu, "&CREST") + crestMenu.Append(self.ssoLoginId, "Login To EVE") crestMenu.Append(self.eveFittingsId, "Browse EVE Fittings") crestMenu.Append(self.exportToEveId, "Export To EVE") diff --git a/service/crest.py b/service/crest.py index f77829edd..236435a38 100644 --- a/service/crest.py +++ b/service/crest.py @@ -1,4 +1,5 @@ import eos.db +from eos.types import Crest as CrestUser import config class Crest(): @@ -12,7 +13,6 @@ class Crest(): return cls._instance def __init__(self): - pass def getCrestCharacters(self): @@ -29,6 +29,12 @@ class Crest(): def postFitting(self, charID, json): char = self.getCrestCharacter(charID) char.auth() - print char.eve.token res = char.eve._session.post('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID, data=json) return res + + def newChar(self, connection): + connection() + info = connection.whoami() + char = CrestUser(info['CharacterName'], info['CharacterID'], connection.refresh_token) + eos.db.save(char) + From 69a4e42ab06f005da3a443d1edfd69f4df7a88a2 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Mon, 19 Oct 2015 21:34:47 -0400 Subject: [PATCH 10/41] Forgot the server --- service/server.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 service/server.py diff --git a/service/server.py b/service/server.py new file mode 100644 index 000000000..3bc28a62b --- /dev/null +++ b/service/server.py @@ -0,0 +1,58 @@ +import SimpleHTTPServer, BaseHTTPServer +import urlparse +import socket +import thread +import wx + +from wx.lib.pubsub import setupkwargs +from wx.lib.pubsub import pub + +# https://github.com/fuzzysteve/CREST-Market-Downloader/ +class AuthHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + print "GET" + 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('Login successful. you can close this window now') + wx.CallAfter(pub.sendMessage, 'sso_login', message=str(parts['code'][0])) + + 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) + self.socket.settimeout(1) + 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 serve(self): + while self.run: + try: + self.handle_request() + except TypeError: + # this can happen if stopping server in middle of request? + pass + +if __name__=="__main__": + httpd = StoppableHTTPServer(('', 6461), AuthHandler) + thread.start_new_thread(httpd.serve, ()) + raw_input("Press to stop server\n") + httpd.stop() From e0f99ee1330ce70652bfbacafedac10c1a4123db Mon Sep 17 00:00:00 2001 From: blitzmann Date: Wed, 21 Oct 2015 23:10:06 -0400 Subject: [PATCH 11/41] Start refinement of CREST utilities: * Add preference page which gives option of implicit grant or user client details * Improve CREST service * Changes in pycrest which make things a little easier --- config.py | 15 ----- gui/builtinPreferenceViews/__init__.py | 2 +- gui/crestFittings.py | 1 - gui/mainFrame.py | 10 ++- pycrest/eve.py | 66 ++++++++------------ server.py | 24 -------- service/crest.py | 85 ++++++++++++++++++++++++-- service/html.py | 30 +++++++++ service/server.py | 12 ++-- service/settings.py | 26 ++++++++ 10 files changed, 176 insertions(+), 95 deletions(-) delete mode 100644 server.py create mode 100644 service/html.py diff --git a/config.py b/config.py index e0b4ba0c0..cdab4bffa 100644 --- a/config.py +++ b/config.py @@ -25,21 +25,6 @@ expansionName = "Vanguard" expansionVersion = "1.0" evemonMinVersion = "4081" -# CREST client information -clientID = '554727742a354f62ad9dfb34a188abc2' -clientSecret = 'fyCksblVC4AHafeYI9XOcV44xi0AOnMLV8tEU45M' -clientCallback = 'http://localhost:6461' -clientTest = True - -# There are times we will need to access the base object outside of CREST calls -# (for example when exporting we need the correct href of the server) -# This will probably move elsewhere eventually -pycrest_eve = pycrest.EVE( - client_id=clientID, - api_key=clientSecret, - redirect_uri=clientCallback, - testing=clientTest) - pyfaPath = None savePath = None saveDB = None diff --git a/gui/builtinPreferenceViews/__init__.py b/gui/builtinPreferenceViews/__init__.py index 923e011ab..20d2cba38 100644 --- a/gui/builtinPreferenceViews/__init__.py +++ b/gui/builtinPreferenceViews/__init__.py @@ -1 +1 @@ -__all__ = ["pyfaGeneralPreferences","pyfaHTMLExportPreferences","pyfaUpdatePreferences","pyfaNetworkPreferences"] +__all__ = ["pyfaGeneralPreferences","pyfaHTMLExportPreferences","pyfaUpdatePreferences","pyfaNetworkPreferences","pyfaCrestPreferences"] diff --git a/gui/crestFittings.py b/gui/crestFittings.py index a89ffb925..3ff8a1748 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -145,7 +145,6 @@ class CrestLogin(wx.Frame): self.mainFrame = parent sCrest = service.Crest.getInstance() - mainSizer = wx.BoxSizer( wx.HORIZONTAL ) self.loginBtn = wx.Button( self, wx.ID_ANY, u"Login via SSO", wx.DefaultPosition, wx.DefaultSize, 5 ) mainSizer.Add( self.loginBtn, 0, wx.ALL, 5 ) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 499219e37..c86865808 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -55,7 +55,6 @@ from gui.utils.clipboard import toClipboard, fromClipboard from gui.fleetBrowser import FleetBrowser from gui.updateDialog import UpdateDialog from gui.builtinViews import * - from time import gmtime, strftime #dummy panel(no paint no erasebk) @@ -491,8 +490,13 @@ class MainFrame(wx.Frame): dlg.Show() def ssoLogin(self, event): - dlg=CrestLogin(self) - dlg.Show() + sCrest = service.Crest.getInstance() + if sCrest.settings.get('mode') == 0: # Implicit, go directly to login + uri = sCrest.startServer() + wx.LaunchDefaultBrowser(uri) + else: + dlg=CrestLogin(self) + dlg.Show() def exportToEve(self, event): dlg=ExportToEve(self) diff --git a/pycrest/eve.py b/pycrest/eve.py index bc7495c71..7262457cf 100644 --- a/pycrest/eve.py +++ b/pycrest/eve.py @@ -122,7 +122,6 @@ class APIConnection(object): self.cache = DictCache() def get(self, resource, params=None): - print resource, params logger.debug('Getting resource %s', resource) if params is None: params = {} @@ -195,6 +194,9 @@ class EVE(APIConnection): 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): @@ -207,8 +209,11 @@ class EVE(APIConnection): def auth_uri(self, scopes=None, state=None): s = [] if not scopes else scopes - return "%s/authorize?response_type=code&redirect_uri=%s&client_id=%s%s%s" % ( + 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, quote(self.redirect_uri, safe=''), self.client_id, "&scope=%s" % ' '.join(s) if scopes else '', @@ -223,49 +228,30 @@ class EVE(APIConnection): raise APIException("Got unexpected status code from API: %i" % res.status_code) return res.json() - def authorize(self, code): - res = self._authorize(params={"grant_type": "authorization_code", "code": code}) - return AuthedConnection(res, - self._authed_endpoint, - self._oauth_endpoint, - self.client_id, - self.api_key, - cache=self.cache) - - def refr_authorize(self, refresh_token): - res = self._authorize(params={"grant_type": "refresh_token", "refresh_token": refresh_token}) - return AuthedConnection({'access_token': res['access_token'], - 'refresh_token': refresh_token, - 'expires_in': res['expires_in']}, - self._authed_endpoint, - self._oauth_endpoint, - self.client_id, - self.api_key, - cache=self.cache) - - def temptoken_authorize(self, access_token, expires_in, refresh_token): - return AuthedConnection({'access_token': access_token, - 'refresh_token': refresh_token, - 'expires_in': expires_in}, - self._authed_endpoint, - self._oauth_endpoint, - self.client_id, - self.api_key, - cache=self.cache) - - -class AuthedConnection(EVE): - def __init__(self, res, endpoint, oauth_endpoint, client_id=None, api_key=None, **kwargs): - EVE.__init__(self, **kwargs) - self.client_id = client_id - self.api_key = api_key + 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._oauth_endpoint = oauth_endpoint - self._endpoint = endpoint + 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) diff --git a/server.py b/server.py deleted file mode 100644 index dc5b9ac63..000000000 --- a/server.py +++ /dev/null @@ -1,24 +0,0 @@ -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -import urlparse - -class RequestHandler(BaseHTTPRequestHandler): - - def __init__(self, *args, **kwargs): - BaseHTTPRequestHandler.__init__(self, *args, **kwargs) - - def do_GET(self): - bits = urlparse.urlparse(self.path) - print urlparse.parse_qs(bits.query) - - -def main(): - try: - server = HTTPServer(('', 6461), RequestHandler) - print('Test server running...') - server.serve_forever() - except KeyboardInterrupt: - print "Closing socket" - server.socket.close() - -if __name__ == '__main__': - main() diff --git a/service/crest.py b/service/crest.py index 236435a38..12a6b18bd 100644 --- a/service/crest.py +++ b/service/crest.py @@ -1,9 +1,23 @@ import eos.db from eos.types import Crest as CrestUser -import config +import pycrest +import copy +import service +from service.server import * +import uuid +from wx.lib.pubsub import setupkwargs +from wx.lib.pubsub import pub + + +# TODO: +# With implicit grant, make sure we know when it expires and delete/inactive char class Crest(): + # @todo: move this to settings + clientCallback = 'http://localhost:6461' + clientTest = True + _instance = None @classmethod def getInstance(cls): @@ -13,22 +27,47 @@ class Crest(): return cls._instance def __init__(self): - pass + self.settings = service.settings.CRESTSettings.getInstance() + self.httpd = StoppableHTTPServer(('', 6461), AuthHandler) + self.scopes = ['characterFittingsRead', 'characterFittingsWrite'] + self.state = None + + # Base EVE connection that is copied to all characters + self.eve = pycrest.EVE( + client_id=self.settings.get('clientID'), + api_key=self.settings.get('clientSecret') if self.settings.get('mode') == 1 else None, + redirect_uri=self.clientCallback, + testing=self.clientTest) + + self.implicitCharacter = None + pub.subscribe(self.handleLogin, 'sso_login') def getCrestCharacters(self): - return eos.db.getCrestCharacters() + chars = eos.db.getCrestCharacters() + for char in chars: + if not hasattr(char, "eve"): + char.eve = copy.copy(self.eve) + # Give EVE instance refresh info. This allows us to set it + # without actually making the request to authorize at this time. + char.eve.temptoken_authorize(refresh_token=char.refresh_token) + return chars def getCrestCharacter(self, charID): - return eos.db.getCrestCharacter(charID) + ''' + Get character, and modify to include the eve connection + ''' + char = eos.db.getCrestCharacter(charID) + if not hasattr(char, "eve"): + char.eve = copy.copy(self.eve) + char.eve.temptoken_authorize(refresh_token=char.refresh_token) + return char def getFittings(self, charID): char = self.getCrestCharacter(charID) - char.auth() return char.eve.get('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID) def postFitting(self, charID, json): char = self.getCrestCharacter(charID) - char.auth() res = char.eve._session.post('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID, data=json) return res @@ -38,3 +77,37 @@ class Crest(): char = CrestUser(info['CharacterName'], info['CharacterID'], connection.refresh_token) eos.db.save(char) + def startServer(self): + thread.start_new_thread(self.httpd.serve, ()) + self.state = str(uuid.uuid4()) + return self.eve.auth_uri(scopes=self.scopes, state=self.state) + + def handleLogin(self, message): + if not message: + return + + if message['state'][0] != self.state: + return + + print "handling login by making characters and stuff" + print message + + if 'access_token' in message: # implicit + eve = copy.copy(self.eve) + eve.temptoken_authorize( + access_token=message['access_token'][0], + expires_in=int(message['expires_in'][0]) + ) + eve() + info = eve.whoami() + self.implicitCharacter = CrestUser(info['CharacterID'], info['CharacterName']) + self.implicitCharacter.eve = eve + wx.CallAfter(pub.sendMessage, 'login_success', type=0) + + elif 'code' in message: + print "handle authentication code" + + #wx.CallAfter(pub.sendMessage, 'login_success', type=1) + + + diff --git a/service/html.py b/service/html.py new file mode 100644 index 000000000..b073f7803 --- /dev/null +++ b/service/html.py @@ -0,0 +1,30 @@ +# HTML is stored here as frankly I'm not sure how loading a file would work with +# our current zipfile packaging (and I'm too lazy to find out) + +HTML = ''' + + + +Done. Please close this window. + +(will put more interesting messages here later) + + + +''' diff --git a/service/server.py b/service/server.py index 3bc28a62b..e36db9631 100644 --- a/service/server.py +++ b/service/server.py @@ -7,18 +7,20 @@ import wx from wx.lib.pubsub import setupkwargs from wx.lib.pubsub import pub +from html import HTML + # https://github.com/fuzzysteve/CREST-Market-Downloader/ class AuthHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): - print "GET" if self.path == "/favicon.ico": return parsed_path = urlparse.urlparse(self.path) - parts=urlparse.parse_qs(parsed_path.query) + parts = urlparse.parse_qs(parsed_path.query) self.send_response(200) self.end_headers() - self.wfile.write('Login successful. you can close this window now') - wx.CallAfter(pub.sendMessage, 'sso_login', message=str(parts['code'][0])) + self.wfile.write(HTML) + + wx.CallAfter(pub.sendMessage, 'sso_login', message=parts) def log_message(self, format, *args): return @@ -51,7 +53,7 @@ class StoppableHTTPServer(BaseHTTPServer.HTTPServer): # this can happen if stopping server in middle of request? pass -if __name__=="__main__": +if __name__ == "__main__": httpd = StoppableHTTPServer(('', 6461), AuthHandler) thread.start_new_thread(httpd.serve, ()) raw_input("Press to stop server\n") diff --git a/service/settings.py b/service/settings.py index dde48f71d..ec86524df 100644 --- a/service/settings.py +++ b/service/settings.py @@ -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, "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? From 6e04c64ffb726138d51ca21343e3194fa695bab4 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 23 Oct 2015 00:52:03 -0400 Subject: [PATCH 12/41] Changed login box to show currently logged in character with timer, and we now fetch the character image. --- gui/crestFittings.py | 59 +++++++++++++++++++++++++------------------- gui/mainFrame.py | 15 +++++++++-- service/crest.py | 2 +- 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/gui/crestFittings.py b/gui/crestFittings.py index 3ff8a1748..606da1d76 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -10,7 +10,7 @@ from eos.types import Cargo from eos.db import getItem from service.server import * import config - +import time class CrestFittings(wx.Frame): @@ -136,39 +136,48 @@ class ExportToEve(wx.Frame): except ValueError: self.statusbar.SetStatusText("", 1) -class CrestLogin(wx.Frame): +class CrestCharacterInfo(wx.Dialog): def __init__(self, parent): - wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="Login", pos=wx.DefaultPosition, size=wx.Size( -1,-1 ), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) - - self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) - + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Character Info", pos=wx.DefaultPosition, size = wx.Size( 200,240 )) self.mainFrame = parent sCrest = service.Crest.getInstance() - mainSizer = wx.BoxSizer( wx.HORIZONTAL ) - self.loginBtn = wx.Button( self, wx.ID_ANY, u"Login via SSO", wx.DefaultPosition, wx.DefaultSize, 5 ) - mainSizer.Add( self.loginBtn, 0, wx.ALL, 5 ) + self.char = sCrest.implicitCharacter + self.bitmapSet = False - self.loginBtn.Bind(wx.EVT_BUTTON, self.startServer) + mainSizer = wx.BoxSizer( wx.VERTICAL ) - self.SetSizer(mainSizer) - self.Layout() + self.headingText = wx.StaticText(self, wx.ID_ANY, "Currently logged in", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + self.headingText.Wrap( -1 ) + self.headingText.SetFont( wx.Font( 12, 74, 90, 92, False) ) + mainSizer.Add( self.headingText, 0, wx.EXPAND|wx.ALL, 5 ) - pub.subscribe(self.sso_login, 'sso_login') - self.Centre(wx.BOTH) + self.pic = wx.StaticBitmap(self, -1, wx.EmptyBitmap(128, 128)) + mainSizer.Add(self.pic, 0, wx.EXPAND, 5 ) - def sso_login(self, message): - self.httpd.stop() - con = config.pycrest_eve.authorize(message) - sCrest = service.Crest.getInstance() - sCrest.newChar(con) - self.Close() + self.characterText = wx.StaticText(self, wx.ID_ANY, self.char.name, wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + self.characterText.Wrap( -1 ) + self.characterText.SetFont( wx.Font( 11, 74, 90, 92, False) ) + mainSizer.Add( self.characterText, 0, wx.EXPAND|wx.ALL, 5 ) - def startServer(self, event): - self.httpd = StoppableHTTPServer(('', 6461), AuthHandler) - thread.start_new_thread(self.httpd.serve, ()) - uri = config.pycrest_eve.auth_uri(scopes=['characterFittingsRead', 'characterFittingsWrite']) - wx.LaunchDefaultBrowser(uri) + self.coutdownText = wx.StaticText( self, wx.ID_ANY, "", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE) + self.coutdownText.Wrap( -1 ) + mainSizer.Add( self.coutdownText, 0, wx.EXPAND, 5 ) + + self.SetSizer( mainSizer ) + self.Centre( wx.BOTH ) + + self.timer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.update, self.timer) + self.timer.Start(1) + + def update(self, event): + t = time.gmtime(self.char.eve.expires-time.time()) + if not self.bitmapSet and hasattr(self.char, 'img'): + self.pic.SetBitmap(wx.ImageFromStream(self.char.img).ConvertToBitmap()) + self.Layout() + self.bitmapSet = True + self.coutdownText.SetLabel(time.strftime("%H:%M:%S", t)) class FittingsTreeView(wx.Panel): diff --git a/gui/mainFrame.py b/gui/mainFrame.py index c86865808..7c58c1853 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -44,7 +44,7 @@ from gui.multiSwitch import MultiSwitch from gui.statsPane import StatsPane from gui.shipBrowser import ShipBrowser, FitSelected, ImportSelected, Stage3Selected from gui.characterEditor import CharacterEditor, SaveCharacterAs -from gui.crestFittings import CrestFittings, ExportToEve, CrestLogin +from gui.crestFittings import CrestFittings, ExportToEve, CrestCharacterInfo from gui.characterSelection import CharacterSelection from gui.patternEditor import DmgPatternEditorDlg from gui.resistsEditor import ResistsEditorDlg @@ -57,6 +57,9 @@ from gui.updateDialog import UpdateDialog from gui.builtinViews import * from time import gmtime, strftime +from wx.lib.pubsub import setupkwargs +from wx.lib.pubsub import pub + #dummy panel(no paint no erasebk) class PFPanel(wx.Panel): def __init__(self,parent): @@ -192,6 +195,8 @@ class MainFrame(wx.Frame): self.sUpdate = service.Update.getInstance() self.sUpdate.CheckUpdate(self.ShowUpdateBox) + pub.subscribe(self.showCharacterMgmt, 'login_success') + def ShowUpdateBox(self, release): dlg = UpdateDialog(self, release) dlg.ShowModal() @@ -489,13 +494,19 @@ class MainFrame(wx.Frame): dlg=CrestFittings(self) dlg.Show() + def showCharacterMgmt(self, type): + if type == 0: + print "login type is implicit" + dlg=CrestCharacterInfo(self) + dlg.Show() + def ssoLogin(self, event): sCrest = service.Crest.getInstance() if sCrest.settings.get('mode') == 0: # Implicit, go directly to login uri = sCrest.startServer() wx.LaunchDefaultBrowser(uri) else: - dlg=CrestLogin(self) + dlg=CrestCharacterInfo(self) dlg.Show() def exportToEve(self, event): diff --git a/service/crest.py b/service/crest.py index 12a6b18bd..20fa56651 100644 --- a/service/crest.py +++ b/service/crest.py @@ -9,7 +9,6 @@ import uuid from wx.lib.pubsub import setupkwargs from wx.lib.pubsub import pub - # TODO: # With implicit grant, make sure we know when it expires and delete/inactive char class Crest(): @@ -102,6 +101,7 @@ class Crest(): info = eve.whoami() self.implicitCharacter = CrestUser(info['CharacterID'], info['CharacterName']) self.implicitCharacter.eve = eve + self.implicitCharacter.fetchImage() wx.CallAfter(pub.sendMessage, 'login_success', type=0) elif 'code' in message: From 953a70327ada98efcbedf6e736a21bf253fe4e92 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 23 Oct 2015 01:21:51 -0400 Subject: [PATCH 13/41] Fix (hack at) pycrest to fix caching issue when getting another character. --- gui/crestFittings.py | 28 ++++++++++++++++------------ pycrest/eve.py | 7 ++++--- service/crest.py | 3 +++ 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/gui/crestFittings.py b/gui/crestFittings.py index 606da1d76..297589606 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -147,22 +147,22 @@ class CrestCharacterInfo(wx.Dialog): mainSizer = wx.BoxSizer( wx.VERTICAL ) - self.headingText = wx.StaticText(self, wx.ID_ANY, "Currently logged in", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) - self.headingText.Wrap( -1 ) - self.headingText.SetFont( wx.Font( 12, 74, 90, 92, False) ) - mainSizer.Add( self.headingText, 0, wx.EXPAND|wx.ALL, 5 ) - - self.pic = wx.StaticBitmap(self, -1, wx.EmptyBitmap(128, 128)) - mainSizer.Add(self.pic, 0, wx.EXPAND, 5 ) - - self.characterText = wx.StaticText(self, wx.ID_ANY, self.char.name, wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE ) + self.characterText = wx.StaticText(self, wx.ID_ANY, self.char.name, wx.DefaultPosition, wx.DefaultSize) self.characterText.Wrap( -1 ) self.characterText.SetFont( wx.Font( 11, 74, 90, 92, False) ) - mainSizer.Add( self.characterText, 0, wx.EXPAND|wx.ALL, 5 ) + mainSizer.Add( self.characterText, 0, wx.ALIGN_CENTRE | wx.ALL, 5 ) - self.coutdownText = wx.StaticText( self, wx.ID_ANY, "", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE) + self.pic = wx.StaticBitmap(self, -1, wx.EmptyBitmap(128, 128)) + mainSizer.Add(self.pic, 0, wx.EXPAND|wx.ALIGN_CENTER, 5 ) + + self.coutdownText = wx.StaticText( self, wx.ID_ANY, "", wx.DefaultPosition, wx.DefaultSize) self.coutdownText.Wrap( -1 ) - mainSizer.Add( self.coutdownText, 0, wx.EXPAND, 5 ) + mainSizer.Add( self.coutdownText, 0, wx.ALIGN_CENTER, 5 ) + + self.logoutBtn = wx.Button( self, wx.ID_ANY, u"Logout", wx.DefaultPosition, wx.DefaultSize, 5 ) + mainSizer.Add( self.logoutBtn, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.logoutBtn.Bind(wx.EVT_BUTTON, self.logout) self.SetSizer( mainSizer ) self.Centre( wx.BOTH ) @@ -179,6 +179,10 @@ class CrestCharacterInfo(wx.Dialog): self.bitmapSet = True self.coutdownText.SetLabel(time.strftime("%H:%M:%S", t)) + def logout(self, event): + sCrest = service.Crest.getInstance() + sCrest.logout() + self.Close() class FittingsTreeView(wx.Panel): def __init__(self, parent): diff --git a/pycrest/eve.py b/pycrest/eve.py index 7262457cf..d74f89567 100644 --- a/pycrest/eve.py +++ b/pycrest/eve.py @@ -258,9 +258,10 @@ class AuthedConnection(EVE): return self._data def whoami(self): - if 'whoami' not in self._cache: - self._cache['whoami'] = self.get("%s/verify" % self._oauth_endpoint) - return self._cache['whoami'] + #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 refresh(self): res = self._authorize(params={"grant_type": "refresh_token", "refresh_token": self.refresh_token}) diff --git a/service/crest.py b/service/crest.py index 20fa56651..c37eb1f53 100644 --- a/service/crest.py +++ b/service/crest.py @@ -76,6 +76,9 @@ class Crest(): char = CrestUser(info['CharacterName'], info['CharacterID'], connection.refresh_token) eos.db.save(char) + def logout(self): + self.implicitCharacter = None + def startServer(self): thread.start_new_thread(self.httpd.serve, ()) self.state = str(uuid.uuid4()) From f6cddcc86df72231c4d62d76f4e8d7870592cee7 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 23 Oct 2015 19:12:05 -0400 Subject: [PATCH 14/41] Add to repo missing preferences panel --- .../pyfaCrestPreferences.py | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 gui/builtinPreferenceViews/pyfaCrestPreferences.py diff --git a/gui/builtinPreferenceViews/pyfaCrestPreferences.py b/gui/builtinPreferenceViews/pyfaCrestPreferences.py new file mode 100644 index 000000000..f359b1ebe --- /dev/null +++ b/gui/builtinPreferenceViews/pyfaCrestPreferences.py @@ -0,0 +1,109 @@ +import wx + +from gui.preferenceView import PreferenceView +from gui.bitmapLoader import BitmapLoader + +import gui.mainFrame +import service + +class PFCrestPref ( PreferenceView): + title = "CREST" + + def populatePanel( self, panel ): + + self.mainFrame = gui.mainFrame.MainFrame.getInstance() + self.settings = service.settings.CRESTSettings.getInstance() + self.dirtySettings = False + dlgWidth = panel.GetParent().GetParent().ClientSize.width + mainSizer = wx.BoxSizer( wx.VERTICAL ) + + self.stTitle = wx.StaticText( panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.stTitle.Wrap( -1 ) + self.stTitle.SetFont( wx.Font( 12, 70, 90, 90, False, wx.EmptyString ) ) + + mainSizer.Add( self.stTitle, 0, wx.ALL, 5 ) + + self.m_staticline1 = wx.StaticLine( panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) + mainSizer.Add( self.m_staticline1, 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5 ) + + self.stInfo = wx.StaticText( panel, wx.ID_ANY, u"Please see the pyfa wiki on GitHub for information regarding these options.", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.stInfo.Wrap(dlgWidth - 50) + mainSizer.Add( self.stInfo, 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5 ) + + self.grantRadioBtn1 = wx.RadioButton( panel, wx.ID_ANY, u"Implicit Grant", wx.DefaultPosition, wx.DefaultSize, 0 ) + mainSizer.Add( self.grantRadioBtn1, 0, wx.ALL, 5 ) + + self.grantRadioBtn2 = wx.RadioButton( panel, wx.ID_ANY, u"User-supplied details", wx.DefaultPosition, wx.DefaultSize, 0 ) + mainSizer.Add( self.grantRadioBtn2, 0, wx.ALL, 5 ) + + proxyTitle = wx.StaticText( panel, wx.ID_ANY, "CREST client details", wx.DefaultPosition, wx.DefaultSize, 0 ) + proxyTitle.Wrap( -1 ) + proxyTitle.SetFont( wx.Font( 12, 70, 90, 90, False, wx.EmptyString ) ) + + mainSizer.Add( proxyTitle, 0, wx.ALL, 5 ) + mainSizer.Add( wx.StaticLine( panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ), 0, wx.EXPAND, 5 ) + + self.grantRadioBtn1.SetValue(self.settings.get('mode') == 0) + self.grantRadioBtn2.SetValue(self.settings.get('mode') == 1) + + self.grantRadioBtn1.Bind(wx.EVT_RADIOBUTTON, self.OnRadioChange) + self.grantRadioBtn2.Bind(wx.EVT_RADIOBUTTON, self.OnRadioChange) + + fgAddrSizer = wx.FlexGridSizer( 2, 2, 0, 0 ) + fgAddrSizer.AddGrowableCol( 1 ) + fgAddrSizer.SetFlexibleDirection( wx.BOTH ) + fgAddrSizer.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) + + self.stSetID = wx.StaticText( panel, wx.ID_ANY, u"Client ID:", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.stSetID.Wrap( -1 ) + fgAddrSizer.Add( self.stSetID, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 ) + + self.inputClientID = wx.TextCtrl( panel, wx.ID_ANY, self.settings.get('clientID'), wx.DefaultPosition, wx.DefaultSize, 0 ) + + fgAddrSizer.Add( self.inputClientID, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5 ) + + self.stSetSecret = wx.StaticText( panel, wx.ID_ANY, u"Client Secret:", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.stSetSecret.Wrap( -1 ) + + fgAddrSizer.Add( self.stSetSecret, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 ) + + self.inputClientSecret = wx.TextCtrl( panel, wx.ID_ANY, self.settings.get('clientSecret'), wx.DefaultPosition, wx.DefaultSize, 0 ) + + fgAddrSizer.Add( self.inputClientSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5 ) + + mainSizer.Add( fgAddrSizer, 0, wx.EXPAND, 5) + + self.inputClientID.Bind(wx.EVT_TEXT, self.OnEditClientID) + self.inputClientSecret.Bind(wx.EVT_TEXT, self.OnEditClientSecret) + + self.ToggleProxySettings(self.settings.get('mode')) + + panel.SetSizer( mainSizer ) + panel.Layout() + + def OnRadioChange(self, event): + self.settings.set('mode', 0 if self.grantRadioBtn1.Value else 1) + self.ToggleProxySettings(self.settings.get('mode')) + + def OnEditClientID(self, event): + self.settings.set('clientID', self.inputClientID.GetValue()) + + def OnEditClientSecret(self, event): + self.settings.set('clientSecret', self.inputClientSecret.GetValue()) + + def ToggleProxySettings(self, mode): + if mode: + self.stSetID.Enable() + self.inputClientID.Enable() + self.stSetSecret.Enable() + self.inputClientSecret.Enable() + else: + self.stSetID.Disable() + self.inputClientID.Disable() + self.stSetSecret.Disable() + self.inputClientSecret.Disable() + + def getImage(self): + return BitmapLoader.getBitmap("prefs_proxy", "gui") + +PFCrestPref.register() From 9929510e53a33e3afc7a517d52b11d4a96c1a476 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 23 Oct 2015 19:14:43 -0400 Subject: [PATCH 15/41] More work on CREST stuff. Now implicit character can GET and POST fits. --- gui/crestFittings.py | 81 +++++++++++++++++++++++++------------- gui/mainFrame.py | 8 ++-- gui/mainMenuBar.py | 19 ++++++++- gui/utils/repeatedTimer.py | 25 ++++++++++++ pycrest/eve.py | 2 +- service/crest.py | 17 +++++++- service/port.py | 3 +- 7 files changed, 121 insertions(+), 34 deletions(-) create mode 100644 gui/utils/repeatedTimer.py diff --git a/gui/crestFittings.py b/gui/crestFittings.py index 297589606..39af6ae4d 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -1,21 +1,16 @@ -import json -import thread - import wx -from wx.lib.pubsub import pub import service import gui.display as d from eos.types import Cargo from eos.db import getItem -from service.server import * -import config import time +import json class CrestFittings(wx.Frame): def __init__(self, parent): - wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=wx.DefaultPosition, size=wx.Size( 550,450 ), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="Browse EVE Fittings", pos=wx.DefaultPosition, size=wx.Size( 550,450 ), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) @@ -23,16 +18,21 @@ class CrestFittings(wx.Frame): mainSizer = wx.BoxSizer(wx.VERTICAL) sCrest = service.Crest.getInstance() - self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) - chars = sCrest.getCrestCharacters() - for char in chars: - self.charChoice.Append(char.name, char.ID) - characterSelectSizer = wx.BoxSizer( wx.HORIZONTAL ) - self.charChoice.SetSelection(0) + if sCrest.settings.get('mode') == 0: + self.stLogged = wx.StaticText(self, wx.ID_ANY, "Currently logged in as %s"%sCrest.implicitCharacter.name, wx.DefaultPosition, wx.DefaultSize) + self.stLogged.Wrap( -1 ) + + characterSelectSizer.Add( self.stLogged, 0, wx.ALL, 5 ) + else: + self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) + chars = sCrest.getCrestCharacters() + for char in chars: + self.charChoice.Append(char.name, char.ID) + self.charChoice.SetSelection(0) + characterSelectSizer.Add( self.charChoice, 1, wx.ALL, 5 ) - characterSelectSizer.Add( self.charChoice, 1, wx.ALL, 5 ) self.fetchBtn = wx.Button( self, wx.ID_ANY, u"Fetch Fits", wx.DefaultPosition, wx.DefaultSize, 5 ) characterSelectSizer.Add( self.fetchBtn, 0, wx.ALL, 5 ) mainSizer.Add( characterSelectSizer, 0, wx.EXPAND, 5 ) @@ -59,19 +59,31 @@ class CrestFittings(wx.Frame): self.fetchBtn.Bind(wx.EVT_BUTTON, self.fetchFittings) self.importBtn.Bind(wx.EVT_BUTTON, self.importFitting) + pub.subscribe(self.ssoLogout, 'logout_success') + self.SetSizer(mainSizer) self.Layout() self.Centre(wx.BOTH) + def ssoLogout(self, message): + self.Close() + def getActiveCharacter(self): + sCrest = service.Crest.getInstance() + + if sCrest.settings.get('mode') == 0: + return sCrest.implicitCharacter.ID + selection = self.charChoice.GetCurrentSelection() return self.charChoice.GetClientData(selection) if selection is not None else None def fetchFittings(self, event): sCrest = service.Crest.getInstance() + waitDialog = wx.BusyInfo("Fetching fits, please wait...", parent=self) fittings = sCrest.getFittings(self.getActiveCharacter()) self.fitTree.populateSkillTree(fittings) + del waitDialog def importFitting(self, event): selection = self.fitView.fitSelection @@ -84,22 +96,29 @@ class CrestFittings(wx.Frame): class ExportToEve(wx.Frame): def __init__(self, parent): - wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=wx.DefaultPosition, size=(wx.Size(500,100)), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="Export fit to EVE", pos=wx.DefaultPosition, size=(wx.Size(350,100)), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) self.mainFrame = parent self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) sCrest = service.Crest.getInstance() + mainSizer = wx.BoxSizer(wx.HORIZONTAL) - self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) - chars = sCrest.getCrestCharacters() - for char in chars: - self.charChoice.Append(char.name, char.ID) + if sCrest.settings.get('mode') == 0: + self.stLogged = wx.StaticText(self, wx.ID_ANY, "Currently logged in as %s"%sCrest.implicitCharacter.name, wx.DefaultPosition, wx.DefaultSize) + self.stLogged.Wrap( -1 ) - mainSizer = wx.BoxSizer( wx.HORIZONTAL ) - self.charChoice.SetSelection(0) + mainSizer.Add( self.stLogged, 0, wx.ALL, 5 ) + else: + self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) + chars = sCrest.getCrestCharacters() + for char in chars: + self.charChoice.Append(char.name, char.ID) + self.charChoice.SetSelection(0) + mainSizer.Add( self.charChoice, 1, wx.ALL, 5 ) + + self.charChoice.SetSelection(0) - mainSizer.Add( self.charChoice, 1, wx.ALL, 5 ) self.exportBtn = wx.Button( self, wx.ID_ANY, u"Export Fit", wx.DefaultPosition, wx.DefaultSize, 5 ) mainSizer.Add( self.exportBtn, 0, wx.ALL, 5 ) @@ -109,6 +128,7 @@ class ExportToEve(wx.Frame): self.statusbar.SetFieldsCount(2) self.statusbar.SetStatusWidths([100, -1]) + pub.subscribe(self.ssoLogout, 'logout_success') self.SetSizer(mainSizer) self.SetStatusBar(self.statusbar) @@ -116,7 +136,15 @@ class ExportToEve(wx.Frame): self.Centre(wx.BOTH) + def ssoLogout(self, message): + self.Close() + def getActiveCharacter(self): + sCrest = service.Crest.getInstance() + + if sCrest.settings.get('mode') == 0: + return sCrest.implicitCharacter.ID + selection = self.charChoice.GetCurrentSelection() return self.charChoice.GetClientData(selection) if selection is not None else None @@ -177,7 +205,9 @@ class CrestCharacterInfo(wx.Dialog): self.pic.SetBitmap(wx.ImageFromStream(self.char.img).ConvertToBitmap()) self.Layout() self.bitmapSet = True - self.coutdownText.SetLabel(time.strftime("%H:%M:%S", t)) + newLabel = time.strftime("%H:%M:%S", t if t >= 0 else 0) + if self.coutdownText.Label != newLabel: + self.coutdownText.SetLabel(time.strftime("%H:%M:%S", t)) def logout(self, event): sCrest = service.Crest.getInstance() @@ -220,7 +250,6 @@ class FittingsTreeView(wx.Panel): shipID = tree.AppendItem(root, name) for fit in fits: fitId = tree.AppendItem(shipID, fit['name']) - print type(fit) tree.SetPyData(fitId, json.dumps(fit)) tree.SortChildren(root) @@ -237,6 +266,7 @@ class FittingsTreeView(wx.Panel): list.append(cargo) except: pass + self.parent.fitView.fitSelection = selection self.parent.fitView.update(list) @@ -246,6 +276,3 @@ class FitView(d.Display): def __init__(self, parent): d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL) - self.fitSelection = None - #self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem) - #self.Bind(wx.EVT_KEY_UP, self.kbEvent) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 7c58c1853..6dbfed3aa 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -496,15 +496,17 @@ class MainFrame(wx.Frame): def showCharacterMgmt(self, type): if type == 0: - print "login type is implicit" dlg=CrestCharacterInfo(self) dlg.Show() def ssoLogin(self, event): sCrest = service.Crest.getInstance() if sCrest.settings.get('mode') == 0: # Implicit, go directly to login - uri = sCrest.startServer() - wx.LaunchDefaultBrowser(uri) + if sCrest.implicitCharacter is not None: + self.showCharacterMgmt(type=0) + else: + uri = sCrest.startServer() + wx.LaunchDefaultBrowser(uri) else: dlg=CrestCharacterInfo(self) dlg.Show() diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 1af6e88e5..924c82540 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -25,6 +25,8 @@ import gui.graphFrame import gui.globalEvents as GE import service +from wx.lib.pubsub import setupkwargs +from wx.lib.pubsub import pub class MainMenuBar(wx.MenuBar): def __init__(self): @@ -47,6 +49,8 @@ class MainMenuBar(wx.MenuBar): self.mainFrame = gui.mainFrame.MainFrame.getInstance() + self.sCrest = service.Crest.getInstance() + wx.MenuBar.__init__(self) # File menu @@ -109,7 +113,10 @@ class MainMenuBar(wx.MenuBar): # CREST Menu crestMenu = wx.Menu() self.Append(crestMenu, "&CREST") - crestMenu.Append(self.ssoLoginId, "Login To EVE") + if self.sCrest.settings.get('mode') != 0: + crestMenu.Append(self.ssoLoginId, "Manage Characters") + else: + crestMenu.Append(self.ssoLoginId, "Login to EVE") crestMenu.Append(self.eveFittingsId, "Browse EVE Fittings") crestMenu.Append(self.exportToEveId, "Export To EVE") @@ -125,6 +132,8 @@ class MainMenuBar(wx.MenuBar): helpMenu.Append( self.mainFrame.widgetInspectMenuID, "Open Widgets Inspect tool", "Open Widgets Inspect tool") self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) + pub.subscribe(self.ssoLogin, 'login_success') + pub.subscribe(self.ssoLogout, 'logout_success') def fitChanged(self, event): enable = event.fitID is not None @@ -142,3 +151,11 @@ class MainMenuBar(wx.MenuBar): self.Enable(self.revertCharId, char.isDirty) event.Skip() + + def ssoLogin(self, type): + if self.sCrest.settings.get('mode') == 0: + self.SetLabel(self.ssoLoginId, "Character Info") + + def ssoLogout(self, message): + if self.sCrest.settings.get('mode') == 0: + self.SetLabel(self.ssoLoginId, "Login to EVE") diff --git a/gui/utils/repeatedTimer.py b/gui/utils/repeatedTimer.py new file mode 100644 index 000000000..be141156b --- /dev/null +++ b/gui/utils/repeatedTimer.py @@ -0,0 +1,25 @@ +from threading import Timer + +class RepeatedTimer(object): + def __init__(self, interval, function, *args, **kwargs): + self._timer = None + self.interval = interval + self.function = function + self.args = args + self.kwargs = kwargs + self.is_running = False + + def _run(self): + self.is_running = False + self.start() + self.function(*self.args, **self.kwargs) + + def start(self): + if not self.is_running: + self._timer = Timer(self.interval, self._run) + self._timer.start() + self.is_running = True + + def stop(self): + self._timer.cancel() + self.is_running = False diff --git a/pycrest/eve.py b/pycrest/eve.py index d74f89567..0e1ec412e 100644 --- a/pycrest/eve.py +++ b/pycrest/eve.py @@ -271,7 +271,7 @@ class AuthedConnection(EVE): return self # for backwards compatibility def get(self, resource, params=None): - if int(time.time()) >= self.expires: + if self.refresh_token and int(time.time()) >= self.expires: self.refresh() return super(self.__class__, self).get(resource, params) diff --git a/service/crest.py b/service/crest.py index c37eb1f53..d52fe27e2 100644 --- a/service/crest.py +++ b/service/crest.py @@ -5,6 +5,7 @@ import copy import service from service.server import * import uuid +from gui.utils.repeatedTimer import RepeatedTimer from wx.lib.pubsub import setupkwargs from wx.lib.pubsub import pub @@ -31,6 +32,8 @@ class Crest(): self.scopes = ['characterFittingsRead', 'characterFittingsWrite'] self.state = None + self.ssoTimer = RepeatedTimer(1, self.logout) + # Base EVE connection that is copied to all characters self.eve = pycrest.EVE( client_id=self.settings.get('clientID'), @@ -55,6 +58,11 @@ class Crest(): ''' Get character, and modify to include the eve connection ''' + if self.settings.get('mode') == 0: + if self.implicitCharacter.ID != charID: + raise ValueError("CharacterID does not match currently logged in character.") + return self.implicitCharacter + char = eos.db.getCrestCharacter(charID) if not hasattr(char, "eve"): char.eve = copy.copy(self.eve) @@ -78,6 +86,8 @@ class Crest(): def logout(self): self.implicitCharacter = None + self.ssoTimer.stop() + wx.CallAfter(pub.sendMessage, 'logout_success', message=None) def startServer(self): thread.start_new_thread(self.httpd.serve, ()) @@ -89,6 +99,7 @@ class Crest(): return if message['state'][0] != self.state: + print "state mismatch" return print "handling login by making characters and stuff" @@ -100,13 +111,17 @@ class Crest(): access_token=message['access_token'][0], expires_in=int(message['expires_in'][0]) ) + self.ssoTimer.interval = int(message['expires_in'][0]) + self.ssoTimer.start() + eve() info = eve.whoami() self.implicitCharacter = CrestUser(info['CharacterID'], info['CharacterName']) self.implicitCharacter.eve = eve self.implicitCharacter.fetchImage() - wx.CallAfter(pub.sendMessage, 'login_success', type=0) + print self.implicitCharacter.eve, self.implicitCharacter.eve.refresh_token + wx.CallAfter(pub.sendMessage, 'login_success', type=0) elif 'code' in message: print "handle authentication code" diff --git a/service/port.py b/service/port.py index 6951a3765..fc7d308b7 100644 --- a/service/port.py +++ b/service/port.py @@ -54,8 +54,9 @@ class Port(object): nested_dict = lambda: collections.defaultdict(nested_dict) fit = nested_dict() + sCrest = service.Crest.getInstance() + eve = sCrest.eve - eve = config.pycrest_eve # max length is 50 characters name = ofit.name[:47] + '...' if len(ofit.name) > 50 else ofit.name fit['name'] = name From 08382db0115a3d607b16731eb79a6f69850beafc Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 23 Oct 2015 21:26:38 -0400 Subject: [PATCH 16/41] Add character management window, ability to add and delete crest characters, and fix issue with not having default client ID available. --- config.py | 2 + gui/crestFittings.py | 106 ++++++++++++++++++++++++++++++++++++++----- gui/mainFrame.py | 4 +- service/crest.py | 37 +++++++++------ service/html.py | 2 - 5 files changed, 123 insertions(+), 28 deletions(-) diff --git a/config.py b/config.py index cdab4bffa..e44283923 100644 --- a/config.py +++ b/config.py @@ -25,6 +25,8 @@ expansionName = "Vanguard" expansionVersion = "1.0" evemonMinVersion = "4081" +clientID = 'af87365240d644f7950af563b8418bad' + pyfaPath = None savePath = None saveDB = None diff --git a/gui/crestFittings.py b/gui/crestFittings.py index 39af6ae4d..2438ea39d 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -1,11 +1,14 @@ import wx +import json + +from wx.lib.pubsub import setupkwargs +from wx.lib.pubsub import pub import service import gui.display as d from eos.types import Cargo from eos.db import getItem import time -import json class CrestFittings(wx.Frame): @@ -27,11 +30,8 @@ class CrestFittings(wx.Frame): characterSelectSizer.Add( self.stLogged, 0, wx.ALL, 5 ) else: self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) - chars = sCrest.getCrestCharacters() - for char in chars: - self.charChoice.Append(char.name, char.ID) - self.charChoice.SetSelection(0) characterSelectSizer.Add( self.charChoice, 1, wx.ALL, 5 ) + self.updateCharList() self.fetchBtn = wx.Button( self, wx.ID_ANY, u"Fetch Fits", wx.DefaultPosition, wx.DefaultSize, 5 ) characterSelectSizer.Add( self.fetchBtn, 0, wx.ALL, 5 ) @@ -66,6 +66,18 @@ class CrestFittings(wx.Frame): self.Centre(wx.BOTH) + def updateCharList(self): + sCrest = service.Crest.getInstance() + chars = sCrest.getCrestCharacters() + + if len(chars) == 0: + self.Close() + + for char in chars: + self.charChoice.Append(char.name, char.ID) + + self.charChoice.SetSelection(0) + def ssoLogout(self, message): self.Close() @@ -92,7 +104,6 @@ class CrestFittings(wx.Frame): fits = sFit.importFitFromBuffer(data) self.mainFrame._openAfterImport(fits) - class ExportToEve(wx.Frame): def __init__(self, parent): @@ -111,12 +122,8 @@ class ExportToEve(wx.Frame): mainSizer.Add( self.stLogged, 0, wx.ALL, 5 ) else: self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) - chars = sCrest.getCrestCharacters() - for char in chars: - self.charChoice.Append(char.name, char.ID) - self.charChoice.SetSelection(0) mainSizer.Add( self.charChoice, 1, wx.ALL, 5 ) - + self.updateCharList() self.charChoice.SetSelection(0) self.exportBtn = wx.Button( self, wx.ID_ANY, u"Export Fit", wx.DefaultPosition, wx.DefaultSize, 5 ) @@ -136,6 +143,18 @@ class ExportToEve(wx.Frame): self.Centre(wx.BOTH) + def updateCharList(self): + sCrest = service.Crest.getInstance() + chars = sCrest.getCrestCharacters() + + if len(chars) == 0: + self.Close() + + for char in chars: + self.charChoice.Append(char.name, char.ID) + + self.charChoice.SetSelection(0) + def ssoLogout(self, message): self.Close() @@ -214,6 +233,71 @@ class CrestCharacterInfo(wx.Dialog): sCrest.logout() self.Close() +class CrestMgmt(wx.Dialog): + + def __init__( self, parent ): + wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = "CREST Character Management", pos = wx.DefaultPosition, size = wx.Size( 550,250 ), style = wx.DEFAULT_DIALOG_STYLE ) + + mainSizer = wx.BoxSizer( wx.HORIZONTAL ) + + self.lcCharacters = wx.ListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT) + + self.lcCharacters.InsertColumn(0, heading='Character') + self.lcCharacters.InsertColumn(1, heading='Refresh Token') + + self.popCharList() + + mainSizer.Add( self.lcCharacters, 1, wx.ALL|wx.EXPAND, 5 ) + + btnSizer = wx.BoxSizer( wx.VERTICAL ) + + self.addBtn = wx.Button( self, wx.ID_ANY, u"Add Character", wx.DefaultPosition, wx.DefaultSize, 0 ) + btnSizer.Add( self.addBtn, 0, wx.ALL | wx.EXPAND, 5 ) + + self.deleteBtn = wx.Button( self, wx.ID_ANY, u"Revoke Character", wx.DefaultPosition, wx.DefaultSize, 0 ) + btnSizer.Add( self.deleteBtn, 0, wx.ALL | wx.EXPAND, 5 ) + + mainSizer.Add( btnSizer, 0, wx.EXPAND, 5 ) + + self.addBtn.Bind(wx.EVT_BUTTON, self.addChar) + self.deleteBtn.Bind(wx.EVT_BUTTON, self.delChar) + + pub.subscribe(self.ssoLogin, 'login_success') + + self.SetSizer( mainSizer ) + self.Layout() + + self.Centre( wx.BOTH ) + + def ssoLogin(self, type): + self.popCharList() + + def popCharList(self): + sCrest = service.Crest.getInstance() + chars = sCrest.getCrestCharacters() + + self.lcCharacters.DeleteAllItems() + + for index, char in enumerate(chars): + self.lcCharacters.InsertStringItem(index, char.name) + self.lcCharacters.SetStringItem(index, 1, char.refresh_token) + self.lcCharacters.SetItemData(index, char.ID) + + self.lcCharacters.SetColumnWidth(0, wx.LIST_AUTOSIZE) + self.lcCharacters.SetColumnWidth(1, wx.LIST_AUTOSIZE) + + def addChar(self, event): + sCrest = service.Crest.getInstance() + uri = sCrest.startServer() + wx.LaunchDefaultBrowser(uri) + + def delChar(self, event): + item = self.lcCharacters.GetFirstSelected() + charID = self.lcCharacters.GetItemData(item) + sCrest = service.Crest.getInstance() + sCrest.delCrestCharacter(charID) + self.popCharList() + class FittingsTreeView(wx.Panel): def __init__(self, parent): wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, style=wx.TAB_TRAVERSAL) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 6dbfed3aa..5bc85793b 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -44,7 +44,7 @@ from gui.multiSwitch import MultiSwitch from gui.statsPane import StatsPane from gui.shipBrowser import ShipBrowser, FitSelected, ImportSelected, Stage3Selected from gui.characterEditor import CharacterEditor, SaveCharacterAs -from gui.crestFittings import CrestFittings, ExportToEve, CrestCharacterInfo +from gui.crestFittings import CrestFittings, ExportToEve, CrestCharacterInfo, CrestMgmt from gui.characterSelection import CharacterSelection from gui.patternEditor import DmgPatternEditorDlg from gui.resistsEditor import ResistsEditorDlg @@ -508,7 +508,7 @@ class MainFrame(wx.Frame): uri = sCrest.startServer() wx.LaunchDefaultBrowser(uri) else: - dlg=CrestCharacterInfo(self) + dlg=CrestMgmt(self) dlg.Show() def exportToEve(self, event): diff --git a/service/crest.py b/service/crest.py index d52fe27e2..8863433a4 100644 --- a/service/crest.py +++ b/service/crest.py @@ -5,13 +5,12 @@ import copy import service from service.server import * import uuid +import config from gui.utils.repeatedTimer import RepeatedTimer from wx.lib.pubsub import setupkwargs from wx.lib.pubsub import pub -# TODO: -# With implicit grant, make sure we know when it expires and delete/inactive char class Crest(): # @todo: move this to settings @@ -36,7 +35,7 @@ class Crest(): # Base EVE connection that is copied to all characters self.eve = pycrest.EVE( - client_id=self.settings.get('clientID'), + client_id=self.settings.get('clientID') if self.settings.get('mode') == 1 else config.clientID, api_key=self.settings.get('clientSecret') if self.settings.get('mode') == 1 else None, redirect_uri=self.clientCallback, testing=self.clientTest) @@ -44,6 +43,10 @@ class Crest(): self.implicitCharacter = None pub.subscribe(self.handleLogin, 'sso_login') + def delCrestCharacter(self, charID): + char = eos.db.getCrestCharacter(charID) + eos.db.remove(char) + def getCrestCharacters(self): chars = eos.db.getCrestCharacters() for char in chars: @@ -52,6 +55,9 @@ class Crest(): # Give EVE instance refresh info. This allows us to set it # without actually making the request to authorize at this time. char.eve.temptoken_authorize(refresh_token=char.refresh_token) + + wx.CallAfter(pub.sendMessage, 'crest_delete', message=None) + return chars def getCrestCharacter(self, charID): @@ -64,7 +70,7 @@ class Crest(): return self.implicitCharacter char = eos.db.getCrestCharacter(charID) - if not hasattr(char, "eve"): + if char and not hasattr(char, "eve"): char.eve = copy.copy(self.eve) char.eve.temptoken_authorize(refresh_token=char.refresh_token) return char @@ -78,12 +84,6 @@ class Crest(): res = char.eve._session.post('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID, data=json) return res - def newChar(self, connection): - connection() - info = connection.whoami() - char = CrestUser(info['CharacterName'], info['CharacterID'], connection.refresh_token) - eos.db.save(char) - def logout(self): self.implicitCharacter = None self.ssoTimer.stop() @@ -119,13 +119,24 @@ class Crest(): self.implicitCharacter = CrestUser(info['CharacterID'], info['CharacterName']) self.implicitCharacter.eve = eve self.implicitCharacter.fetchImage() - print self.implicitCharacter.eve, self.implicitCharacter.eve.refresh_token wx.CallAfter(pub.sendMessage, 'login_success', type=0) elif 'code' in message: - print "handle authentication code" + eve = copy.copy(self.eve) + eve.authorize(message['code'][0]) + eve() + info = eve.whoami() - #wx.CallAfter(pub.sendMessage, 'login_success', type=1) + # 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 = CrestUser(info['CharacterID'], info['CharacterName'], eve.refresh_token) + char.eve = eve + eos.db.save(char) + + wx.CallAfter(pub.sendMessage, 'login_success', type=1) diff --git a/service/html.py b/service/html.py index b073f7803..7f8c17fa3 100644 --- a/service/html.py +++ b/service/html.py @@ -6,8 +6,6 @@ HTML = ''' Done. Please close this window. - -(will put more interesting messages here later) - - -''' diff --git a/service/server.py b/service/server.py index 7423bdf64..4410c492b 100644 --- a/service/server.py +++ b/service/server.py @@ -1,4 +1,4 @@ -import SimpleHTTPServer, BaseHTTPServer +import BaseHTTPServer import urlparse import socket import thread @@ -7,12 +7,36 @@ import wx from wx.lib.pubsub import setupkwargs from wx.lib.pubsub import pub -from html import HTML - import logging logger = logging.getLogger(__name__) +HTML = ''' + + + +Done. Please close this window. + + + +''' + # https://github.com/fuzzysteve/CREST-Market-Downloader/ class AuthHandler(BaseHTTPServer.BaseHTTPRequestHandler): def do_GET(self): From 1ce2921eb74054a74f9c4868b792119c88b3215c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 25 Oct 2015 18:14:12 -0400 Subject: [PATCH 27/41] Put current character info in titlebar, and disable getting image. --- eos/saveddata/crest.py | 5 +++-- gui/mainFrame.py | 30 +++++++++++++++++++++++------- gui/mainMenuBar.py | 2 +- service/crest.py | 2 +- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/eos/saveddata/crest.py b/eos/saveddata/crest.py index ccb00b1f4..4cc178a79 100644 --- a/eos/saveddata/crest.py +++ b/eos/saveddata/crest.py @@ -21,7 +21,7 @@ import urllib from cStringIO import StringIO from sqlalchemy.orm import reconstructor -from tomorrow import threads +#from tomorrow import threads class Crest(object): @@ -35,6 +35,7 @@ class Crest(object): def init(self): pass + ''' @threads(1) def fetchImage(self): url = 'https://image.eveonline.com/character/%d_128.jpg'%self.ID @@ -42,4 +43,4 @@ class Crest(object): data = fp.read() fp.close() self.img = StringIO(data) - + ''' diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 5bc85793b..bbed57562 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -103,8 +103,8 @@ class MainFrame(wx.Frame): return cls.__instance if cls.__instance is not None else MainFrame() def __init__(self): - title="pyfa %s%s - Python Fitting Assistant"%(config.version, "" if config.tag.lower() != 'git' else " (git)") - wx.Frame.__init__(self, None, wx.ID_ANY, title) + self.title="pyfa %s%s - Python Fitting Assistant"%(config.version, "" if config.tag.lower() != 'git' else " (git)") + wx.Frame.__init__(self, None, wx.ID_ANY, self.title) MainFrame.__instance = self @@ -195,7 +195,11 @@ class MainFrame(wx.Frame): self.sUpdate = service.Update.getInstance() self.sUpdate.CheckUpdate(self.ShowUpdateBox) - pub.subscribe(self.showCharacterMgmt, 'login_success') + pub.subscribe(self.onSSOLogin, 'login_success') + pub.subscribe(self.onSSOLogout, 'logout_success') + + self.titleTimer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.updateTitle, self.titleTimer) def ShowUpdateBox(self, release): dlg = UpdateDialog(self, release) @@ -494,16 +498,28 @@ class MainFrame(wx.Frame): dlg=CrestFittings(self) dlg.Show() - def showCharacterMgmt(self, type): + def updateTitle(self, event): + sCrest = service.Crest.getInstance() + char = sCrest.implicitCharacter + if char: + t = time.gmtime(char.eve.expires-time.time()) + sTime = time.strftime("%H:%M:%S", t if t >= 0 else 0) + newTitle = "%s | %s - %s"%(self.title, char.name, sTime) + self.SetTitle(newTitle) + + def onSSOLogin(self, type): if type == 0: - dlg=CrestCharacterInfo(self) - dlg.Show() + self.titleTimer.Start(1000) + + def onSSOLogout(self, message): + self.titleTimer.Stop() + self.SetTitle(self.title) def ssoLogin(self, event): sCrest = service.Crest.getInstance() if sCrest.settings.get('mode') == 0: # Implicit, go directly to login if sCrest.implicitCharacter is not None: - self.showCharacterMgmt(type=0) + sCrest.logout() else: uri = sCrest.startServer() wx.LaunchDefaultBrowser(uri) diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 924c82540..6940d3cc6 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -154,7 +154,7 @@ class MainMenuBar(wx.MenuBar): def ssoLogin(self, type): if self.sCrest.settings.get('mode') == 0: - self.SetLabel(self.ssoLoginId, "Character Info") + self.SetLabel(self.ssoLoginId, "Logout Character") def ssoLogout(self, message): if self.sCrest.settings.get('mode') == 0: diff --git a/service/crest.py b/service/crest.py index c0081f15a..8c86d1ba6 100644 --- a/service/crest.py +++ b/service/crest.py @@ -136,7 +136,7 @@ class Crest(): self.implicitCharacter = CrestUser(info['CharacterID'], info['CharacterName']) self.implicitCharacter.eve = eve - self.implicitCharacter.fetchImage() + #self.implicitCharacter.fetchImage() wx.CallAfter(pub.sendMessage, 'login_success', type=0) elif 'code' in message: From 781abeea5307a85488c8f449b8c6a4e255bd7fb6 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 25 Oct 2015 19:41:12 -0400 Subject: [PATCH 28/41] Implement a character cache, as the database-layer cache is seemingly not working. =/ --- eos/db/saveddata/crest.py | 4 ++-- eos/db/saveddata/queries.py | 12 ++++++------ eos/saveddata/{crest.py => crestchar.py} | 2 +- eos/types.py | 2 +- gui/crestFittings.py | 3 ++- service/crest.py | 25 ++++++++++++------------ 6 files changed, 25 insertions(+), 23 deletions(-) rename eos/saveddata/{crest.py => crestchar.py} (98%) diff --git a/eos/db/saveddata/crest.py b/eos/db/saveddata/crest.py index af8fba88e..0934f177e 100644 --- a/eos/db/saveddata/crest.py +++ b/eos/db/saveddata/crest.py @@ -21,11 +21,11 @@ from sqlalchemy import Table, Column, Integer, String, Boolean from sqlalchemy.orm import mapper from eos.db import saveddata_meta -from eos.types import Crest +from eos.types import CrestChar crest_table = Table("crest", saveddata_meta, Column("ID", Integer, primary_key = True), Column("name", String, nullable = False, unique = True), Column("refresh_token", String, nullable = False)) -mapper(Crest, crest_table) +mapper(CrestChar, crest_table) diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py index c467153c3..5c58a8501 100644 --- a/eos/db/saveddata/queries.py +++ b/eos/db/saveddata/queries.py @@ -19,7 +19,7 @@ from eos.db.util import processEager, processWhere from eos.db import saveddata_session, sd_lock -from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad, TargetResists, Crest +from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad, TargetResists, CrestChar from eos.db.saveddata.fleet import squadmembers_table from eos.db.saveddata.fit import projectedFits_table from sqlalchemy.sql import and_ @@ -419,23 +419,23 @@ def getProjectedFits(fitID): def getCrestCharacters(eager=None): eager = processEager(eager) with sd_lock: - characters = saveddata_session.query(Crest).options(*eager).all() + characters = saveddata_session.query(CrestChar).options(*eager).all() return characters -@cachedQuery(Crest, 1, "lookfor") +@cachedQuery(CrestChar, 1, "lookfor") def getCrestCharacter(lookfor, eager=None): if isinstance(lookfor, int): if eager is None: with sd_lock: - character = saveddata_session.query(Crest).get(lookfor) + character = saveddata_session.query(CrestChar).get(lookfor) else: eager = processEager(eager) with sd_lock: - character = saveddata_session.query(Crest).options(*eager).filter(Crest.ID == lookfor).first() + character = saveddata_session.query(CrestChar).options(*eager).filter(CrestChar.ID == lookfor).first() elif isinstance(lookfor, basestring): eager = processEager(eager) with sd_lock: - character = saveddata_session.query(Crest).options(*eager).filter(Crest.name == lookfor).first() + character = saveddata_session.query(CrestChar).options(*eager).filter(CrestChar.name == lookfor).first() else: raise TypeError("Need integer or string as argument") return character diff --git a/eos/saveddata/crest.py b/eos/saveddata/crestchar.py similarity index 98% rename from eos/saveddata/crest.py rename to eos/saveddata/crestchar.py index 4cc178a79..9fa78f551 100644 --- a/eos/saveddata/crest.py +++ b/eos/saveddata/crestchar.py @@ -24,7 +24,7 @@ from sqlalchemy.orm import reconstructor #from tomorrow import threads -class Crest(object): +class CrestChar(object): def __init__(self, id, name, refresh_token=None): self.ID = id diff --git a/eos/types.py b/eos/types.py index e4f1fd5e9..7bd2d8c7e 100644 --- a/eos/types.py +++ b/eos/types.py @@ -21,7 +21,7 @@ from eos.gamedata import Attribute, Category, Effect, Group, Icon, Item, MarketG MetaGroup, AttributeInfo, Unit, EffectInfo, MetaType, MetaData, Traits from eos.saveddata.price import Price from eos.saveddata.user import User -from eos.saveddata.crest import Crest +from eos.saveddata.crestchar import CrestChar from eos.saveddata.damagePattern import DamagePattern from eos.saveddata.targetResists import TargetResists from eos.saveddata.character import Character, Skill diff --git a/gui/crestFittings.py b/gui/crestFittings.py index e322ae199..cbfc53034 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -9,6 +9,7 @@ import gui.display as d from eos.types import Cargo from eos.db import getItem import time +import webbrowser class CrestFittings(wx.Frame): @@ -292,7 +293,7 @@ class CrestMgmt(wx.Dialog): def addChar(self, event): sCrest = service.Crest.getInstance() uri = sCrest.startServer() - wx.LaunchDefaultBrowser(uri) + webbrowser.open(uri) def delChar(self, event): item = self.lcCharacters.GetFirstSelected() diff --git a/service/crest.py b/service/crest.py index 8c86d1ba6..82ca47b69 100644 --- a/service/crest.py +++ b/service/crest.py @@ -4,7 +4,7 @@ import uuid from wx.lib.pubsub import pub import eos.db -from eos.types import Crest as CrestUser +from eos.types import CrestChar from service import pycrest import service from service.server import * @@ -46,23 +46,19 @@ class Crest(): testing=self.clientTest) self.implicitCharacter = None + + # The database cache does not seem to be working for some reason. Use + # this as a temporary measure + self.charCache = {} pub.subscribe(self.handleLogin, 'sso_login') def delCrestCharacter(self, charID): char = eos.db.getCrestCharacter(charID) eos.db.remove(char) + wx.CallAfter(pub.sendMessage, 'crest_delete', message=None) def getCrestCharacters(self): chars = eos.db.getCrestCharacters() - for char in chars: - if not hasattr(char, "eve"): - char.eve = copy.copy(self.eve) - # Give EVE instance refresh info. This allows us to set it - # without actually making the request to authorize at this time. - char.eve.temptoken_authorize(refresh_token=char.refresh_token) - - wx.CallAfter(pub.sendMessage, 'crest_delete', message=None) - return chars def getCrestCharacter(self, charID): @@ -74,10 +70,14 @@ class Crest(): 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.copy(self.eve) char.eve.temptoken_authorize(refresh_token=char.refresh_token) + self.charCache[charID] = char return char def getFittings(self, charID): @@ -134,7 +134,7 @@ class Crest(): logger.debug("Got character info: %s" % info) - self.implicitCharacter = CrestUser(info['CharacterID'], info['CharacterName']) + self.implicitCharacter = CrestChar(info['CharacterID'], info['CharacterName']) self.implicitCharacter.eve = eve #self.implicitCharacter.fetchImage() @@ -152,8 +152,9 @@ class Crest(): if char: char.refresh_token = eve.refresh_token else: - char = CrestUser(info['CharacterID'], info['CharacterName'], eve.refresh_token) + char = CrestChar(info['CharacterID'], info['CharacterName'], eve.refresh_token) char.eve = eve + self.charCache[int(info['CharacterID'])] = char eos.db.save(char) wx.CallAfter(pub.sendMessage, 'login_success', type=1) From 6ee61862802a9b7c4c6b9b902a628b11d312db67 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 25 Oct 2015 19:51:58 -0400 Subject: [PATCH 29/41] Make a post thing for pycrest. Still having authentication errors occasionally. --- service/crest.py | 2 +- service/pycrest/eve.py | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/service/crest.py b/service/crest.py index 82ca47b69..47a900773 100644 --- a/service/crest.py +++ b/service/crest.py @@ -86,7 +86,7 @@ class Crest(): def postFitting(self, charID, json): char = self.getCrestCharacter(charID) - res = char.eve._session.post('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID, data=json) + res = char.eve.post('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID, data=json) return res def logout(self): diff --git a/service/pycrest/eve.py b/service/pycrest/eve.py index e56bfc927..51b25970c 100644 --- a/service/pycrest/eve.py +++ b/service/pycrest/eve.py @@ -265,18 +265,15 @@ class AuthedConnection(EVE): # self._cache['whoami'] = self.get("%s/verify" % self._oauth_endpoint) return self.get("%s/verify" % self._oauth_endpoint) - def refresh(self): - res = self._authorize(params={"grant_type": "refresh_token", "refresh_token": self.refresh_token}) - self.token = res['access_token'] - self.expires = int(time.time()) + res['expires_in'] - self._session.headers.update({"Authorization": "Bearer %s" % self.token}) - return self # for backwards compatibility - def get(self, resource, params=None): if self.refresh_token and int(time.time()) >= self.expires: - self.refresh() + 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) class APIObject(object): def __init__(self, parent, connection): From cb0003b942852614f7ee1089fb2fe533e86472ec Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 25 Oct 2015 20:17:42 -0400 Subject: [PATCH 30/41] Use deepcopy instead of copy. Fixes issue with using same session object across characters --- service/crest.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/service/crest.py b/service/crest.py index 47a900773..c2a1c65fa 100644 --- a/service/crest.py +++ b/service/crest.py @@ -75,7 +75,7 @@ class Crest(): char = eos.db.getCrestCharacter(charID) if char and not hasattr(char, "eve"): - char.eve = copy.copy(self.eve) + char.eve = copy.deepcopy(self.eve) char.eve.temptoken_authorize(refresh_token=char.refresh_token) self.charCache[charID] = char return char @@ -86,8 +86,7 @@ class Crest(): def postFitting(self, charID, json): char = self.getCrestCharacter(charID) - res = char.eve.post('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID, data=json) - return res + return char.eve.post('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID, data=json) def logout(self): logging.debug("Character logout") @@ -121,7 +120,7 @@ class Crest(): logger.debug("Handling CREST login with: %s"%message) if 'access_token' in message: # implicit - eve = copy.copy(self.eve) + eve = copy.deepcopy(self.eve) eve.temptoken_authorize( access_token=message['access_token'][0], expires_in=int(message['expires_in'][0]) @@ -140,7 +139,7 @@ class Crest(): wx.CallAfter(pub.sendMessage, 'login_success', type=0) elif 'code' in message: - eve = copy.copy(self.eve) + eve = copy.deepcopy(self.eve) eve.authorize(message['code'][0]) eve() info = eve.whoami() From 5b341dfc06fda3e50ffd66909e8121010915d2ed Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 25 Oct 2015 21:44:04 -0400 Subject: [PATCH 31/41] pycrest now injects cached until value into the results --- service/pycrest/eve.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/service/pycrest/eve.py b/service/pycrest/eve.py index 51b25970c..b42041f90 100644 --- a/service/pycrest/eve.py +++ b/service/pycrest/eve.py @@ -143,9 +143,9 @@ class APIConnection(object): # check cache key = (resource, frozenset(self._session.headers.items()), frozenset(prms.items())) cached = self.cache.get(key) - if cached and cached['expires'] > time.time(): + if cached and cached['cached_until'] > time.time(): logger.debug('Cache hit for resource %s (params=%s)', resource, prms) - return cached['payload'] + return cached elif cached: logger.debug('Cache stale for resource %s (params=%s)', resource, prms) self.cache.invalidate(key) @@ -160,10 +160,10 @@ class APIConnection(object): ret = res.json() # cache result - key = (resource, frozenset(self._session.headers.items()), frozenset(prms.items())) expires = self._get_expires(res) if expires > 0: - self.cache.put(key, {'expires': time.time() + expires, 'payload': ret}) + ret.update({'cached_until': time.time() + expires}) + self.cache.put(key, ret) return ret From 34f699b96a3aa3ab8c6d91a1b2e9403bafa3c737 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 25 Oct 2015 21:44:24 -0400 Subject: [PATCH 32/41] Use `cached_until` value for GUI countdown --- gui/crestFittings.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/gui/crestFittings.py b/gui/crestFittings.py index cbfc53034..53c7f7d7d 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -62,6 +62,13 @@ class CrestFittings(wx.Frame): pub.subscribe(self.ssoLogout, 'logout_success') + self.statusbar = wx.StatusBar(self) + self.statusbar.SetFieldsCount() + self.SetStatusBar(self.statusbar) + + self.cacheTimer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.updateCacheStatus, self.cacheTimer) + self.SetSizer(mainSizer) self.Layout() @@ -79,6 +86,15 @@ class CrestFittings(wx.Frame): self.charChoice.SetSelection(0) + def updateCacheStatus(self, event): + t = time.gmtime(self.cacheTime-time.time()) + if t < 0: + self.cacheTimer.Stop() + self.statusbar.Hide() + else: + sTime = time.strftime("%H:%M:%S", t) + self.statusbar.SetStatusText("Cached for %s"%sTime, 0) + def ssoLogout(self, message): self.Close() @@ -95,6 +111,9 @@ class CrestFittings(wx.Frame): sCrest = service.Crest.getInstance() waitDialog = wx.BusyInfo("Fetching fits, please wait...", parent=self) fittings = sCrest.getFittings(self.getActiveCharacter()) + self.cacheTime = fittings.get('cached_until') + self.updateCacheStatus(None) + self.cacheTimer.Start(1000) self.fitTree.populateSkillTree(fittings) del waitDialog From b52cbef26f32be6e7efa462b3a4d1a5a6fc919f0 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 25 Oct 2015 21:48:55 -0400 Subject: [PATCH 33/41] Clean up some imports, get rid of CharacterInfo window. --- gui/crestFittings.py | 58 +++++--------------------------------------- service/__init__.py | 2 ++ service/crest.py | 13 +++++----- 3 files changed, 14 insertions(+), 59 deletions(-) diff --git a/gui/crestFittings.py b/gui/crestFittings.py index 53c7f7d7d..7602512db 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -1,5 +1,7 @@ -import wx +import time +import webbrowser import json +import wx from wx.lib.pubsub import setupkwargs from wx.lib.pubsub import pub @@ -8,8 +10,6 @@ import service import gui.display as d from eos.types import Cargo from eos.db import getItem -import time -import webbrowser class CrestFittings(wx.Frame): @@ -124,6 +124,7 @@ class CrestFittings(wx.Frame): fits = sFit.importFitFromBuffer(data) self.mainFrame._openAfterImport(fits) + class ExportToEve(wx.Frame): def __init__(self, parent): @@ -206,55 +207,6 @@ class ExportToEve(wx.Frame): except ValueError: self.statusbar.SetStatusText("", 1) -class CrestCharacterInfo(wx.Dialog): - - def __init__(self, parent): - wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Character Info", pos=wx.DefaultPosition, size = wx.Size( 200,240 )) - self.mainFrame = parent - sCrest = service.Crest.getInstance() - self.char = sCrest.implicitCharacter - self.bitmapSet = False - - mainSizer = wx.BoxSizer( wx.VERTICAL ) - - self.characterText = wx.StaticText(self, wx.ID_ANY, self.char.name, wx.DefaultPosition, wx.DefaultSize) - self.characterText.Wrap( -1 ) - self.characterText.SetFont( wx.Font( 11, 74, 90, 92, False) ) - mainSizer.Add( self.characterText, 0, wx.ALIGN_CENTRE | wx.ALL, 5 ) - - self.pic = wx.StaticBitmap(self, -1, wx.EmptyBitmap(128, 128)) - mainSizer.Add(self.pic, 0, wx.ALIGN_CENTRE | wx.ALL, 5 ) - - self.coutdownText = wx.StaticText( self, wx.ID_ANY, "", wx.DefaultPosition, wx.DefaultSize) - self.coutdownText.Wrap( -1 ) - mainSizer.Add( self.coutdownText, 0, wx.ALIGN_CENTER, 5 ) - - self.logoutBtn = wx.Button( self, wx.ID_ANY, u"Logout", wx.DefaultPosition, wx.DefaultSize, 5 ) - mainSizer.Add( self.logoutBtn, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.logoutBtn.Bind(wx.EVT_BUTTON, self.logout) - - self.SetSizer( mainSizer ) - self.Centre( wx.BOTH ) - - self.timer = wx.Timer(self) - self.Bind(wx.EVT_TIMER, self.update, self.timer) - self.timer.Start(1) - - def update(self, event): - t = time.gmtime(self.char.eve.expires-time.time()) - if not self.bitmapSet and hasattr(self.char, 'img'): - self.pic.SetBitmap(wx.ImageFromStream(self.char.img).ConvertToBitmap()) - self.Layout() - self.bitmapSet = True - newLabel = time.strftime("%H:%M:%S", t if t >= 0 else 0) - if self.coutdownText.Label != newLabel: - self.coutdownText.SetLabel(time.strftime("%H:%M:%S", t)) - - def logout(self, event): - sCrest = service.Crest.getInstance() - sCrest.logout() - self.Close() class CrestMgmt(wx.Dialog): @@ -321,6 +273,7 @@ class CrestMgmt(wx.Dialog): sCrest.delCrestCharacter(charID) self.popCharList() + class FittingsTreeView(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, id=wx.ID_ANY) @@ -377,6 +330,7 @@ class FittingsTreeView(wx.Panel): self.parent.fitView.fitSelection = selection self.parent.fitView.update(list) + class FitView(d.Display): DEFAULT_COLS = ["Base Icon", "Base Name"] diff --git a/service/__init__.py b/service/__init__.py index 00e0e1153..8f5445527 100644 --- a/service/__init__.py +++ b/service/__init__.py @@ -11,3 +11,5 @@ from service.price import Price from service.network import Network from service.eveapi import EVEAPIConnection, ParseXML from service.crest import Crest +from service.server import StoppableHTTPServer, AuthHandler +from service.pycrest import EVE diff --git a/service/crest.py b/service/crest.py index c2a1c65fa..603120c84 100644 --- a/service/crest.py +++ b/service/crest.py @@ -1,3 +1,7 @@ +import thread +import config +import logging +import threading import copy import uuid @@ -5,12 +9,7 @@ from wx.lib.pubsub import pub import eos.db from eos.types import CrestChar -from service import pycrest import service -from service.server import * -import config -import logging -import threading logger = logging.getLogger(__name__) @@ -39,7 +38,7 @@ class Crest(): self.httpdTimer = None # Base EVE connection that is copied to all characters - self.eve = pycrest.EVE( + self.eve = service.pycrest.EVE( client_id=self.settings.get('clientID') if self.settings.get('mode') == 1 else config.clientID, api_key=self.settings.get('clientSecret') if self.settings.get('mode') == 1 else None, redirect_uri=self.clientCallback, @@ -99,7 +98,7 @@ class Crest(): def startServer(self): logging.debug("Starting server") - self.httpd = StoppableHTTPServer(('', 6461), AuthHandler) + self.httpd = service.StoppableHTTPServer(('', 6461), service.AuthHandler) thread.start_new_thread(self.httpd.serve, ()) # keep server going for only 60 seconds From 7032baa7ef198e0ca4ba4f8f3208e276f6175ef0 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sun, 25 Oct 2015 22:02:15 -0400 Subject: [PATCH 34/41] Add some logic to disable from CREST menu items if character is not logged in --- gui/mainFrame.py | 2 +- gui/mainMenuBar.py | 8 ++++++++ service/crest.py | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 6bef5cc4f..f9e2523a7 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -45,7 +45,7 @@ from gui.multiSwitch import MultiSwitch from gui.statsPane import StatsPane from gui.shipBrowser import ShipBrowser, FitSelected, ImportSelected, Stage3Selected from gui.characterEditor import CharacterEditor, SaveCharacterAs -from gui.crestFittings import CrestFittings, ExportToEve, CrestCharacterInfo, CrestMgmt +from gui.crestFittings import CrestFittings, ExportToEve, CrestMgmt from gui.characterSelection import CharacterSelection from gui.patternEditor import DmgPatternEditorDlg from gui.resistsEditor import ResistsEditorDlg diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 6940d3cc6..0b071d534 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -120,6 +120,10 @@ class MainMenuBar(wx.MenuBar): crestMenu.Append(self.eveFittingsId, "Browse EVE Fittings") crestMenu.Append(self.exportToEveId, "Export To EVE") + if self.sCrest.settings.get('mode') == 0 or len(self.sCrest.getCrestCharacters()) == 0: + self.Enable(self.eveFittingsId, False) + self.Enable(self.exportToEveId, False) + # Help menu helpMenu = wx.Menu() self.Append(helpMenu, "&Help") @@ -155,7 +159,11 @@ class MainMenuBar(wx.MenuBar): def ssoLogin(self, type): if self.sCrest.settings.get('mode') == 0: self.SetLabel(self.ssoLoginId, "Logout Character") + self.Enable(self.eveFittingsId, True) + self.Enable(self.exportToEveId, True) def ssoLogout(self, message): if self.sCrest.settings.get('mode') == 0: self.SetLabel(self.ssoLoginId, "Login to EVE") + self.Enable(self.eveFittingsId, False) + self.Enable(self.exportToEveId, False) diff --git a/service/crest.py b/service/crest.py index 603120c84..16ff118eb 100644 --- a/service/crest.py +++ b/service/crest.py @@ -4,6 +4,7 @@ import logging import threading import copy import uuid +import wx from wx.lib.pubsub import pub From b0511ed85631338ecddd78a0225946877b868122 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Mon, 26 Oct 2015 23:54:42 -0400 Subject: [PATCH 35/41] Delete crest chars when changing client settings --- gui/builtinPreferenceViews/pyfaCrestPreferences.py | 13 +++++++------ service/crest.py | 7 +++++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/gui/builtinPreferenceViews/pyfaCrestPreferences.py b/gui/builtinPreferenceViews/pyfaCrestPreferences.py index f359b1ebe..e06e04f7f 100644 --- a/gui/builtinPreferenceViews/pyfaCrestPreferences.py +++ b/gui/builtinPreferenceViews/pyfaCrestPreferences.py @@ -71,10 +71,11 @@ class PFCrestPref ( PreferenceView): fgAddrSizer.Add( self.inputClientSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5 ) - mainSizer.Add( fgAddrSizer, 0, wx.EXPAND, 5) + self.btnApply = wx.Button( panel, wx.ID_ANY, u"Save Client Settings", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.btnApply.Bind(wx.EVT_BUTTON, self.OnBtnApply) - self.inputClientID.Bind(wx.EVT_TEXT, self.OnEditClientID) - self.inputClientSecret.Bind(wx.EVT_TEXT, self.OnEditClientSecret) + mainSizer.Add( fgAddrSizer, 0, wx.EXPAND, 5) + mainSizer.Add( self.btnApply, 0, wx.ALIGN_RIGHT, 5) self.ToggleProxySettings(self.settings.get('mode')) @@ -85,11 +86,11 @@ class PFCrestPref ( PreferenceView): self.settings.set('mode', 0 if self.grantRadioBtn1.Value else 1) self.ToggleProxySettings(self.settings.get('mode')) - def OnEditClientID(self, event): + def OnBtnApply(self, event): self.settings.set('clientID', self.inputClientID.GetValue()) - - def OnEditClientSecret(self, event): self.settings.set('clientSecret', self.inputClientSecret.GetValue()) + sCrest = service.Crest.getInstance() + sCrest.delAllCharacters() def ToggleProxySettings(self, mode): if mode: diff --git a/service/crest.py b/service/crest.py index 16ff118eb..8431132f7 100644 --- a/service/crest.py +++ b/service/crest.py @@ -57,6 +57,13 @@ class Crest(): eos.db.remove(char) wx.CallAfter(pub.sendMessage, 'crest_delete', message=None) + def delAllCharacters(self): + chars = eos.db.getCrestCharacters() + for char in chars: + eos.db.remove(char) + self.charCache = {} + wx.CallAfter(pub.sendMessage, 'crest_delete', message=None) + def getCrestCharacters(self): chars = eos.db.getCrestCharacters() return chars From aedd7ce2dea84b929780cab92a3d74a0ded32e4f Mon Sep 17 00:00:00 2001 From: blitzmann Date: Mon, 26 Oct 2015 21:27:54 -0400 Subject: [PATCH 36/41] Add support to delete fit from EVE --- gui/crestFittings.py | 27 +++++++++++++++++++++++++-- service/crest.py | 4 ++++ service/pycrest/eve.py | 5 +++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/gui/crestFittings.py b/gui/crestFittings.py index 7602512db..4ddfe4580 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -50,15 +50,21 @@ class CrestFittings(wx.Frame): fitSizer = wx.BoxSizer( wx.VERTICAL ) self.fitView = FitView(self) - self.importBtn = wx.Button( self, wx.ID_ANY, u"Import", wx.DefaultPosition, wx.DefaultSize, 5 ) fitSizer.Add( self.fitView, 1, wx.ALL|wx.EXPAND, 5 ) - fitSizer.Add( self.importBtn, 0, wx.ALL|wx.EXPAND, 5 ) + + btnSizer = wx.BoxSizer( wx.HORIZONTAL ) + self.importBtn = wx.Button( self, wx.ID_ANY, u"Import to pyfa", wx.DefaultPosition, wx.DefaultSize, 5 ) + self.deleteBtn = wx.Button( self, wx.ID_ANY, u"Delete from EVE", wx.DefaultPosition, wx.DefaultSize, 5 ) + btnSizer.Add( self.importBtn, 1, wx.ALL, 5 ) + btnSizer.Add( self.deleteBtn, 1, wx.ALL, 5 ) + fitSizer.Add( btnSizer, 0, wx.EXPAND ) contentSizer.Add(fitSizer, 1, wx.EXPAND, 0) mainSizer.Add(contentSizer, 1, wx.EXPAND, 5) self.fetchBtn.Bind(wx.EVT_BUTTON, self.fetchFittings) self.importBtn.Bind(wx.EVT_BUTTON, self.importFitting) + self.deleteBtn.Bind(wx.EVT_BUTTON, self.deleteFitting) pub.subscribe(self.ssoLogout, 'logout_success') @@ -119,11 +125,27 @@ class CrestFittings(wx.Frame): def importFitting(self, event): selection = self.fitView.fitSelection + if not selection: + return data = self.fitTree.fittingsTreeCtrl.GetPyData(selection) sFit = service.Fit.getInstance() fits = sFit.importFitFromBuffer(data) self.mainFrame._openAfterImport(fits) + def deleteFitting(self, event): + sCrest = service.Crest.getInstance() + selection = self.fitView.fitSelection + if not selection: + return + data = json.loads(self.fitTree.fittingsTreeCtrl.GetPyData(selection)) + + dlg = wx.MessageDialog(self, + "Do you really want to delete %s (%s) from EVE?"%(data['name'], data['ship']['name']), + "Confirm Delete", wx.YES | wx.NO | wx.ICON_QUESTION) + + if dlg.ShowModal() == wx.ID_YES: + sCrest.delFitting(self.getActiveCharacter(), data['fittingID']) + class ExportToEve(wx.Frame): @@ -337,3 +359,4 @@ class FitView(d.Display): def __init__(self, parent): d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL) + self.fitSelection = None diff --git a/service/crest.py b/service/crest.py index 8431132f7..b86289d0a 100644 --- a/service/crest.py +++ b/service/crest.py @@ -95,6 +95,10 @@ class Crest(): char = self.getCrestCharacter(charID) return char.eve.post('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID, data=json) + def delFitting(self, charID, fittingID): + char = self.getCrestCharacter(charID) + return char.eve.delete('https://api-sisi.testeveonline.com/characters/%d/fittings/%d/'%(char.ID, fittingID)) + def logout(self): logging.debug("Character logout") self.implicitCharacter = None diff --git a/service/pycrest/eve.py b/service/pycrest/eve.py index b42041f90..55614463d 100644 --- a/service/pycrest/eve.py +++ b/service/pycrest/eve.py @@ -275,6 +275,11 @@ class AuthedConnection(EVE): 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 = {} From 6eafbb0a25819c1d64437925c89ba9be23b2f5c2 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Wed, 28 Oct 2015 21:25:01 -0400 Subject: [PATCH 37/41] Possible fix for server issues. Instead of using a timer, use the socket timeout, and ensure that we stop server before spawning a new one. --- service/crest.py | 9 ++++----- service/server.py | 9 ++++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/service/crest.py b/service/crest.py index b86289d0a..858d95b7c 100644 --- a/service/crest.py +++ b/service/crest.py @@ -36,7 +36,6 @@ class Crest(): self.httpd = None self.state = None self.ssoTimer = None - self.httpdTimer = None # Base EVE connection that is copied to all characters self.eve = service.pycrest.EVE( @@ -107,16 +106,15 @@ class Crest(): 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() self.httpd = service.StoppableHTTPServer(('', 6461), service.AuthHandler) thread.start_new_thread(self.httpd.serve, ()) - # keep server going for only 60 seconds - self.httpdTimer = threading.Timer(60, self.stopServer) - self.httpdTimer.start() - self.state = str(uuid.uuid4()) return self.eve.auth_uri(scopes=self.scopes, state=self.state) @@ -169,3 +167,4 @@ class Crest(): wx.CallAfter(pub.sendMessage, 'login_success', type=1) + self.stopServer() diff --git a/service/server.py b/service/server.py index 4410c492b..cf20505e8 100644 --- a/service/server.py +++ b/service/server.py @@ -58,7 +58,8 @@ class StoppableHTTPServer(BaseHTTPServer.HTTPServer): def server_bind(self): BaseHTTPServer.HTTPServer.server_bind(self) - self.socket.settimeout(1) + # Allow listeing for 60 seconds + self.socket.settimeout(60) self.run = True def get_request(self): @@ -71,8 +72,13 @@ class StoppableHTTPServer(BaseHTTPServer.HTTPServer): pass def stop(self): + self.socket.close() self.run = False + def handle_timeout(self): + logger.debug("Server timed out waiting for connection") + self.stop() + def serve(self): while self.run: try: @@ -86,3 +92,4 @@ if __name__ == "__main__": thread.start_new_thread(httpd.serve, ()) raw_input("Press to stop server\n") httpd.stop() + From 649b99d7bdccb04d0ae44e7dfb6f9087c2f5e828 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 30 Oct 2015 21:07:56 -0400 Subject: [PATCH 38/41] Another possible fix for server issues. --- service/crest.py | 2 ++ service/server.py | 23 +++++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/service/crest.py b/service/crest.py index 858d95b7c..895ac0c67 100644 --- a/service/crest.py +++ b/service/crest.py @@ -5,6 +5,7 @@ import threading import copy import uuid import wx +import time from wx.lib.pubsub import pub @@ -112,6 +113,7 @@ class Crest(): 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, ()) diff --git a/service/server.py b/service/server.py index cf20505e8..0ee1fb2c2 100644 --- a/service/server.py +++ b/service/server.py @@ -58,8 +58,12 @@ class StoppableHTTPServer(BaseHTTPServer.HTTPServer): def server_bind(self): BaseHTTPServer.HTTPServer.server_bind(self) - # Allow listeing for 60 seconds - self.socket.settimeout(60) + # 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): @@ -72,20 +76,19 @@ class StoppableHTTPServer(BaseHTTPServer.HTTPServer): pass def stop(self): - self.socket.close() self.run = False + self.server_close() def handle_timeout(self): - logger.debug("Server timed out waiting for connection") - self.stop() + logger.debug("Number of tries: %d"%self.tries) + self.tries += 1 + if self.tries == self.max_tries: + logger.debug("Server timed out waiting for connection") + self.stop() def serve(self): while self.run: - try: - self.handle_request() - except TypeError: - # this can happen if stopping server in middle of request? - pass + self.handle_request() if __name__ == "__main__": httpd = StoppableHTTPServer(('', 6461), AuthHandler) From 24f770aa7bf9e3cf97f391f2a71fc2dac858889c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 30 Oct 2015 21:09:06 -0400 Subject: [PATCH 39/41] Use + instead of space (fixes an Iceweasel browser issue) --- service/pycrest/eve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/pycrest/eve.py b/service/pycrest/eve.py index 55614463d..6e08d317e 100644 --- a/service/pycrest/eve.py +++ b/service/pycrest/eve.py @@ -218,7 +218,7 @@ class EVE(APIConnection): grant_type, self.redirect_uri, self.client_id, - "&scope=%s" % ' '.join(s) if scopes else '', + "&scope=%s" % '+'.join(s) if scopes else '', "&state=%s" % state if state else '' ) From ccb9e085b2af3058c369db320604aebce2484dc9 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 31 Oct 2015 13:20:13 -0400 Subject: [PATCH 40/41] Setting to change server for CREST, and some other improvements. --- config.py | 2 - .../pyfaCrestPreferences.py | 40 ++++++++++------- gui/crestFittings.py | 9 ++-- gui/mainFrame.py | 4 +- gui/mainMenuBar.py | 9 ++-- service/crest.py | 43 ++++++++++++++++--- service/server.py | 5 ++- service/settings.py | 2 +- 8 files changed, 79 insertions(+), 35 deletions(-) diff --git a/config.py b/config.py index 97456447d..a09bc30c9 100644 --- a/config.py +++ b/config.py @@ -24,8 +24,6 @@ expansionName = "Vanguard" expansionVersion = "1.0" evemonMinVersion = "4081" -clientID = 'af87365240d644f7950af563b8418bad' - pyfaPath = None savePath = None saveDB = None diff --git a/gui/builtinPreferenceViews/pyfaCrestPreferences.py b/gui/builtinPreferenceViews/pyfaCrestPreferences.py index e06e04f7f..1cd5b05ba 100644 --- a/gui/builtinPreferenceViews/pyfaCrestPreferences.py +++ b/gui/builtinPreferenceViews/pyfaCrestPreferences.py @@ -5,6 +5,7 @@ from gui.bitmapLoader import BitmapLoader import gui.mainFrame import service +from service.crest import CrestModes class PFCrestPref ( PreferenceView): title = "CREST" @@ -30,24 +31,28 @@ class PFCrestPref ( PreferenceView): self.stInfo.Wrap(dlgWidth - 50) mainSizer.Add( self.stInfo, 0, wx.EXPAND|wx.TOP|wx.BOTTOM, 5 ) - self.grantRadioBtn1 = wx.RadioButton( panel, wx.ID_ANY, u"Implicit Grant", wx.DefaultPosition, wx.DefaultSize, 0 ) - mainSizer.Add( self.grantRadioBtn1, 0, wx.ALL, 5 ) + rbSizer = wx.BoxSizer(wx.HORIZONTAL) + self.rbMode = wx.RadioBox(panel, -1, "Mode", wx.DefaultPosition, wx.DefaultSize, ['Implicit', 'User-supplied details'], 1, wx.RA_SPECIFY_COLS) + self.rbServer = wx.RadioBox(panel, -1, "Server", wx.DefaultPosition, wx.DefaultSize, ['Tranquility', 'Singularity'], 1, wx.RA_SPECIFY_COLS) - self.grantRadioBtn2 = wx.RadioButton( panel, wx.ID_ANY, u"User-supplied details", wx.DefaultPosition, wx.DefaultSize, 0 ) - mainSizer.Add( self.grantRadioBtn2, 0, wx.ALL, 5 ) + self.rbMode.SetSelection(self.settings.get('mode')) + self.rbServer.SetSelection(self.settings.get('server')) - proxyTitle = wx.StaticText( panel, wx.ID_ANY, "CREST client details", wx.DefaultPosition, wx.DefaultSize, 0 ) - proxyTitle.Wrap( -1 ) - proxyTitle.SetFont( wx.Font( 12, 70, 90, 90, False, wx.EmptyString ) ) + rbSizer.Add(self.rbMode, 1, wx.TOP | wx.RIGHT, 5 ) + rbSizer.Add(self.rbServer, 1, wx.ALL, 5 ) - mainSizer.Add( proxyTitle, 0, wx.ALL, 5 ) + self.rbMode.Bind(wx.EVT_RADIOBOX, self.OnModeChange) + self.rbServer.Bind(wx.EVT_RADIOBOX, self.OnServerChange) + + mainSizer.Add(rbSizer, 1, wx.ALL|wx.EXPAND, 0) + + detailsTitle = wx.StaticText( panel, wx.ID_ANY, "CREST client details", wx.DefaultPosition, wx.DefaultSize, 0 ) + detailsTitle.Wrap( -1 ) + detailsTitle.SetFont( wx.Font( 12, 70, 90, 90, False, wx.EmptyString ) ) + + mainSizer.Add( detailsTitle, 0, wx.ALL, 5 ) mainSizer.Add( wx.StaticLine( panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ), 0, wx.EXPAND, 5 ) - self.grantRadioBtn1.SetValue(self.settings.get('mode') == 0) - self.grantRadioBtn2.SetValue(self.settings.get('mode') == 1) - - self.grantRadioBtn1.Bind(wx.EVT_RADIOBUTTON, self.OnRadioChange) - self.grantRadioBtn2.Bind(wx.EVT_RADIOBUTTON, self.OnRadioChange) fgAddrSizer = wx.FlexGridSizer( 2, 2, 0, 0 ) fgAddrSizer.AddGrowableCol( 1 ) @@ -82,9 +87,14 @@ class PFCrestPref ( PreferenceView): panel.SetSizer( mainSizer ) panel.Layout() - def OnRadioChange(self, event): - self.settings.set('mode', 0 if self.grantRadioBtn1.Value else 1) + def OnModeChange(self, event): + self.settings.set('mode', event.GetInt()) self.ToggleProxySettings(self.settings.get('mode')) + service.Crest.restartService() + + def OnServerChange(self, event): + self.settings.set('server', event.GetInt()) + service.Crest.restartService() def OnBtnApply(self, event): self.settings.set('clientID', self.inputClientID.GetValue()) diff --git a/gui/crestFittings.py b/gui/crestFittings.py index 4ddfe4580..c4a342f62 100644 --- a/gui/crestFittings.py +++ b/gui/crestFittings.py @@ -7,6 +7,7 @@ from wx.lib.pubsub import setupkwargs from wx.lib.pubsub import pub import service +from service.crest import CrestModes import gui.display as d from eos.types import Cargo from eos.db import getItem @@ -24,7 +25,7 @@ class CrestFittings(wx.Frame): characterSelectSizer = wx.BoxSizer( wx.HORIZONTAL ) - if sCrest.settings.get('mode') == 0: + if sCrest.settings.get('mode') == CrestModes.IMPLICIT: self.stLogged = wx.StaticText(self, wx.ID_ANY, "Currently logged in as %s"%sCrest.implicitCharacter.name, wx.DefaultPosition, wx.DefaultSize) self.stLogged.Wrap( -1 ) @@ -107,7 +108,7 @@ class CrestFittings(wx.Frame): def getActiveCharacter(self): sCrest = service.Crest.getInstance() - if sCrest.settings.get('mode') == 0: + if sCrest.settings.get('mode') == CrestModes.IMPLICIT: return sCrest.implicitCharacter.ID selection = self.charChoice.GetCurrentSelection() @@ -159,7 +160,7 @@ class ExportToEve(wx.Frame): mainSizer = wx.BoxSizer(wx.VERTICAL) hSizer = wx.BoxSizer(wx.HORIZONTAL) - if sCrest.settings.get('mode') == 0: + if sCrest.settings.get('mode') == CrestModes.IMPLICIT: self.stLogged = wx.StaticText(self, wx.ID_ANY, "Currently logged in as %s"%sCrest.implicitCharacter.name, wx.DefaultPosition, wx.DefaultSize) self.stLogged.Wrap( -1 ) @@ -207,7 +208,7 @@ class ExportToEve(wx.Frame): def getActiveCharacter(self): sCrest = service.Crest.getInstance() - if sCrest.settings.get('mode') == 0: + if sCrest.settings.get('mode') == CrestModes.IMPLICIT: return sCrest.implicitCharacter.ID selection = self.charChoice.GetCurrentSelection() diff --git a/gui/mainFrame.py b/gui/mainFrame.py index f9e2523a7..6b8a18237 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -58,6 +58,8 @@ from gui.updateDialog import UpdateDialog from gui.builtinViews import * from time import gmtime, strftime +from service.crest import CrestModes + from wx.lib.pubsub import setupkwargs from wx.lib.pubsub import pub @@ -518,7 +520,7 @@ class MainFrame(wx.Frame): def ssoLogin(self, event): sCrest = service.Crest.getInstance() - if sCrest.settings.get('mode') == 0: # Implicit, go directly to login + if sCrest.settings.get('mode') == CrestModes.IMPLICIT: if sCrest.implicitCharacter is not None: sCrest.logout() else: diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 0b071d534..7c6bd0396 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -24,6 +24,7 @@ import gui.mainFrame import gui.graphFrame import gui.globalEvents as GE import service +from service.crest import CrestModes from wx.lib.pubsub import setupkwargs from wx.lib.pubsub import pub @@ -113,14 +114,14 @@ class MainMenuBar(wx.MenuBar): # CREST Menu crestMenu = wx.Menu() self.Append(crestMenu, "&CREST") - if self.sCrest.settings.get('mode') != 0: + if self.sCrest.settings.get('mode') != CrestModes.IMPLICIT: crestMenu.Append(self.ssoLoginId, "Manage Characters") else: crestMenu.Append(self.ssoLoginId, "Login to EVE") crestMenu.Append(self.eveFittingsId, "Browse EVE Fittings") crestMenu.Append(self.exportToEveId, "Export To EVE") - if self.sCrest.settings.get('mode') == 0 or len(self.sCrest.getCrestCharacters()) == 0: + if self.sCrest.settings.get('mode') == CrestModes.IMPLICIT or len(self.sCrest.getCrestCharacters()) == 0: self.Enable(self.eveFittingsId, False) self.Enable(self.exportToEveId, False) @@ -157,13 +158,13 @@ class MainMenuBar(wx.MenuBar): event.Skip() def ssoLogin(self, type): - if self.sCrest.settings.get('mode') == 0: + if self.sCrest.settings.get('mode') == CrestModes.IMPLICIT: self.SetLabel(self.ssoLoginId, "Logout Character") self.Enable(self.eveFittingsId, True) self.Enable(self.exportToEveId, True) def ssoLogout(self, message): - if self.sCrest.settings.get('mode') == 0: + if self.sCrest.settings.get('mode') == CrestModes.IMPLICIT: self.SetLabel(self.ssoLoginId, "Login to EVE") self.Enable(self.eveFittingsId, False) self.Enable(self.exportToEveId, False) diff --git a/service/crest.py b/service/crest.py index 895ac0c67..01b2aab53 100644 --- a/service/crest.py +++ b/service/crest.py @@ -10,13 +10,27 @@ import time from wx.lib.pubsub import pub import eos.db +from eos.enum import Enum from eos.types import CrestChar import service logger = logging.getLogger(__name__) +class Servers(Enum): + TQ = 0 + SISI = 1 + +class CrestModes(Enum): + IMPLICIT = 0 + USER = 1 + class Crest(): + clientIDs = { + Servers.TQ: 'f9be379951c046339dc13a00e6be7704', + Servers.SISI: 'af87365240d644f7950af563b8418bad' + } + # @todo: move this to settings clientCallback = 'http://localhost:6461' clientTest = True @@ -29,7 +43,17 @@ class Crest(): return cls._instance + @classmethod + def restartService(cls): + # This is hear 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. + cls._instance.stopServer() + cls._instance = Crest() + return cls._instance + def __init__(self): + print "init crest" self.settings = service.settings.CRESTSettings.getInstance() self.scopes = ['characterFittingsRead', 'characterFittingsWrite'] @@ -40,10 +64,11 @@ class Crest(): # 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') == 1 else config.clientID, - api_key=self.settings.get('clientSecret') if self.settings.get('mode') == 1 else None, - redirect_uri=self.clientCallback, - testing=self.clientTest) + 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 @@ -52,6 +77,10 @@ class Crest(): self.charCache = {} pub.subscribe(self.handleLogin, 'sso_login') + @property + def isTestServer(self): + return self.settings.get('server') == Servers.SISI + def delCrestCharacter(self, charID): char = eos.db.getCrestCharacter(charID) eos.db.remove(char) @@ -72,7 +101,7 @@ class Crest(): ''' Get character, and modify to include the eve connection ''' - if self.settings.get('mode') == 0: + 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 @@ -148,7 +177,7 @@ class Crest(): self.implicitCharacter.eve = eve #self.implicitCharacter.fetchImage() - wx.CallAfter(pub.sendMessage, 'login_success', type=0) + wx.CallAfter(pub.sendMessage, 'login_success', type=CrestModes.IMPLICIT) elif 'code' in message: eve = copy.deepcopy(self.eve) eve.authorize(message['code'][0]) @@ -167,6 +196,6 @@ class Crest(): self.charCache[int(info['CharacterID'])] = char eos.db.save(char) - wx.CallAfter(pub.sendMessage, 'login_success', type=1) + wx.CallAfter(pub.sendMessage, 'login_success', type=CrestModes.USER) self.stopServer() diff --git a/service/server.py b/service/server.py index 0ee1fb2c2..a4750876f 100644 --- a/service/server.py +++ b/service/server.py @@ -88,7 +88,10 @@ class StoppableHTTPServer(BaseHTTPServer.HTTPServer): def serve(self): while self.run: - self.handle_request() + try: + self.handle_request() + except TypeError: + pass if __name__ == "__main__": httpd = StoppableHTTPServer(('', 6461), AuthHandler) diff --git a/service/settings.py b/service/settings.py index ec86524df..10e7389b5 100644 --- a/service/settings.py +++ b/service/settings.py @@ -278,7 +278,7 @@ class CRESTSettings(): # mode # 0 - Implicit authentication # 1 - User-supplied client details - serviceCRESTDefaultSettings = {"mode": 0, "clientID": "", "clientSecret": ""} + serviceCRESTDefaultSettings = {"mode": 0, "server": 0, "clientID": "", "clientSecret": ""} self.serviceCRESTSettings = SettingsProvider.getInstance().getSettings("pyfaServiceCRESTSettings", serviceCRESTDefaultSettings) From bb4fe63a7cb197bbaf04972e1ebcb281718f367f Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 31 Oct 2015 14:10:41 -0400 Subject: [PATCH 41/41] Add CREST preference icon (basic EVE icon), and fix menus when changing modes and whatnot --- .../pyfaCrestPreferences.py | 2 +- gui/mainMenuBar.py | 11 +++++++++++ imgs/gui/eve.png | Bin 0 -> 2680 bytes service/crest.py | 7 +++++-- 4 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 imgs/gui/eve.png diff --git a/gui/builtinPreferenceViews/pyfaCrestPreferences.py b/gui/builtinPreferenceViews/pyfaCrestPreferences.py index 1cd5b05ba..308999953 100644 --- a/gui/builtinPreferenceViews/pyfaCrestPreferences.py +++ b/gui/builtinPreferenceViews/pyfaCrestPreferences.py @@ -115,6 +115,6 @@ class PFCrestPref ( PreferenceView): self.inputClientSecret.Disable() def getImage(self): - return BitmapLoader.getBitmap("prefs_proxy", "gui") + return BitmapLoader.getBitmap("eve", "gui") PFCrestPref.register() diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 7c6bd0396..95772fdbc 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -139,6 +139,7 @@ class MainMenuBar(wx.MenuBar): self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) pub.subscribe(self.ssoLogin, 'login_success') pub.subscribe(self.ssoLogout, 'logout_success') + pub.subscribe(self.updateCrest, 'crest_changed') def fitChanged(self, event): enable = event.fitID is not None @@ -168,3 +169,13 @@ class MainMenuBar(wx.MenuBar): self.SetLabel(self.ssoLoginId, "Login to EVE") self.Enable(self.eveFittingsId, False) self.Enable(self.exportToEveId, False) + + def updateCrest(self, message): + bool = self.sCrest.settings.get('mode') == CrestModes.IMPLICIT or len(self.sCrest.getCrestCharacters()) == 0 + self.Enable(self.eveFittingsId, not bool) + self.Enable(self.exportToEveId, not bool) + if self.sCrest.settings.get('mode') == CrestModes.IMPLICIT: + self.SetLabel(self.ssoLoginId, "Login to EVE") + else: + self.SetLabel(self.ssoLoginId, "Manage Characters") + diff --git a/imgs/gui/eve.png b/imgs/gui/eve.png new file mode 100644 index 0000000000000000000000000000000000000000..07bb383634a021aa467e44419d33bf2f00038541 GIT binary patch literal 2680 zcmV-;3WxQHP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf5&!@T5&_cPe*6Fc02p*dSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E;js(W8VM(3F%2hK~zXfU6uJ)6lWHOYi3RoL3;11uCCsz z_v(FbKv6`R-WN2OSU*=R{MnyMSARD(HDZ%^%WruMp%JN@x(C5g>LS$dSF zkp0f^S>15|9UXZ?O}s?HVJ<_&Kiqv?@6jg&?=_@XtQWGxlhZC`)D7~5}6G?-&` z=GZ<{ticd()+P>Wk_WYkW<$J@iZ@WPMk>~9h#g!)9|yjGg)s^x517Msp6VuhXs0Jy z>x=FJ5Kp+q9o=e+*HY1XQ-W!Viqt`yj(D9tzR#R&qLK%VK&siCY_KGNW=s4aIQD-) z5-x$a@Rp89oh`J-lh|hq?MA$by_WE9OQH!{(1x3+NRu^OZx8OaSM30-!CF&juQ9yW z6sk3cc36SnE=Zc0ZLKA|2Y{{tV^ys^-T*gSBK5Y|{>9h`t|C-uC$y{%>;X5_0md0e zaECv%r8rnq9Ng#&ZSsURxx<^?;Z45KMn4d&@dshe;0=E6X4vEkZn0Huu?4r=BfAV$ z+b!{WO<)%l-LDJRA;!oKbzrL|vWKB+o2jY>4re?|&&@N;G^qjA(Nj^XXphwoyq4G zV-}QixpX=`H#avkGc`3eF)=YVIyyT1dgR6Mn}MOx-rvV?$}2RLYFB@#uK1J;e6Cnq zgJ@T5pw?`amu}QmY@ycHc&au|=jd#n2I={1K9i+0dB%sC1$rt=PtN7%7X6t@FHFp4 zN2lgS#;1ROJ^pNXd|+t2Z+MEJipA!YhVl>f6`!ceKUI`(K&-1j(v?)}SA9&bSf?zG zY5i3*G-LhbEJ)FW)`aV9N@tnS;1U^KsNN&c_yy_`h^WSgYrSHvN33<>8mCC@BJ_R% z=|S}?4J$q*OV;blH>k@#K}c7nX=RG^$FybZ4XaY7vgn&E({GxlC+8P%ql2qmwt@s zy%AGswagilc@ipLLg@?`y#cd(l}Kq6t1WD~RbVJp7AMG(YLzc8b4BFdYPn~! zrV+WdQteBK9TBNFh1-J?d&Tf9bDmSPxz}UUBa_*eW9cU^$6t{o)Uov#{4nFLA0r5f4)2?3!Iv+l{`=Gbueoy<|?pqyQH*R-bzS(i%`Y(Ki zp@7g3X1~~6f|*MQdl@3Nu2DEEFuhM|E(LX!rFeKU|9WNt+!&t7^gVz5cyRd9z%ba- z+57DA^EdY&J?rclywlzPOV{JucY7H1pYL94>$vdK&5PIDc@hmAif?DbTnPmfR}TdRQ_GN;(Oh zTdK2n_q~E4>FgP}_iMjWZG|I0Ys$tag{b`A640ea*8k2JsKQ=5q$6%ydD5)Or~9mgxU-)fzYBl zRH7sblT>MvsmuT-HJ}nbLE4}OPiA6EOai5yr?Mjjm`6w= zJ?)*3Ai{)P`?>S#t$U|0Ts?QW?ezCIzCCyC#F;C{PF*_Idf~|N^Iso3d-&+-LoH{I zetVH4BKcASUrupk6iZ?NSp*~Pg&2vER<=aL5v!rc+xL56v>^hZe!czvPi=R76D`|9YaLoH{(Mt~>LF2_|cvK*NX zVdKmm4o4{0voHl*zS+^;cIV-Z+nv|j@BP%)ars6&sGqrb?c}*1kDs}8vvzDICJ>;>3_Af9y)qriNnWQ|MAsf$o#30^FBw&mndGGm-7iWDk23jk4&UdBQHfGYq# z;P8L~_FJIFp9_oLV{ty<@S!#rm!L8wEO=rm!VwZYObVcc5*i0Mn6yYB=89zRfXq7t zxRfuFECFr41102KRLl|LEItOnf`<_hk)>N%d|ZIb`C!pTL{S_o@mLHd7945(?~kc6aaMqUPUOD!7+l+l3k0>GwSbvhZzJ?U{M0t)07=>Gt;(^E7*uxOG10000 https://api-sisi.testeveonline.com/characters/1611853631/fittings/37486494/ char = self.getCrestCharacter(charID) return char.eve.post('https://api-sisi.testeveonline.com/characters/%d/fittings/'%char.ID, data=json)