1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-15 00:30:33 +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())
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)
def capi_handle_response(self, event=None):
def capi_handle_response(self, event=None): # noqa: C901, CCR001
"""Handle the resulting data from a CAPI query."""
play_bad = False
play_bad: bool = False
err: Optional[str] = None
data: Union[companion.CAPIData, companion.CAPIFailedRequest]
querytime: int
play_sound: bool
auto_update: bool
capi_response: Union[companion.CAPIFailedRequest, companion.EDMCCAPIResponse]
try:
logger.trace_if('capi.worker', 'Pulling answer off queue')
data, querytime, play_sound, auto_update = self.capi_response_queue.get(block=False)
if isinstance(data, companion.CAPIFailedRequest):
logger.trace_if('capi.worker', f'Failed Request: {data.message}')
if data.exception:
raise data.exception
capi_response = self.capi_response_queue.get(block=False)
if isinstance(capi_response, companion.CAPIFailedRequest):
logger.trace_if('capi.worker', f'Failed Request: {capi_response.message}')
if capi_response.exception:
raise capi_response.exception
else:
raise ValueError(data.message)
raise ValueError(capi_response.message)
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
if 'commander' not in data:
if 'commander' not in capi_response.capi_data:
# This can happen with EGS Auth if no commander created yet
# LANG: No data was returned for the commander from the Frontier CAPI
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
err = self.status['text'] = _("Who are you?!") # Shouldn't happen
elif (not data.get('lastSystem', {}).get('name')
or (data['commander'].get('docked')
and not data.get('lastStarport', {}).get('name'))):
elif (not capi_response.capi_data.get('lastSystem', {}).get('name')
or (capi_response.capi_data['commander'].get('docked')
and not capi_response.capi_data.get('lastStarport', {}).get('name'))):
# LANG: We don't know where the commander is, when we should
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
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
logger.trace_if('capi.worker', 'Raising 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
logger.warning(f"{auto_update!r} and not {monitor.state['OnFoot']!r} and "
f"not {data['commander'].get('docked')!r}")
logger.warning(f"{capi_response.auto_update!r} and not {monitor.state['OnFoot']!r} and "
f"not {capi_response.capi_data['commander'].get('docked')!r}")
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
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()
elif data['lastStarport']['name'] != monitor.station:
elif capi_response.capi_data['lastStarport']['name'] != 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}")
raise companion.ServerLagging()
else:
last_station = None
if data['commander']['docked']:
last_station = data['lastStarport']['name']
if capi_response.capi_data['commander']['docked']:
last_station = capi_response.capi_data['lastStarport']['name']
if monitor.station is None:
# Likely (re-)Embarked on ship docked at an EDO settlement.
@ -1017,33 +1026,40 @@ class AppWindow(object):
if last_station != monitor.station:
# CAPI lastStarport must match
logger.warning(f"({data['lastStarport']['name']!r} != {monitor.station!r}) AND "
f"{last_station!r} != {monitor.station!r}")
logger.warning(f"({capi_response.capi_data['lastStarport']['name']!r} != {monitor.station!r})"
f" AND {last_station!r} != {monitor.station!r}")
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
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()
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
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()
else:
# TODO: Change to depend on its own CL arg
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
self.ship['text'] = ship_name_map.get(data['ship']['name'].lower(), data['ship']['name'])
monitor.state['ShipID'] = data['ship']['id']
monitor.state['ShipType'] = data['ship']['name'].lower()
self.ship['text'] = ship_name_map.get(
capi_response.capi_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']:
self.ship.configure(state=tk.DISABLED)
@ -1053,43 +1069,42 @@ class AppWindow(object):
self.ship.configure(state=True)
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 (suitname := suit.get('edmcName')) is not None:
# We've been paranoid about loadout->suit->suitname, now just assume loadouts is there
loadout_name = index_possibly_sparse_list(
data['loadouts'], loadout['loadoutSlotId']
capi_response.capi_data['loadouts'], loadout['loadoutSlotId']
)['name']
self.suit['text'] = f'{suitname} ({loadout_name})'
self.suit_show_if_set()
if data['commander'].get('credits') is not None:
monitor.state['Credits'] = data['commander']['credits']
monitor.state['Loan'] = data['commander'].get('debt', 0)
if capi_response.capi_data['commander'].get('credits') is not None:
monitor.state['Credits'] = capi_response.capi_data['commander']['credits']
monitor.state['Loan'] = capi_response.capi_data['commander'].get('debt', 0)
# 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 ''
if err:
play_bad = True
# 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'
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:
logger.error('There was no response in the queue!')
# TODO: Set status text
return
logger.error('There was no response in the queue!')
# TODO: Set status text
return
except companion.CredentialsError:
# Redirected back to Auth server - force full re-authentication
companion.session.dump(r)
companion.session.invalidate()
companion.session.retrying = False
companion.session.login()
@ -1097,7 +1112,7 @@ class AppWindow(object):
# Companion API problem
except companion.ServerLagging as e:
err = str(e)
if retrying:
if capi_response.retrying:
self.status['text'] = err
play_bad = True
@ -1124,13 +1139,13 @@ class AppWindow(object):
if not err: # not self.status['text']: # no errors
# 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()
# Update Odyssey Suit data
companion.session.suit_update(data)
companion.session.suit_update(capi_response.capi_data)
self.update_suit_text()
self.suit_show_if_set()
@ -1413,7 +1428,9 @@ class AppWindow(object):
if time() < self.capi_query_holdoff_time:
# Update button in main window
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)
else:

