1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-06-02 00:21:05 +03:00

CAPI: Convert full Update flow to class passing

* Base the following on common EDMCCAPIReturn: EDMCFailedrequest,
  EDMCCAPIRequest, EDMCCAPIResponse.  This saves repeating a bunch of
  variable types and comments.
* Use the above throughout the 'Update' button flow.
* Still need to address 'Save Raw Data', i.e. AppWindow.save_raw().
This commit is contained in:
Athanasius 2021-08-23 16:28:26 +01:00
parent c6f93bd3c6
commit f80623e025
No known key found for this signature in database
GPG Key ID: AE3E527847057C7D
2 changed files with 157 additions and 94 deletions

View File

@ -935,77 +935,86 @@ class AppWindow(object):
querytime = int(time()) querytime = int(time())
logger.trace_if('capi.worker', 'Requesting full station data') logger.trace_if('capi.worker', 'Requesting full station data')
companion.session.station(querytime=querytime, play_sound=play_sound) companion.session.station(
querytime=querytime, retrying=retrying, play_sound=play_sound
)
config.set('querytime', querytime) config.set('querytime', querytime)
def capi_handle_response(self, event=None): # noqa: C901, CCR001
def capi_handle_response(self, event=None):
"""Handle the resulting data from a CAPI query.""" """Handle the resulting data from a CAPI query."""
play_bad = False play_bad: bool = False
err: Optional[str] = None err: Optional[str] = None
data: Union[companion.CAPIData, companion.CAPIFailedRequest] capi_response: Union[companion.CAPIFailedRequest, companion.EDMCCAPIResponse]
querytime: int
play_sound: bool
auto_update: bool
try: try:
logger.trace_if('capi.worker', 'Pulling answer off queue') logger.trace_if('capi.worker', 'Pulling answer off queue')
data, querytime, play_sound, auto_update = self.capi_response_queue.get(block=False) capi_response = self.capi_response_queue.get(block=False)
if isinstance(data, companion.CAPIFailedRequest): if isinstance(capi_response, companion.CAPIFailedRequest):
logger.trace_if('capi.worker', f'Failed Request: {data.message}') logger.trace_if('capi.worker', f'Failed Request: {capi_response.message}')
if data.exception: if capi_response.exception:
raise data.exception raise capi_response.exception
else: else:
raise ValueError(data.message) raise ValueError(capi_response.message)
logger.trace_if('capi.worker', 'Answer is not a Failure') logger.trace_if('capi.worker', 'Answer is not a Failure')
if not isinstance(capi_response, companion.EDMCCAPIResponse):
msg = f"Response was neither CAPIFailedRequest nor EDMCAPIResponse: {type(capi_response)}"
logger.error(msg)
raise ValueError(msg)
# Validation # Validation
if 'commander' not in data: if 'commander' not in capi_response.capi_data:
# This can happen with EGS Auth if no commander created yet # This can happen with EGS Auth if no commander created yet
# LANG: No data was returned for the commander from the Frontier CAPI # LANG: No data was returned for the commander from the Frontier CAPI
err = self.status['text'] = _('CAPI: No commander data returned') err = self.status['text'] = _('CAPI: No commander data returned')
elif not data.get('commander', {}).get('name'): elif not capi_response.capi_data.get('commander', {}).get('name'):
# LANG: We didn't have the commander name when we should have # LANG: We didn't have the commander name when we should have
err = self.status['text'] = _("Who are you?!") # Shouldn't happen err = self.status['text'] = _("Who are you?!") # Shouldn't happen
elif (not data.get('lastSystem', {}).get('name') elif (not capi_response.capi_data.get('lastSystem', {}).get('name')
or (data['commander'].get('docked') or (capi_response.capi_data['commander'].get('docked')
and not data.get('lastStarport', {}).get('name'))): and not capi_response.capi_data.get('lastStarport', {}).get('name'))):
# LANG: We don't know where the commander is, when we should # LANG: We don't know where the commander is, when we should
err = self.status['text'] = _("Where are you?!") # Shouldn't happen err = self.status['text'] = _("Where are you?!") # Shouldn't happen
elif not data.get('ship', {}).get('name') or not data.get('ship', {}).get('modules'): elif (
not capi_response.capi_data.get('ship', {}).get('name')
or not capi_response.capi_data.get('ship', {}).get('modules')
):
# LANG: We don't know what ship the commander is in, when we should # LANG: We don't know what ship the commander is in, when we should
err = self.status['text'] = _("What are you flying?!") # Shouldn't happen err = self.status['text'] = _("What are you flying?!") # Shouldn't happen
elif monitor.cmdr and data['commander']['name'] != monitor.cmdr: elif monitor.cmdr and capi_response.capi_data['commander']['name'] != monitor.cmdr:
# Companion API Commander doesn't match Journal # Companion API Commander doesn't match Journal
logger.trace_if('capi.worker', 'Raising CmdrError()') logger.trace_if('capi.worker', 'Raising CmdrError()')
raise companion.CmdrError() raise companion.CmdrError()
elif auto_update and not monitor.state['OnFoot'] and not data['commander'].get('docked'): elif (
capi_response.auto_update and not monitor.state['OnFoot']
and not capi_response.capi_data['commander'].get('docked')
):
# auto update is only when just docked # auto update is only when just docked
logger.warning(f"{auto_update!r} and not {monitor.state['OnFoot']!r} and " logger.warning(f"{capi_response.auto_update!r} and not {monitor.state['OnFoot']!r} and "
f"not {data['commander'].get('docked')!r}") f"not {capi_response.capi_data['commander'].get('docked')!r}")
raise companion.ServerLagging() raise companion.ServerLagging()
elif data['lastSystem']['name'] != monitor.system: elif capi_response.capi_data['lastSystem']['name'] != monitor.system:
# CAPI system must match last journal one # CAPI system must match last journal one
logger.warning(f"{data['lastSystem']['name']!r} != {monitor.system!r}") logger.warning(f"{capi_response.capi_data['lastSystem']['name']!r} != {monitor.system!r}")
raise companion.ServerLagging() raise companion.ServerLagging()
elif data['lastStarport']['name'] != monitor.station: elif capi_response.capi_data['lastStarport']['name'] != monitor.station:
if monitor.state['OnFoot'] and monitor.station: if monitor.state['OnFoot'] and monitor.station:
logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND " logger.warning(f"({capi_response.capi_data['lastStarport']['name']!r} != {monitor.station!r}) AND "
f"{monitor.state['OnFoot']!r} and {monitor.station!r}") f"{monitor.state['OnFoot']!r} and {monitor.station!r}")
raise companion.ServerLagging() raise companion.ServerLagging()
else: else:
last_station = None last_station = None
if data['commander']['docked']: if capi_response.capi_data['commander']['docked']:
last_station = data['lastStarport']['name'] last_station = capi_response.capi_data['lastStarport']['name']
if monitor.station is None: if monitor.station is None:
# Likely (re-)Embarked on ship docked at an EDO settlement. # Likely (re-)Embarked on ship docked at an EDO settlement.
@ -1017,33 +1026,40 @@ class AppWindow(object):
if last_station != monitor.station: if last_station != monitor.station:
# CAPI lastStarport must match # CAPI lastStarport must match
logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND " logger.warning(f"({capi_response.capi_data['lastStarport']['name']!r} != {monitor.station!r})"
f"{last_station!r} != {monitor.station!r}") f" AND {last_station!r} != {monitor.station!r}")
raise companion.ServerLagging() raise companion.ServerLagging()
self.capi_query_holdoff_time = querytime + companion.capi_query_cooldown self.capi_query_holdoff_time = capi_response.querytime + companion.capi_query_cooldown
elif not monitor.state['OnFoot'] and data['ship']['id'] != monitor.state['ShipID']: elif not monitor.state['OnFoot'] and capi_response.capi_data['ship']['id'] != monitor.state['ShipID']:
# CAPI ship must match # CAPI ship must match
logger.warning(f"not {monitor.state['OnFoot']!r} and " logger.warning(f"not {monitor.state['OnFoot']!r} and "
f"{data['ship']['id']!r} != {monitor.state['ShipID']!r}") f"{capi_response.capi_data['ship']['id']!r} != {monitor.state['ShipID']!r}")
raise companion.ServerLagging() raise companion.ServerLagging()
elif not monitor.state['OnFoot'] and data['ship']['name'].lower() != monitor.state['ShipType']: elif (
not monitor.state['OnFoot']
and capi_response.capi_data['ship']['name'].lower() != monitor.state['ShipType']
):
# CAPI ship type must match # CAPI ship type must match
logger.warning(f"not {monitor.state['OnFoot']!r} and " logger.warning(f"not {monitor.state['OnFoot']!r} and "
f"{data['ship']['name'].lower()!r} != {monitor.state['ShipType']!r}") f"{capi_response.capi_data['ship']['name'].lower()!r} != "
f"{monitor.state['ShipType']!r}")
raise companion.ServerLagging() raise companion.ServerLagging()
else: else:
# TODO: Change to depend on its own CL arg # TODO: Change to depend on its own CL arg
if __debug__: # Recording if __debug__: # Recording
companion.session.dump_capi_data(data) companion.session.dump_capi_data(capi_response.capi_data)
if not monitor.state['ShipType']: # Started game in SRV or fighter if not monitor.state['ShipType']: # Started game in SRV or fighter
self.ship['text'] = ship_name_map.get(data['ship']['name'].lower(), data['ship']['name']) self.ship['text'] = ship_name_map.get(
monitor.state['ShipID'] = data['ship']['id'] capi_response.capi_data['ship']['name'].lower(),
monitor.state['ShipType'] = data['ship']['name'].lower() capi_response.capi_data['ship']['name']
)
monitor.state['ShipID'] = capi_response.capi_data['ship']['id']
monitor.state['ShipType'] = capi_response.capi_data['ship']['name'].lower()
if not monitor.state['Modules']: if not monitor.state['Modules']:
self.ship.configure(state=tk.DISABLED) self.ship.configure(state=tk.DISABLED)
@ -1053,34 +1069,34 @@ class AppWindow(object):
self.ship.configure(state=True) self.ship.configure(state=True)
if monitor.state.get('SuitCurrent') is not None: if monitor.state.get('SuitCurrent') is not None:
if (loadout := data.get('loadout')) is not None: if (loadout := capi_response.capi_data.get('loadout')) is not None:
if (suit := loadout.get('suit')) is not None: if (suit := loadout.get('suit')) is not None:
if (suitname := suit.get('edmcName')) is not None: if (suitname := suit.get('edmcName')) is not None:
# We've been paranoid about loadout->suit->suitname, now just assume loadouts is there # We've been paranoid about loadout->suit->suitname, now just assume loadouts is there
loadout_name = index_possibly_sparse_list( loadout_name = index_possibly_sparse_list(
data['loadouts'], loadout['loadoutSlotId'] capi_response.capi_data['loadouts'], loadout['loadoutSlotId']
)['name'] )['name']
self.suit['text'] = f'{suitname} ({loadout_name})' self.suit['text'] = f'{suitname} ({loadout_name})'
self.suit_show_if_set() self.suit_show_if_set()
if data['commander'].get('credits') is not None: if capi_response.capi_data['commander'].get('credits') is not None:
monitor.state['Credits'] = data['commander']['credits'] monitor.state['Credits'] = capi_response.capi_data['commander']['credits']
monitor.state['Loan'] = data['commander'].get('debt', 0) monitor.state['Loan'] = capi_response.capi_data['commander'].get('debt', 0)
# stuff we can do when not docked # stuff we can do when not docked
err = plug.notify_newdata(data, monitor.is_beta) err = plug.notify_newdata(capi_response.capi_data, monitor.is_beta)
self.status['text'] = err and err or '' self.status['text'] = err and err or ''
if err: if err:
play_bad = True play_bad = True
# Export market data # Export market data
if not self.export_market_data(data): if not self.export_market_data(capi_response.capi_data):
err = 'Error: Exporting Market data' err = 'Error: Exporting Market data'
play_bad = True play_bad = True
self.capi_query_holdoff_time = querytime + companion.capi_query_cooldown self.capi_query_holdoff_time = capi_response.querytime + companion.capi_query_cooldown
except queue.Empty: except queue.Empty:
logger.error('There was no response in the queue!') logger.error('There was no response in the queue!')
@ -1089,7 +1105,6 @@ class AppWindow(object):
except companion.CredentialsError: except companion.CredentialsError:
# Redirected back to Auth server - force full re-authentication # Redirected back to Auth server - force full re-authentication
companion.session.dump(r)
companion.session.invalidate() companion.session.invalidate()
companion.session.retrying = False companion.session.retrying = False
companion.session.login() companion.session.login()
@ -1097,7 +1112,7 @@ class AppWindow(object):
# Companion API problem # Companion API problem
except companion.ServerLagging as e: except companion.ServerLagging as e:
err = str(e) err = str(e)
if retrying: if capi_response.retrying:
self.status['text'] = err self.status['text'] = err
play_bad = True play_bad = True
@ -1124,13 +1139,13 @@ class AppWindow(object):
if not err: # not self.status['text']: # no errors if not err: # not self.status['text']: # no errors
# LANG: Time when we last obtained Frontier CAPI data # LANG: Time when we last obtained Frontier CAPI data
self.status['text'] = strftime(_('Last updated at %H:%M:%S'), localtime(querytime)) self.status['text'] = strftime(_('Last updated at %H:%M:%S'), localtime(capi_response.querytime))
if play_sound and play_bad: if capi_response.play_sound and play_bad:
hotkeymgr.play_bad() hotkeymgr.play_bad()
# Update Odyssey Suit data # Update Odyssey Suit data
companion.session.suit_update(data) companion.session.suit_update(capi_response.capi_data)
self.update_suit_text() self.update_suit_text()
self.suit_show_if_set() self.suit_show_if_set()
@ -1413,7 +1428,9 @@ class AppWindow(object):
if time() < self.capi_query_holdoff_time: if time() < self.capi_query_holdoff_time:
# Update button in main window # Update button in main window
self.button['text'] = self.theme_button['text'] \ self.button['text'] = self.theme_button['text'] \
= _('cooldown {SS}s').format(SS=int(self.capi_query_holdoff_time - time())) # LANG: Cooldown on 'Update' button = _('cooldown {SS}s').format( # LANG: Cooldown on 'Update' button
SS=int(self.capi_query_holdoff_time - time())
)
self.w.after(1000, self.cooldown) self.w.after(1000, self.cooldown)
else: else:

