From 255b1913d82e6c9d93b4fed7ae43acd8146e9e76 Mon Sep 17 00:00:00 2001 From: aussig Date: Wed, 21 Dec 2022 12:50:13 +0000 Subject: [PATCH 1/5] Initial implementation of CAPI /fleetcarrier endpoint --- EDMarketConnector.py | 5 +++++ companion.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 8dd45f8c..bc8d2f29 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1081,6 +1081,11 @@ class AppWindow(object): query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME, play_sound=play_sound ) + logger.trace_if('capi.worker', 'Calling companion.session.fleetcarrier') + companion.session.fleetcarrier( + query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME, + play_sound=play_sound + ) def capi_handle_response(self, event=None): # noqa: C901, CCR001 """Handle the resulting data from a CAPI query.""" diff --git a/companion.py b/companion.py index 1c68e38b..650aa2c9 100644 --- a/companion.py +++ b/companion.py @@ -612,6 +612,8 @@ class Session(object): FRONTIER_CAPI_PATH_PROFILE = '/profile' FRONTIER_CAPI_PATH_MARKET = '/market' FRONTIER_CAPI_PATH_SHIPYARD = '/shipyard' + FRONTIER_CAPI_PATH_FLEETCARRIER = '/fleetcarrier' + # This is a dummy value, to signal to Session.capi_query_worker that we # the 'station' triplet of queries. _CAPI_PATH_STATION = '_edmc_station' @@ -949,6 +951,9 @@ class Session(object): if query.endpoint == self._CAPI_PATH_STATION: capi_data = capi_station_queries(query.capi_host) + elif query.endpoint == self.FRONTIER_CAPI_PATH_FLEETCARRIER: + capi_data = capi_single_query(query.capi_host, self.FRONTIER_CAPI_PATH_FLEETCARRIER) + else: capi_data = capi_single_query(query.capi_host, self.FRONTIER_CAPI_PATH_PROFILE) @@ -1020,6 +1025,35 @@ class Session(object): auto_update=auto_update ) ) + + def fleetcarrier( + self, query_time: int, tk_response_event: Optional[str] = None, + play_sound: bool = False, auto_update: bool = False + ) -> None: + """ + Perform CAPI query for fleetcarrier data. + + :param query_time: When this query was initiated. + :param tk_response_event: Name of tk event to generate when response queued. + :param play_sound: Whether the app should play a sound on error. + :param auto_update: Whether this request was triggered automatically. + """ + capi_host = self.capi_host_for_galaxy() + if not capi_host: + return + + # Ask the thread worker to perform a fleetcarrier query + logger.trace_if('capi.worker', 'Enqueueing fleetcarrier request') + self.capi_request_queue.put( + EDMCCAPIRequest( + capi_host=capi_host, + endpoint=self.FRONTIER_CAPI_PATH_FLEETCARRIER, + tk_response_event=tk_response_event, + query_time=query_time, + play_sound=play_sound, + auto_update=auto_update + ) + ) ###################################################################### ###################################################################### From 5b38a96828c1a3b8fe5b95e146223702794820b7 Mon Sep 17 00:00:00 2001 From: aussig Date: Wed, 21 Dec 2022 13:15:08 +0000 Subject: [PATCH 2/5] Initial implementation of CAPI /fleetcarrier endpoint --- EDMarketConnector.py | 5 +++++ companion.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 8dd45f8c..bc8d2f29 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1081,6 +1081,11 @@ class AppWindow(object): query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME, play_sound=play_sound ) + logger.trace_if('capi.worker', 'Calling companion.session.fleetcarrier') + companion.session.fleetcarrier( + query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME, + play_sound=play_sound + ) def capi_handle_response(self, event=None): # noqa: C901, CCR001 """Handle the resulting data from a CAPI query.""" diff --git a/companion.py b/companion.py index 1c68e38b..650aa2c9 100644 --- a/companion.py +++ b/companion.py @@ -612,6 +612,8 @@ class Session(object): FRONTIER_CAPI_PATH_PROFILE = '/profile' FRONTIER_CAPI_PATH_MARKET = '/market' FRONTIER_CAPI_PATH_SHIPYARD = '/shipyard' + FRONTIER_CAPI_PATH_FLEETCARRIER = '/fleetcarrier' + # This is a dummy value, to signal to Session.capi_query_worker that we # the 'station' triplet of queries. _CAPI_PATH_STATION = '_edmc_station' @@ -949,6 +951,9 @@ class Session(object): if query.endpoint == self._CAPI_PATH_STATION: capi_data = capi_station_queries(query.capi_host) + elif query.endpoint == self.FRONTIER_CAPI_PATH_FLEETCARRIER: + capi_data = capi_single_query(query.capi_host, self.FRONTIER_CAPI_PATH_FLEETCARRIER) + else: capi_data = capi_single_query(query.capi_host, self.FRONTIER_CAPI_PATH_PROFILE) @@ -1020,6 +1025,35 @@ class Session(object): auto_update=auto_update ) ) + + def fleetcarrier( + self, query_time: int, tk_response_event: Optional[str] = None, + play_sound: bool = False, auto_update: bool = False + ) -> None: + """ + Perform CAPI query for fleetcarrier data. + + :param query_time: When this query was initiated. + :param tk_response_event: Name of tk event to generate when response queued. + :param play_sound: Whether the app should play a sound on error. + :param auto_update: Whether this request was triggered automatically. + """ + capi_host = self.capi_host_for_galaxy() + if not capi_host: + return + + # Ask the thread worker to perform a fleetcarrier query + logger.trace_if('capi.worker', 'Enqueueing fleetcarrier request') + self.capi_request_queue.put( + EDMCCAPIRequest( + capi_host=capi_host, + endpoint=self.FRONTIER_CAPI_PATH_FLEETCARRIER, + tk_response_event=tk_response_event, + query_time=query_time, + play_sound=play_sound, + auto_update=auto_update + ) + ) ###################################################################### ###################################################################### From 8b30ba83316345a004139cff32b8e6098629ee5e Mon Sep 17 00:00:00 2001 From: aussig Date: Wed, 21 Dec 2022 13:43:38 +0000 Subject: [PATCH 3/5] Initial implementation of CAPI /fleetcarrier endpoint --- EDMarketConnector.py | 5 +++++ companion.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 8dd45f8c..bc8d2f29 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1081,6 +1081,11 @@ class AppWindow(object): query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME, play_sound=play_sound ) + logger.trace_if('capi.worker', 'Calling companion.session.fleetcarrier') + companion.session.fleetcarrier( + query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME, + play_sound=play_sound + ) def capi_handle_response(self, event=None): # noqa: C901, CCR001 """Handle the resulting data from a CAPI query.""" diff --git a/companion.py b/companion.py index 725c9101..ebdbe868 100644 --- a/companion.py +++ b/companion.py @@ -612,6 +612,8 @@ class Session(object): FRONTIER_CAPI_PATH_PROFILE = '/profile' FRONTIER_CAPI_PATH_MARKET = '/market' FRONTIER_CAPI_PATH_SHIPYARD = '/shipyard' + FRONTIER_CAPI_PATH_FLEETCARRIER = '/fleetcarrier' + # This is a dummy value, to signal to Session.capi_query_worker that we # the 'station' triplet of queries. _CAPI_PATH_STATION = '_edmc_station' @@ -952,6 +954,9 @@ class Session(object): if query.endpoint == self._CAPI_PATH_STATION: capi_data = capi_station_queries(query.capi_host) + elif query.endpoint == self.FRONTIER_CAPI_PATH_FLEETCARRIER: + capi_data = capi_single_query(query.capi_host, self.FRONTIER_CAPI_PATH_FLEETCARRIER) + else: capi_data = capi_single_query(query.capi_host, self.FRONTIER_CAPI_PATH_PROFILE) @@ -1023,6 +1028,35 @@ class Session(object): auto_update=auto_update ) ) + + def fleetcarrier( + self, query_time: int, tk_response_event: Optional[str] = None, + play_sound: bool = False, auto_update: bool = False + ) -> None: + """ + Perform CAPI query for fleetcarrier data. + + :param query_time: When this query was initiated. + :param tk_response_event: Name of tk event to generate when response queued. + :param play_sound: Whether the app should play a sound on error. + :param auto_update: Whether this request was triggered automatically. + """ + capi_host = self.capi_host_for_galaxy() + if not capi_host: + return + + # Ask the thread worker to perform a fleetcarrier query + logger.trace_if('capi.worker', 'Enqueueing fleetcarrier request') + self.capi_request_queue.put( + EDMCCAPIRequest( + capi_host=capi_host, + endpoint=self.FRONTIER_CAPI_PATH_FLEETCARRIER, + tk_response_event=tk_response_event, + query_time=query_time, + play_sound=play_sound, + auto_update=auto_update + ) + ) ###################################################################### ###################################################################### From fb78ff3820dc1635fccaa8902d19bd85fc4af177 Mon Sep 17 00:00:00 2001 From: aussig Date: Wed, 21 Dec 2022 20:20:14 +0000 Subject: [PATCH 4/5] Handling of /fleetcarrier CAPI endpoint and implementation of plugin callback function --- EDMarketConnector.py | 23 ++++++++++++++++++++++- plug.py | 26 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index bc8d2f29..5193a0d6 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1112,7 +1112,28 @@ class AppWindow(object): raise ValueError(msg) # Validation - if 'commander' not in capi_response.capi_data: + if capi_response.capi_data.source_endpoint == companion.session.FRONTIER_CAPI_PATH_FLEETCARRIER: + if 'name' not in capi_response.capi_data: + # LANG: No data was returned for the fleetcarrier from the Frontier CAPI + err = self.status['text'] = _('CAPI: No fleetcarrier data returned') + + elif not capi_response.capi_data.get('name', {}).get('callsign'): + # LANG: We didn't have the fleetcarrier callsign when we should have + err = self.status['text'] = _("CAPI: Fleetcarrier data incomplete") # Shouldn't happen + + else: + if __debug__: # Recording + companion.session.dump_capi_data(capi_response.capi_data) + + err = plug.notify_capi_fleetcarrierdata(capi_response.capi_data, monitor.is_beta) + self.status['text'] = err and err or '' + if err: + play_bad = True + + # TODO: Need to set a different holdoff time for the FC CAPI request + self.capi_query_holdoff_time = capi_response.query_time + companion.capi_query_cooldown + + elif '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') diff --git a/plug.py b/plug.py index deeb6ebe..9b68082c 100644 --- a/plug.py +++ b/plug.py @@ -401,6 +401,32 @@ def notify_capidata( return error +def notify_capi_fleetcarrierdata( + data: companion.CAPIData, + is_beta: bool +) -> Optional[str]: + """ + Send the latest CAPI Fleetcarrier data from the FD servers to each plugin. + + :param data: + :param is_beta: whether the player is in a Beta universe. + :returns: Error message from the first plugin that returns one (if any) + """ + error = None + for plugin in PLUGINS: + fc_data = plugin._get_func('capi_fleetcarrier') + + if fc_data: + try: + newerror = fc_data(data, is_beta) + error = error or newerror + + except Exception: + logger.exception(f'Plugin "{plugin.name}" failed on receiving Fleetcarrier data') + + return error + + def show_error(err: str) -> None: """ Display an error message in the status line of the main window. From c85ddbac9a6caec272ace3aad9ac1eca122b0958 Mon Sep 17 00:00:00 2001 From: aussig Date: Wed, 21 Dec 2022 22:58:25 +0000 Subject: [PATCH 5/5] Fleetcarrier CAPI now triggered by certain carrier journal events, and throttled on its own cooldown (15 mins) --- EDMarketConnector.py | 65 ++++++++++++++++++++++++++++++++++++++++---- companion.py | 1 + 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 5193a0d6..0373d4b4 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -476,6 +476,8 @@ class AppWindow(object): def __init__(self, master: tk.Tk): # noqa: C901, CCR001 # TODO - can possibly factor something out self.capi_query_holdoff_time = config.get_int('querytime', default=0) + companion.capi_query_cooldown + self.capi_fleetcarrier_query_holdoff_time = config.get_int('fleetcarrierquerytime', default=0) \ + + companion.capi_fleetcarrier_query_cooldown self.w = master self.w.title(applongname) @@ -1081,10 +1083,55 @@ class AppWindow(object): query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME, play_sound=play_sound ) + + def capi_request_fleetcarrier_data(self, event=None) -> None: + """ + Perform CAPI fleetcarrier data retrieval and associated actions. + + This is triggered by certain FleetCarrier journal events + + :param event: Tk generated event details. + """ + logger.trace_if('capi.worker', 'Begin') + should_return, new_data = killswitch.check_killswitch('capi.fleetcarrier', {}) + if should_return: + logger.warning('capi.fleetcarrier has been disabled via killswitch. Returning.') + self.status['text'] = 'CAPI fleetcarrier disabled by killswitch' + hotkeymgr.play_bad() + return + + if not monitor.cmdr: + logger.trace_if('capi.worker', 'Aborting Query: Cmdr unknown') + # LANG: CAPI queries aborted because Cmdr name is unknown + self.status['text'] = _('CAPI query aborted: Cmdr name unknown') + return + + if not monitor.mode: + logger.trace_if('capi.worker', 'Aborting Query: Game Mode unknown') + # LANG: CAPI queries aborted because game mode unknown + self.status['text'] = _('CAPI query aborted: Game mode unknown') + return + + if monitor.state['GameVersion'] is None: + logger.trace_if('capi.worker', 'Aborting Query: GameVersion unknown') + # LANG: CAPI queries aborted because GameVersion unknown + self.status['text'] = _('CAPI query aborted: GameVersion unknown') + return + + if not companion.session.retrying: + if time() < self.capi_fleetcarrier_query_holdoff_time: # Was invoked while in cooldown + return + + # LANG: Status - Attempting to retrieve data from Frontier CAPI + self.status['text'] = _('Fetching data...') + self.w.update_idletasks() + + query_time = int(time()) + logger.trace_if('capi.worker', 'Requesting fleetcarrier data') + config.set('fleetcarrierquerytime', query_time) logger.trace_if('capi.worker', 'Calling companion.session.fleetcarrier') companion.session.fleetcarrier( - query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME, - play_sound=play_sound + query_time=query_time, tk_response_event=self._CAPI_RESPONSE_TK_EVENT_NAME ) def capi_handle_response(self, event=None): # noqa: C901, CCR001 @@ -1111,8 +1158,9 @@ class AppWindow(object): logger.error(msg) raise ValueError(msg) - # Validation if capi_response.capi_data.source_endpoint == companion.session.FRONTIER_CAPI_PATH_FLEETCARRIER: + # Fleetcarrier CAPI response + # Validation if 'name' not in capi_response.capi_data: # LANG: No data was returned for the fleetcarrier from the Frontier CAPI err = self.status['text'] = _('CAPI: No fleetcarrier data returned') @@ -1130,9 +1178,11 @@ class AppWindow(object): if err: play_bad = True - # TODO: Need to set a different holdoff time for the FC CAPI request - self.capi_query_holdoff_time = capi_response.query_time + companion.capi_query_cooldown + self.capi_fleetcarrier_query_holdoff_time = capi_response.query_time \ + + companion.capi_fleetcarrier_query_cooldown + # Other CAPI response + # Validation elif '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 @@ -1515,6 +1565,11 @@ class AppWindow(object): if not should_return: self.w.after(int(SERVER_RETRY * 1000), self.capi_request_data) + if entry['event'] in ['CarrierBuy', 'CarrierStats', 'CarrierTradeOrder']: # 'CargoTransfer' too? + should_return, new_data = killswitch.check_killswitch('capi.fleetcarrier', {}) + if not should_return: + self.w.after(int(SERVER_RETRY * 1000), self.capi_request_fleetcarrier_data) + if entry['event'] == 'ShutDown': # Enable WinSparkle automatic update checks # NB: Do this blindly, in case option got changed whilst in-game diff --git a/companion.py b/companion.py index ebdbe868..49c20057 100644 --- a/companion.py +++ b/companion.py @@ -46,6 +46,7 @@ else: capi_query_cooldown = 60 # Minimum time between (sets of) CAPI queries +capi_fleetcarrier_query_cooldown = 60 * 15 # Minimum time between CAPI fleetcarrier queries capi_default_requests_timeout = 10 auth_timeout = 30 # timeout for initial auth