View File

@ -473,33 +473,52 @@ class Auth(object):
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."""
endpoint: str # The CAPI query to perform.
querytime: int # When this query is considered to have started (time_t).
play_sound: bool # Whether to play good/bad sounds for success/failure.
auto_update: bool # Whether this was automatically triggered.
def __init__(self, endpoint: str, querytime: int, play_sound: bool = False, auto_update: bool = False):
self.endpoint = endpoint
self.querytime = querytime
self.play_sound = play_sound
self.auto_update = auto_update
def __init__(
self, endpoint: str,
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.endpoint: str = endpoint # The CAPI query to perform.
class EDMCCAPIResponse():
class EDMCCAPIResponse(EDMCCAPIReturn):
"""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."""
def __init__(self, message, exception=None):
self.message = message
self.exception = exception
def __init__(
self, message: str,
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):
@ -701,6 +720,7 @@ class Session(object):
if r.url.startswith(FRONTIER_AUTH_SERVER):
logger.info('Redirected back to Auth Server')
self.dump(r)
raise CredentialsError('Redirected back to Auth Server')
elif 500 <= r.status_code < 600:
@ -814,49 +834,59 @@ class Session(object):
break
logger.trace_if('capi.worker', f'Processing query: {query.endpoint}')
data: CAPIData
capi_data: CAPIData
if query.endpoint == self._CAPI_PATH_STATION:
try:
data = capi_station_queries()
capi_data = capi_station_queries()
except Exception as e:
self.capi_response_queue.put(
(
CAPIFailedRequest(
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:
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:
try:
data = capi_single_query(self.FRONTIER_CAPI_PATH_PROFILE)
capi_data = capi_single_query(self.FRONTIER_CAPI_PATH_PROFILE)
except Exception as e:
self.capi_response_queue.put(
(
CAPIFailedRequest(
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:
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>>')
@ -867,15 +897,17 @@ class Session(object):
"""Ask the CAPI query thread to finish."""
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.
: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 endpoint: The CAPI endpoint to query, might be a pseudo-value.
:param auto_update: Whether this request was triggered automatically.
:return:
"""
logger.trace_if('capi.query', f'Performing query for endpoint "{endpoint}"')
if self.state == Session.STATE_INIT:
@ -894,27 +926,40 @@ class Session(object):
self.capi_query_queue.put(
EDMCCAPIRequest(
endpoint=endpoint,
retrying=retrying,
querytime=querytime,
play_sound=play_sound,
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.
: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 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.
: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 auto_update: Whether this request was triggered automatically.
"""
@ -923,6 +968,7 @@ class Session(object):
EDMCCAPIRequest(
endpoint=self._CAPI_PATH_STATION,
querytime=querytime,
retrying=retrying,
play_sound=play_sound,
auto_update=auto_update
)