View File

@ -473,33 +473,52 @@ class Auth(object):
return base64.urlsafe_b64encode(text).decode().replace('=', '') return base64.urlsafe_b64encode(text).decode().replace('=', '')
class EDMCCAPIRequest(): class EDMCCAPIReturn:
"""Base class for Request, Failure or Response."""
def __init__(
self, querytime: int, retrying: bool = False,
play_sound: bool = False, auto_update: bool = False
):
self.querytime: int = querytime # When this query is considered to have started (time_t).
self.retrying: bool = retrying # Whether this is already a retry.
self.play_sound: bool = play_sound # Whether to play good/bad sounds for success/failure.
self.auto_update: bool = auto_update # Whether this was automatically triggered.
class EDMCCAPIRequest(EDMCCAPIReturn):
"""Encapsulates a request for CAPI data.""" """Encapsulates a request for CAPI data."""
endpoint: str # The CAPI query to perform. def __init__(
querytime: int # When this query is considered to have started (time_t). self, endpoint: str,
play_sound: bool # Whether to play good/bad sounds for success/failure. querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False
auto_update: bool # Whether this was automatically triggered. ):
super().__init__(querytime=querytime, retrying=retrying, play_sound=play_sound, auto_update=auto_update)
def __init__(self, endpoint: str, querytime: int, play_sound: bool = False, auto_update: bool = False): self.endpoint: str = endpoint # The CAPI query to perform.
self.endpoint = endpoint
self.querytime = querytime
self.play_sound = play_sound
self.auto_update = auto_update
class EDMCCAPIResponse(): class EDMCCAPIResponse(EDMCCAPIReturn):
"""Encapsulates a response from CAPI quer(y|ies).""" """Encapsulates a response from CAPI quer(y|ies)."""
... def __init__(
self, capi_data: CAPIData,
querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False
):
super().__init__(querytime=querytime, retrying=retrying, play_sound=play_sound, auto_update=auto_update)
self.capi_data: CAPIData = capi_data # Frontier CAPI response, possibly augmented (station query)
class CAPIFailedRequest(): class CAPIFailedRequest(EDMCCAPIReturn):
"""CAPI failed query error class.""" """CAPI failed query error class."""
def __init__(self, message, exception=None): def __init__(
self.message = message self, message: str,
self.exception = exception querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False,
exception=None
):
super().__init__(querytime=querytime, retrying=retrying, play_sound=play_sound, auto_update=auto_update)
self.message: str = message # User-friendly reason for failure.
self.exception: int = exception # Exception that recipient should raise.
class Session(object): class Session(object):
@ -701,6 +720,7 @@ class Session(object):
if r.url.startswith(FRONTIER_AUTH_SERVER): if r.url.startswith(FRONTIER_AUTH_SERVER):
logger.info('Redirected back to Auth Server') logger.info('Redirected back to Auth Server')
self.dump(r)
raise CredentialsError('Redirected back to Auth Server') raise CredentialsError('Redirected back to Auth Server')
elif 500 <= r.status_code < 600: elif 500 <= r.status_code < 600:
@ -814,49 +834,59 @@ class Session(object):
break break
logger.trace_if('capi.worker', f'Processing query: {query.endpoint}') logger.trace_if('capi.worker', f'Processing query: {query.endpoint}')
data: CAPIData capi_data: CAPIData
if query.endpoint == self._CAPI_PATH_STATION: if query.endpoint == self._CAPI_PATH_STATION:
try: try:
data = capi_station_queries() capi_data = capi_station_queries()
except Exception as e: except Exception as e:
self.capi_response_queue.put( self.capi_response_queue.put(
( (
CAPIFailedRequest( CAPIFailedRequest(
message=e.args, message=e.args,
exception=e exception=e,
querytime=query.querytime,
play_sound=query.play_sound,
auto_update=query.auto_update
), ),
query.querytime,
query.play_sound,
query.auto_update
) )
) )
else: else:
self.capi_response_queue.put( self.capi_response_queue.put(
(data, query.querytime, query.play_sound, query.auto_update) EDMCCAPIResponse(
capi_data=capi_data,
querytime=query.querytime,
play_sound=query.play_sound,
auto_update=query.auto_update
)
) )
else: else:
try: try:
data = capi_single_query(self.FRONTIER_CAPI_PATH_PROFILE) capi_data = capi_single_query(self.FRONTIER_CAPI_PATH_PROFILE)
except Exception as e: except Exception as e:
self.capi_response_queue.put( self.capi_response_queue.put(
( (
CAPIFailedRequest( CAPIFailedRequest(
message=e.args, message=e.args,
exception=e exception=e,
querytime=query.querytime,
play_sound=query.play_sound,
auto_update=query.auto_update
), ),
query.querytime,
query.play_sound,
query.auto_update
) )
) )
else: else:
self.capi_response_queue.put( self.capi_response_queue.put(
(data, query.querytime, query.play_sound, query.auto_update) EDMCCAPIResponse(
capi_data=capi_data,
querytime=query.querytime,
play_sound=query.play_sound,
auto_update=query.auto_update
)
) )
self.tk_master.event_generate('<<CAPIResponse>>') self.tk_master.event_generate('<<CAPIResponse>>')
@ -867,15 +897,17 @@ class Session(object):
"""Ask the CAPI query thread to finish.""" """Ask the CAPI query thread to finish."""
self.capi_query_queue.put(None) self.capi_query_queue.put(None)
def query(self, endpoint: str, querytime: int, play_sound: bool = False, auto_update: bool = False) -> None: def query(
self, endpoint: str,
querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False
) -> None:
""" """
Perform a query against the specified CAPI endpoint. Perform a query against the specified CAPI endpoint.
:param querytime: When this query was initiated. :param querytime: When this query was initiated.
:param retrying: Whether this is a retry.
:param play_sound: Whether the app should play a sound on error. :param play_sound: Whether the app should play a sound on error.
:param endpoint: The CAPI endpoint to query, might be a pseudo-value.
:param auto_update: Whether this request was triggered automatically. :param auto_update: Whether this request was triggered automatically.
:return:
""" """
logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"') logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"')
if self.state == Session.STATE_INIT: if self.state == Session.STATE_INIT:
@ -894,27 +926,40 @@ class Session(object):
self.capi_query_queue.put( self.capi_query_queue.put(
EDMCCAPIRequest( EDMCCAPIRequest(
endpoint=endpoint, endpoint=endpoint,
retrying=retrying,
querytime=querytime, querytime=querytime,
play_sound=play_sound, play_sound=play_sound,
auto_update=auto_update auto_update=auto_update
) )
) )
def profile(self, querytime: int = int(time.time()), play_sound: bool = False, auto_update: bool = False) -> None: def profile(
self,
querytime: int = int(time.time()), retrying: bool = False,
play_sound: bool = False, auto_update: bool = False
) -> None:
""" """
Perform general CAPI /profile endpoint query. Perform general CAPI /profile endpoint query.
:param querytime: When this query was initiated. :param querytime: When this query was initiated.
:param retrying: Whether this is a retry.
:param play_sound: Whether the app should play a sound on error. :param play_sound: Whether the app should play a sound on error.
:param auto_update: Whether this request was triggered automatically. :param auto_update: Whether this request was triggered automatically.
""" """
self.query(self.FRONTIER_CAPI_PATH_PROFILE, querytime=querytime, play_sound=play_sound, auto_update=auto_update) self.query(
self.FRONTIER_CAPI_PATH_PROFILE, querytime=querytime, retrying=retrying,
play_sound=play_sound, auto_update=auto_update
)
def station(self, querytime: int, play_sound: bool = False, auto_update: bool = False) -> None: def station(
self,
querytime: int, retrying: bool = False, play_sound: bool = False, auto_update: bool = False
) -> None:
""" """
Perform CAPI quer(y|ies) for station data. Perform CAPI quer(y|ies) for station data.
:param querytime: When this query was initiated. :param querytime: When this query was initiated.
:param retrying: Whether this is a retry.
:param play_sound: Whether the app should play a sound on error. :param play_sound: Whether the app should play a sound on error.
:param auto_update: Whether this request was triggered automatically. :param auto_update: Whether this request was triggered automatically.
""" """
@ -923,6 +968,7 @@ class Session(object):
EDMCCAPIRequest( EDMCCAPIRequest(
endpoint=self._CAPI_PATH_STATION, endpoint=self._CAPI_PATH_STATION,
querytime=querytime, querytime=querytime,
retrying=retrying,
play_sound=play_sound, play_sound=play_sound,
auto_update=auto_update auto_update=auto_update
) )