From 255b1913d82e6c9d93b4fed7ae43acd8146e9e76 Mon Sep 17 00:00:00 2001 From: aussig Date: Wed, 21 Dec 2022 12:50:13 +0000 Subject: [PATCH 01/20] 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 02/20] 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 03/20] 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 04/20] 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 05/20] 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 From eedf4febdf33de8fd99be5e95c3f8a62323a7a4f Mon Sep 17 00:00:00 2001 From: aussig Date: Wed, 21 Dec 2022 23:00:35 +0000 Subject: [PATCH 06/20] 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 8e5953408e5b4c288d40431ad9af30cb65c38d1a Mon Sep 17 00:00:00 2001 From: aussig Date: Wed, 21 Dec 2022 23:00:35 +0000 Subject: [PATCH 07/20] 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 6ebdea1a6cd262c8d83bb1a0af614ccc04e26a43 Mon Sep 17 00:00:00 2001 From: aussig Date: Wed, 21 Dec 2022 23:00:36 +0000 Subject: [PATCH 08/20] 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 From a551da59a35777738a505e6886acca5a45194c1a Mon Sep 17 00:00:00 2001 From: aussig Date: Thu, 22 Dec 2022 08:57:50 +0000 Subject: [PATCH 09/20] Documentation for capi_fleetcarrier() plugin callback function --- PLUGINS.md | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index dd71ca28..f768b7aa 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -99,8 +99,8 @@ liable to change without notice. `from prefs import prefsVersion` - to allow for versioned preferences. `from companion import CAPIData, SERVER_LIVE, SERVER_LEGACY, SERVER_BETA` - -`CAPIData` is the actual type of `data` as passed into `cmdr_data()` and -`cmdr_data_legacy()`. +`CAPIData` is the actual type of `data` as passed into `cmdr_data()`, +`cmdr_data_legacy()` and `capi_fleetcarrier()`. See [Commander Data from Frontier CAPI](#commander-data-from-frontier-capi)) for further information. @@ -561,9 +561,9 @@ See [Avoiding potential pitfalls](#avoiding-potential-pitfalls). ### Events Once you have created your plugin and EDMarketConnector has loaded it there -are four other functions you can define to be notified by EDMarketConnector +are five other functions you can define to be notified by EDMarketConnector when something happens: `journal_entry()`, `journal_entry_cqc()`, -`dashboard_entry()` and `cmdr_data()`. +`dashboard_entry()`, `cmdr_data()` and `capi_fleetcarrier()`. Your events all get called on the main Tkinter loop so be sure not to block for very long or the app will appear to freeze. If you have a long running @@ -940,6 +940,37 @@ def cmdr_data(data, is_beta): | :-------- | :--------------: | :------------------------------------------------------------------------------------------------------- | | `data` | `CAPIData` | `/profile` API response, with `/market` and `/shipyard` added under the keys `marketdata` and `shipdata` | | `is_beta` | `bool` | If the game is currently in beta | + +If a plugin has a `capi_fleetcarrier()` function it gets called when the application has just fetched fresh Fleetcarrier data from Frontier's CAPI servers. This is done when `CarrierBuy`, `CarrierStats` or `CarrierTradeOrder` events are detected in the Player Journal. To avoid flooding Frontier's CAPI server, a throttle is applied to ensure a significant interval between requests (currently 15 mins). + +```python +from companion import CAPIData, SERVER_LIVE, SERVER_LEGACY, SERVER_BETA + +def capi_fleetcarrier(data, is_beta): + """ + We have new data on our Fleet Carrier + """ + if data.get('name') is None or data['name'].get('callsign') is None: + raise ValueError("this isn't possible") + + logger.info(data['name']['callsign']) + + # Determining source galaxy for the data + if data.source_host == SERVER_LIVE: + ... + + elif data.source_host == SERVER_BETA: + ... + + elif data.source_host == SERVER_LEGACY: + ... +``` + +| Parameter | Type | Description | +| :-------- | :--------------: | :------------------------------------------------------------------------------------------------------- | +| `data` | `CAPIData` | `/fleetcarrier` API response | +| `is_beta` | `bool` | If the game is currently in beta | + `CAPIData` is a class, which you can `from companion import CAPIDATA`, and is based on `UserDict`. The actual data from CAPI queries is thus accessible via python's normal `data['key']` syntax. However, being a class, it can also @@ -947,12 +978,14 @@ have extra properties, such as `source_host`, as shown above. Plugin authors are free to use *that* property, **but MUST NOT rely on any other extra properties present in `CAPIData`, they are for internal use only.** -The contents of `data` will always have at least the data returned by a CAPI +In the `cmdr_data()` callback, the contents of `data` will always have at least the data returned by a CAPI `/profile` query. If the player is docked at a station, and the relevant services are available then the `lastStarport` key's value will have been augmented with `/market` and/or `/shipyard` data. **But do not assume this will always be the case**. +In the `capi_fleetcarrier()` callback, the contents of `data` will be the unmodified response from the CAPI `/fleetcarrier` query. See [this documentation](https://github.com/Athanasius/fd-api/blob/main/docs/FrontierDevelopments-CAPI-endpoints.md) for details of the expected content structure and data. + If there is a killswitch in effect for some of the CAPI endpoints, then the data passed to this function might not be as complete as you expect. Code defensively. @@ -1082,7 +1115,7 @@ time after the corresponding `journal_entry()` event. ## Error messages You can display an error in EDMarketConnector's status area by returning a -string from your `journal_entry()`, `dashboard_entry()` or `cmdr_data()` +string from your `journal_entry()`, `dashboard_entry()`, `cmdr_data()` or `capi_fleetcarrier()` function, or asynchronously (e.g. from a "worker" thread that is performing a long-running operation) by calling `plug.show_error()`. Either method will cause the "bad" sound to be played (unless the user has muted sound). @@ -1311,7 +1344,7 @@ versions of EDMarketConnector: `Settings` > `Plugins` tab. - Check that callback functions `plugin_prefs`, `prefs_changed`, - `journal_entry`, `dashboard_entry` and `cmdr_data`, if used, are declared + `journal_entry`, `dashboard_entry`, `cmdr_data` and `capi_fleetcarrier`, if used, are declared with the correct number of arguments. Older versions of this app were tolerant of missing arguments in these function declarations. From 9e17c46b25e258c4a150147d48e5dde1d0b2c230 Mon Sep 17 00:00:00 2001 From: aussig Date: Thu, 22 Dec 2022 16:21:42 +0000 Subject: [PATCH 10/20] Implement suggestions from PR #1773. --- EDMarketConnector.py | 23 +++++++++-------------- PLUGINS.md | 5 ++--- companion.py | 4 +++- plug.py | 14 ++++++-------- 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index fc4bca2c..f2d6ac4d 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -916,7 +916,7 @@ class AppWindow(object): def login(self): """Initiate CAPI/Frontier login and set other necessary state.""" - should_return, new_data = killswitch.check_killswitch('capi.auth', {}) + should_return, __ = killswitch.check_killswitch('capi.auth', {}) if should_return: logger.warning('capi.auth has been disabled via killswitch. Returning.') self.status['text'] = 'CAPI auth disabled by killswitch' @@ -1007,7 +1007,7 @@ class AppWindow(object): :param event: Tk generated event details. """ logger.trace_if('capi.worker', 'Begin') - should_return, new_data = killswitch.check_killswitch('capi.auth', {}) + should_return, __ = killswitch.check_killswitch('capi.auth', {}) if should_return: logger.warning('capi.auth has been disabled via killswitch. Returning.') self.status['text'] = 'CAPI auth disabled by killswitch' @@ -1093,7 +1093,7 @@ class AppWindow(object): :param event: Tk generated event details. """ logger.trace_if('capi.worker', 'Begin') - should_return, new_data = killswitch.check_killswitch('capi.fleetcarrier', {}) + should_return, __ = killswitch.check_killswitch('capi.request.fleetcarrier', {}) if should_return: logger.warning('capi.fleetcarrier has been disabled via killswitch. Returning.') self.status['text'] = 'CAPI fleetcarrier disabled by killswitch' @@ -1106,12 +1106,6 @@ class AppWindow(object): 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 @@ -1120,6 +1114,7 @@ class AppWindow(object): if not companion.session.retrying: if time() < self.capi_fleetcarrier_query_holdoff_time: # Was invoked while in cooldown + logger.debug('CAPI fleetcarrier query aborted, too soon since last request') return # LANG: Status - Attempting to retrieve data from Frontier CAPI @@ -1173,7 +1168,7 @@ class AppWindow(object): if __debug__: # Recording companion.session.dump_capi_data(capi_response.capi_data) - err = plug.notify_capi_fleetcarrierdata(capi_response.capi_data, monitor.is_beta) + err = plug.notify_capi_fleetcarrierdata(capi_response.capi_data) self.status['text'] = err and err or '' if err: play_bad = True @@ -1306,7 +1301,7 @@ class AppWindow(object): if err: play_bad = True - should_return, new_data = killswitch.check_killswitch('capi.request./market', {}) + should_return, __ = killswitch.check_killswitch('capi.request./market', {}) if should_return: logger.warning("capi.request./market has been disabled by killswitch. Returning.") @@ -1561,12 +1556,12 @@ class AppWindow(object): auto_update = True if auto_update: - should_return, new_data = killswitch.check_killswitch('capi.auth', {}) + should_return, __ = killswitch.check_killswitch('capi.auth', {}) 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 entry['event'] in ('CarrierBuy', 'CarrierStats'): + should_return, __ = killswitch.check_killswitch('capi.request.fleetcarrier', {}) if not should_return: self.w.after(int(SERVER_RETRY * 1000), self.capi_request_fleetcarrier_data) diff --git a/PLUGINS.md b/PLUGINS.md index f768b7aa..3bb6f2ee 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -941,12 +941,12 @@ def cmdr_data(data, is_beta): | `data` | `CAPIData` | `/profile` API response, with `/market` and `/shipyard` added under the keys `marketdata` and `shipdata` | | `is_beta` | `bool` | If the game is currently in beta | -If a plugin has a `capi_fleetcarrier()` function it gets called when the application has just fetched fresh Fleetcarrier data from Frontier's CAPI servers. This is done when `CarrierBuy`, `CarrierStats` or `CarrierTradeOrder` events are detected in the Player Journal. To avoid flooding Frontier's CAPI server, a throttle is applied to ensure a significant interval between requests (currently 15 mins). +If a plugin has a `capi_fleetcarrier()` function it gets called when the application has just fetched fresh Fleetcarrier data from Frontier's CAPI servers. This is done when `CarrierBuy`or `CarrierStats` events are detected in the Player Journal. To avoid flooding Frontier's CAPI server, a throttle is applied to ensure a significant interval between requests (currently 15 mins). Also be aware that calls to the `/fleetcarrier` CAPI endpoint have been reported to take a very long time to return, potentially up to 20 minutes. Delays in responses from this endpoint could delay other CAPI queries. ```python from companion import CAPIData, SERVER_LIVE, SERVER_LEGACY, SERVER_BETA -def capi_fleetcarrier(data, is_beta): +def capi_fleetcarrier(data): """ We have new data on our Fleet Carrier """ @@ -969,7 +969,6 @@ def capi_fleetcarrier(data, is_beta): | Parameter | Type | Description | | :-------- | :--------------: | :------------------------------------------------------------------------------------------------------- | | `data` | `CAPIData` | `/fleetcarrier` API response | -| `is_beta` | `bool` | If the game is currently in beta | `CAPIData` is a class, which you can `from companion import CAPIDATA`, and is based on `UserDict`. The actual data from CAPI queries is thus accessible diff --git a/companion.py b/companion.py index 26f46287..70b7e1cc 100644 --- a/companion.py +++ b/companion.py @@ -48,6 +48,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 +capi_fleetcarrier_requests_timeout = 60 auth_timeout = 30 # timeout for initial auth # Used by both class Auth and Session @@ -960,7 +961,8 @@ class Session(object): 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) + capi_data = capi_single_query(query.capi_host, self.FRONTIER_CAPI_PATH_FLEETCARRIER, + timeout=capi_fleetcarrier_requests_timeout) else: capi_data = capi_single_query(query.capi_host, self.FRONTIER_CAPI_PATH_PROFILE) diff --git a/plug.py b/plug.py index 9b68082c..d77c5fa9 100644 --- a/plug.py +++ b/plug.py @@ -402,23 +402,21 @@ def notify_capidata( def notify_capi_fleetcarrierdata( - data: companion.CAPIData, - is_beta: bool + data: companion.CAPIData ) -> 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. + :param data: The CAPIData returned in the CAPI response :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: + fc_callback = plugin._get_func('capi_fleetcarrier') + if fc_callback is not None and callable(fc_callback): try: - newerror = fc_data(data, is_beta) + # Pass a copy of the CAPIData in case the callee modifies it + newerror = fc_callback(copy.deepcopy(data)) error = error or newerror except Exception: From 3e295db06149686571740ced6459f71d643a22a6 Mon Sep 17 00:00:00 2001 From: aussig Date: Thu, 22 Dec 2022 17:39:20 +0000 Subject: [PATCH 11/20] Typing of return values from killswitch.check_killswitch() --- EDMarketConnector.py | 29 ++++++++++++++++++++++------- companion.py | 11 ++++++++++- plugins/eddb.py | 5 ++++- plugins/eddn.py | 15 +++++++++++++++ plugins/edsm.py | 11 ++++++++++- plugins/inara.py | 5 +++++ 6 files changed, 66 insertions(+), 10 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index f2d6ac4d..4cabfa94 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -16,7 +16,7 @@ from builtins import object, str from os import chdir, environ from os.path import dirname, join from time import localtime, strftime, time -from typing import TYPE_CHECKING, Literal, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Tuple, Union # Have this as early as possible for people running EDMarketConnector.exe # from cmd.exe or a bat file or similar. Else they might not be in the correct @@ -916,7 +916,10 @@ class AppWindow(object): def login(self): """Initiate CAPI/Frontier login and set other necessary state.""" - should_return, __ = killswitch.check_killswitch('capi.auth', {}) + should_return: bool + new_data: Dict[str, Any] = {} + + should_return, new_data = killswitch.check_killswitch('capi.auth', {}) if should_return: logger.warning('capi.auth has been disabled via killswitch. Returning.') self.status['text'] = 'CAPI auth disabled by killswitch' @@ -1007,7 +1010,10 @@ class AppWindow(object): :param event: Tk generated event details. """ logger.trace_if('capi.worker', 'Begin') - should_return, __ = killswitch.check_killswitch('capi.auth', {}) + should_return: bool + new_data: Dict[str, Any] = {} + + should_return, new_data = killswitch.check_killswitch('capi.auth', {}) if should_return: logger.warning('capi.auth has been disabled via killswitch. Returning.') self.status['text'] = 'CAPI auth disabled by killswitch' @@ -1093,7 +1099,10 @@ class AppWindow(object): :param event: Tk generated event details. """ logger.trace_if('capi.worker', 'Begin') - should_return, __ = killswitch.check_killswitch('capi.request.fleetcarrier', {}) + should_return: bool + new_data: Dict[str, Any] = {} + + should_return, new_data = killswitch.check_killswitch('capi.request.fleetcarrier', {}) if should_return: logger.warning('capi.fleetcarrier has been disabled via killswitch. Returning.') self.status['text'] = 'CAPI fleetcarrier disabled by killswitch' @@ -1301,7 +1310,10 @@ class AppWindow(object): if err: play_bad = True - should_return, __ = killswitch.check_killswitch('capi.request./market', {}) + should_return: bool + new_data: Dict[str, Any] = {} + + should_return, new_data = killswitch.check_killswitch('capi.request./market', {}) if should_return: logger.warning("capi.request./market has been disabled by killswitch. Returning.") @@ -1555,13 +1567,16 @@ class AppWindow(object): elif entry['event'] == 'Disembark' and entry.get('Taxi') and entry.get('OnStation'): auto_update = True + should_return: bool + new_data: Dict[str, Any] = {} + if auto_update: - should_return, __ = killswitch.check_killswitch('capi.auth', {}) + should_return, new_data = killswitch.check_killswitch('capi.auth', {}) if not should_return: self.w.after(int(SERVER_RETRY * 1000), self.capi_request_data) if entry['event'] in ('CarrierBuy', 'CarrierStats'): - should_return, __ = killswitch.check_killswitch('capi.request.fleetcarrier', {}) + should_return, new_data = killswitch.check_killswitch('capi.request.fleetcarrier', {}) if not should_return: self.w.after(int(SERVER_RETRY * 1000), self.capi_request_fleetcarrier_data) diff --git a/companion.py b/companion.py index 70b7e1cc..e81e4b39 100644 --- a/companion.py +++ b/companion.py @@ -328,6 +328,9 @@ class Auth(object): """ logger.debug(f'Trying for "{self.cmdr}"') + should_return: bool + new_data: Dict[str, Any] = {} + should_return, new_data = killswitch.check_killswitch('capi.auth', {}) if should_return: logger.warning('capi.auth has been disabled via killswitch. Returning.') @@ -667,6 +670,9 @@ class Session(object): :return: True if login succeeded, False if re-authorization initiated. """ + should_return: bool + new_data: Dict[str, Any] = {} + should_return, new_data = killswitch.check_killswitch('capi.auth', {}) if should_return: logger.warning('capi.auth has been disabled via killswitch. Returning.') @@ -785,6 +791,9 @@ class Session(object): :return: The resulting CAPI data, of type CAPIData. """ capi_data: CAPIData = CAPIData() + should_return: bool + new_data: Dict[str, Any] = {} + should_return, new_data = killswitch.check_killswitch('capi.request.' + capi_endpoint, {}) if should_return: logger.warning(f"capi.request.{capi_endpoint} has been disabled by killswitch. Returning.") @@ -962,7 +971,7 @@ class Session(object): elif query.endpoint == self.FRONTIER_CAPI_PATH_FLEETCARRIER: capi_data = capi_single_query(query.capi_host, self.FRONTIER_CAPI_PATH_FLEETCARRIER, - timeout=capi_fleetcarrier_requests_timeout) + timeout=capi_fleetcarrier_requests_timeout) else: capi_data = capi_single_query(query.capi_host, self.FRONTIER_CAPI_PATH_PROFILE) diff --git a/plugins/eddb.py b/plugins/eddb.py index e050df32..55ba4be3 100644 --- a/plugins/eddb.py +++ b/plugins/eddb.py @@ -42,7 +42,7 @@ # ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# # ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# import tkinter -from typing import TYPE_CHECKING, Any, Mapping, MutableMapping, Optional +from typing import TYPE_CHECKING, Any, Dict, Mapping, MutableMapping, Optional import requests @@ -168,6 +168,9 @@ def journal_entry( # noqa: CCR001 :param state: `monitor.state` :return: None if no error, else an error string. """ + should_return: bool + new_entry: Dict[str, Any] = {} + should_return, new_entry = killswitch.check_killswitch('plugins.eddb.journal', entry) if should_return: # LANG: Journal Processing disabled due to an active killswitch diff --git a/plugins/eddn.py b/plugins/eddn.py index b1df07b7..cecdd0e0 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -402,6 +402,9 @@ class EDDNSender: :return: `True` for "now remove this message from the queue" """ logger.trace_if("plugin.eddn.send", "Sending message") + should_return: bool + new_data: Dict[str, Any] = {} + should_return, new_data = killswitch.check_killswitch('plugins.eddn.send', json.loads(msg)) if should_return: logger.warning('eddn.send has been disabled via killswitch. Returning.') @@ -635,6 +638,9 @@ class EDDN: :param data: a dict containing the starport data :param is_beta: whether or not we're currently in beta mode """ + should_return: bool + new_data: Dict[str, Any] = {} + should_return, new_data = killswitch.check_killswitch('capi.request./market', {}) if should_return: logger.warning("capi.request./market has been disabled by killswitch. Returning.") @@ -766,6 +772,9 @@ class EDDN: :param data: dict containing the outfitting data :param is_beta: whether or not we're currently in beta mode """ + should_return: bool + new_data: Dict[str, Any] = {} + should_return, new_data = killswitch.check_killswitch('capi.request./shipyard', {}) if should_return: logger.warning("capi.request./shipyard has been disabled by killswitch. Returning.") @@ -832,6 +841,9 @@ class EDDN: :param data: dict containing the shipyard data :param is_beta: whether or not we are in beta mode """ + should_return: bool + new_data: Dict[str, Any] = {} + should_return, new_data = killswitch.check_killswitch('capi.request./shipyard', {}) if should_return: logger.warning("capi.request./shipyard has been disabled by killswitch. Returning.") @@ -2164,6 +2176,9 @@ def journal_entry( # noqa: C901, CCR001 :param state: `dict` - Current `monitor.state` data. :return: `str` - Error message, or `None` if no errors. """ + should_return: bool + new_data: Dict[str, Any] = {} + should_return, new_data = killswitch.check_killswitch('plugins.eddn.journal', entry) if should_return: plug.show_error(_('EDDN journal handler disabled. See Log.')) # LANG: Killswitch disabled EDDN diff --git a/plugins/edsm.py b/plugins/edsm.py index 130a794d..4c6ba02b 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -38,7 +38,7 @@ from datetime import datetime, timedelta, timezone from queue import Queue from threading import Thread from time import sleep -from typing import TYPE_CHECKING, Any, List, Literal, Mapping, MutableMapping, Optional, Set, Tuple, Union, cast +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Mapping, MutableMapping, Optional, Set, Tuple, Union, cast import requests @@ -479,6 +479,9 @@ def journal_entry( # noqa: C901, CCR001 :param state: `monitor.state` :return: None if no error, else an error string. """ + should_return: bool + new_entry: Dict[str, Any] = {} + should_return, new_entry = killswitch.check_killswitch('plugins.edsm.journal', entry, logger) if should_return: # LANG: EDSM plugin - Journal handling disabled by killswitch @@ -759,6 +762,9 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently retrying = 0 while retrying < 3: + should_skip: bool + new_item: Dict[str, Any] = {} + should_skip, new_item = killswitch.check_killswitch( 'plugins.edsm.worker', item if item is not None else cast(Tuple[str, Mapping[str, Any]], ("", {})), @@ -795,6 +801,9 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently # drop events if required by killswitch new_pending = [] for e in pending: + skip: bool + new: Dict[str, Any] = {} + skip, new = killswitch.check_killswitch(f'plugin.edsm.worker.{e["event"]}', e, logger) if skip: continue diff --git a/plugins/inara.py b/plugins/inara.py index f3f32741..163fdcb7 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -385,6 +385,9 @@ def journal_entry( # noqa: C901, CCR001 return '' + should_return: bool + new_entry: Dict[str, Any] = {} + should_return, new_entry = killswitch.check_killswitch('plugins.inara.journal', entry, logger) if should_return: plug.show_error(_('Inara disabled. See Log.')) # LANG: INARA support disabled via killswitch @@ -1536,6 +1539,8 @@ def new_add_event( def clean_event_list(event_list: List[Event]) -> List[Event]: """Check for killswitched events and remove or modify them as requested.""" out = [] + bad: bool + new_event: Dict[str, Any] = {} for e in event_list: bad, new_event = killswitch.check_killswitch(f'plugins.inara.worker.{e.name}', e.data, logger) From 7e726e606a81f9672b73b70f88e87b26318fc1e2 Mon Sep 17 00:00:00 2001 From: aussig Date: Fri, 23 Dec 2022 12:47:35 +0000 Subject: [PATCH 12/20] Store original request cmdr name in 'request_cmdr' in CAPIData --- PLUGINS.md | 99 +++++++++++++++++++++++++++------------------------- companion.py | 17 ++++++--- 2 files changed, 64 insertions(+), 52 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 3bb6f2ee..0deff4f5 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -31,7 +31,7 @@ then you'll need to be using an appropriate version of Python. The current version is listed in the [Environment section of Releasing.md](https://github.com/EDCD/EDMarketConnector/blob/main/docs/Releasing.md#environment). If you're developing your plugin simply against an install of EDMarketConnector -then you'll be relying on the bundled version of Python (it's baked +then you'll be relying on the bundled version of Python (it's baked into the .exe via the py2exe build process). Please be sure to read the [Avoiding potential pitfalls](#avoiding-potential-pitfalls) @@ -40,21 +40,21 @@ EDMarketConnector code including whole application crashes. ## Being aware of core application changes -It is highly advisable to ensure you are aware of all EDMarketConnector -releases, including the pre-releases. The -beta and -rc changelogs will +It is highly advisable to ensure you are aware of all EDMarketConnector +releases, including the pre-releases. The -beta and -rc changelogs will contain valuable information about any forthcoming changes that affect plugins. The easiest way is: 1. Login to [GitHub](https://github.com). 2. Navigate to [EDMarketConnector](https://github.com/EDCD/EDMarketConnector). - 3. Click the 'Watch' (or 'Unwatch' if you previously set up any watches on - us). It's currently (2021-05-13) the left-most button of 3 near the + 3. Click the 'Watch' (or 'Unwatch' if you previously set up any watches on + us). It's currently (2021-05-13) the left-most button of 3 near the top-right of the page. 4. Click 'Custom'. 5. Ensure 'Releases' is selected. 6. Click 'Apply'. -And, of course, either ensure you check your GitHub messages regularly, or +And, of course, either ensure you check your GitHub messages regularly, or have it set up to email you such notifications. You should also keep an eye on [our GitHub Discussions](https://github.com/EDCD/EDMarketConnector/discussions) @@ -113,13 +113,13 @@ from the original files unless specified as allowed in this section. Use `monitor.game_running()` as follows in case a plugin needs to know if we think the game is running. *NB: This is a function, and should be called as such. Using the bare word `game_running` will always be `True`.* - + ``` from monitor import monitor ... if monitor.game_running(): ... -``` +``` `import timeout_session` - provides a method called `new_session` that creates a requests.session with a default timeout on all requests. Recommended to @@ -128,7 +128,7 @@ reduce noise in HTTP requests `from ttkHyperlinkLabel import HyperlinkLabel` and `import myNotebook as nb` - For creating UI elements. -In addition to the above we also explicitly package the following python +In addition to the above we also explicitly package the following python modules for plugin use: - shutil @@ -245,7 +245,7 @@ include variables, and even the returns of functions, in the output. ## Checking core EDMC version -If you have code that needs to act differently under different versions of +If you have code that needs to act differently under different versions of this application then you can check utilise `config.appversion`. Prior to version 5.0.0 this was a simple string. From 5.0.0 onwards it is, @@ -306,7 +306,7 @@ Mac, and `$TMP/EDMarketConnector.log` on Linux. ## Avoiding potential pitfalls -There are a number of things that your code should either do or avoiding +There are a number of things that your code should either do or avoiding doing so as to play nicely with the core EDMarketConnector code and not risk causing application crashes or hangs. @@ -317,12 +317,12 @@ See the section on [packaging extra modules](#your-plugin-directory-name-must-be ### Use a thread for long-running code -By default, your plugin code will be running in the main thread. So, if you -perform some operation that takes significant time (more than a second) you -will be blocking both the core code from continuing *and* any other plugins +By default, your plugin code will be running in the main thread. So, if you +perform some operation that takes significant time (more than a second) you +will be blocking both the core code from continuing *and* any other plugins from running their main-thread code. -This includes any connections to remote services, such as a website or +This includes any connections to remote services, such as a website or remote database. So please place such code within its own thread. See the [EDSM plugin](https://github.com/EDCD/EDMarketConnector/blob/main/plugins/edsm.py) @@ -331,20 +331,20 @@ with a queue to send data, and telling the sub-thread to stop during shutdown. ### All tkinter calls in main thread -The only tkinter calls that should ever be made from a sub-thread are +The only tkinter calls that should ever be made from a sub-thread are `event_generate()` calls to send data back to the main thread. -Any attempt to manipulate tkinter UI elements directly from a sub-thread +Any attempt to manipulate tkinter UI elements directly from a sub-thread will most likely crash the whole program. See the [EDSM plugin](https://github.com/EDCD/EDMarketConnector/blob/main/plugins/edsm.py) -code for an example of using `event_generate()` to cause the plugin main -thread code to update a UI element. Start from the `plugin_app()` +code for an example of using `event_generate()` to cause the plugin main +thread code to update a UI element. Start from the `plugin_app()` implementation. ### Do not call tkinter `event_generate` during shutdown. -However, you must **not** make *any* tkinter `event_generate()` call whilst +However, you must **not** make *any* tkinter `event_generate()` call whilst the application is shutting down. The application shutdown sequence is itself triggered from the `<>` event @@ -352,8 +352,8 @@ handler, and generating another event from any code in, or called from, there causes the application to hang somewhere in the tk libraries. You can detect if the application is shutting down with the boolean -`config.shutting_down`. Note that although this is technically a function -its implementation is of a property on `config.AbstractConfig` and thus you +`config.shutting_down`. Note that although this is technically a function +its implementation is of a property on `config.AbstractConfig` and thus you should treat it as a variable. **Do NOT use**: @@ -365,7 +365,7 @@ should treat it as a variable. # During shutdown ``` -as this will cause the 'During shutdown' branch to *always* be taken, as in +as this will cause the 'During shutdown' branch to *always* be taken, as in this context you're testing if the function exists, and that is always True. So instead use: @@ -396,8 +396,8 @@ your plugin's settings in a platform-independent way. Previously this was done with a single set and two get methods, the new methods provide better type safety. -If you want to maintain compatibility with pre-5.0.0 versions of this -application (please encourage plugin users to update!) then you'll need to +If you want to maintain compatibility with pre-5.0.0 versions of this +application (please encourage plugin users to update!) then you'll need to include this code in at least once in your plugin (no harm in putting it in all modules/files): @@ -670,8 +670,8 @@ cause `state['NavRoute'] = None`, but if you open the galaxy map in-game and cause an automatic re-plot of last route, then a new `NavRoute` event will also be generated and passed to plugins. -[2] - Some data from the CAPI is sometimes returned as a `list` (when all -members are present) and other times as an integer-keyed `dict` (when at +[2] - Some data from the CAPI is sometimes returned as a `list` (when all +members are present) and other times as an integer-keyed `dict` (when at least one member is missing, so the indices are not contiguous). We choose to always convert to the integer-keyed `dict` form so that code utilising the data is simpler. @@ -697,7 +697,7 @@ Journal `ModuleInfo` event. `OnFoot` is an indication as to if the player is on-foot, rather than in a vehicle. -`Component`, `Item`, `Consumable` & `Data` are `dict`s tracking your +`Component`, `Item`, `Consumable` & `Data` are `dict`s tracking your Odyssey MicroResources in your Ship Locker. `BacKPack` contains `dict`s for the same when you're on-foot. @@ -706,10 +706,10 @@ relating to suits and their loadouts. New in version 5.0.1: -`Odyssey` boolean based on the presence of such a flag in the `LoadGame` +`Odyssey` boolean based on the presence of such a flag in the `LoadGame` event. Defaults to `False`, i.e. if no such key in the event. -The previously undocumented `Horizons` boolean is similarly from `LoadGame`, +The previously undocumented `Horizons` boolean is similarly from `LoadGame`, but blindly retrieves the value rather than having a strict default. There'd be an exception if it wasn't there, and the value would be `None`. Note that this is **NOT** the same as the return from @@ -786,8 +786,8 @@ react to either in your plugin code then either compare in a case insensitive manner or check for both. The difference in case allows you to differentiate between the two scenarios. -**NB: Any of these events are passing to `journal_entry_cqc` rather than to -`journal_entry` if player has loaded into Arena (CQC).** +**NB: Any of these events are passing to `journal_entry_cqc` rather than to +`journal_entry` if player has loaded into Arena (CQC).** This event is not sent when EDMarketConnector is running on a different machine so you should not *rely* on receiving this event. @@ -804,15 +804,15 @@ Examples of this are: 1. Every `NavRoute` event contains the full `Route` array as loaded from `NavRoute.json`. - + *NB: There is no indication available when a player cancels a route.* The game itself does not provide any such, not in a Journal event, not in a `Status.json` flag. - + The Journal documentation v28 is incorrect about the event and file being `Route(.json)` the word is `NavRoute`. Also the format of the data is, e.g. - + ```json { "timestamp":"2021-03-10T11:31:37Z", "event":"NavRoute", @@ -826,9 +826,9 @@ Examples of this are: ``` 1. Every `ModuleInfo` event contains the full data as loaded from the - `ModulesInfo.json` file. Note that we use the singular form here to + `ModulesInfo.json` file. Note that we use the singular form here to stay consistent with the Journal event name. - + --- ### Journal entry in CQC @@ -888,7 +888,7 @@ def dashboard_entry(cmdr: str, is_beta: bool, entry: Dict[str, Any]): sys.stderr.write("Hardpoints {}\n".format(is_deployed and "deployed" or "stowed")) ``` -`dashboard_entry()` is called with the latest data from the `Status.json` +`dashboard_entry()` is called with the latest data from the `Status.json` file when an update to that file is detected. This will be when something on the player's cockpit display changes - @@ -983,7 +983,12 @@ services are available then the `lastStarport` key's value will have been augmented with `/market` and/or `/shipyard` data. **But do not assume this will always be the case**. -In the `capi_fleetcarrier()` callback, the contents of `data` will be the unmodified response from the CAPI `/fleetcarrier` query. See [this documentation](https://github.com/Athanasius/fd-api/blob/main/docs/FrontierDevelopments-CAPI-endpoints.md) for details of the expected content structure and data. +In the `capi_fleetcarrier()` callback, the contents of `data` will be the response from the CAPI `/fleetcarrier` query. See [this documentation](https://github.com/Athanasius/fd-api/blob/main/docs/FrontierDevelopments-CAPI-endpoints.md) for details of the expected content structure and data. + +In all cases, `data` will include a `request_cmdr` entry, which will be the +name of the active CMDR _at the point the request was made_. In the case of +a CAPI request taking a long time to return, the user may have switched +CMDR during the request, so this may be different to the current CMDR. If there is a killswitch in effect for some of the CAPI endpoints, then the data passed to this function might not be as complete as you expect. Code @@ -1197,11 +1202,11 @@ Any modules the core application code uses will naturally be packaged, and we explicitly include a small number of additional modules for the use of plugins. -Whilst we would like to make all of the `stdlib` of Python available it is -not automatically packaged into our releases by py2exe. We hope to address -this in the 5.3 release series. In the meantime, if there's anything -missing that you'd like to use, please ask. Yes, this very much means you -need to test your plugins against a Windows installation of the application +Whilst we would like to make all of the `stdlib` of Python available it is +not automatically packaged into our releases by py2exe. We hope to address +this in the 5.3 release series. In the meantime, if there's anything +missing that you'd like to use, please ask. Yes, this very much means you +need to test your plugins against a Windows installation of the application to be sure it will work. See @@ -1351,7 +1356,7 @@ versions of EDMarketConnector: [2to3](https://docs.python.org/3/library/2to3.html) tool can automate much of this work. -We advise *against* making any attempt to have a plugin's code work under -both Python 2.7 and 3.x. We no longer maintain the Python 2.7-based -versions of this application, and you shouldn't support use of them with +We advise *against* making any attempt to have a plugin's code work under +both Python 2.7 and 3.x. We no longer maintain the Python 2.7-based +versions of this application, and you shouldn't support use of them with your plugin. diff --git a/companion.py b/companion.py index e81e4b39..fe12ecde 100644 --- a/companion.py +++ b/companion.py @@ -575,7 +575,8 @@ class EDMCCAPIRequest(EDMCCAPIReturn): self, capi_host: str, endpoint: str, query_time: int, tk_response_event: Optional[str] = None, - play_sound: bool = False, auto_update: bool = False + play_sound: bool = False, auto_update: bool = False, + cmdr: Optional[str] = None ): super().__init__( query_time=query_time, tk_response_event=tk_response_event, @@ -583,6 +584,7 @@ class EDMCCAPIRequest(EDMCCAPIReturn): ) self.capi_host: str = capi_host # The CAPI host to use. self.endpoint: str = endpoint # The CAPI query to perform. + self.cmdr: Optional[str] = cmdr # The CMDR name used for the request. class EDMCCAPIResponse(EDMCCAPIReturn): @@ -590,10 +592,12 @@ class EDMCCAPIResponse(EDMCCAPIReturn): def __init__( self, capi_data: CAPIData, - query_time: int, play_sound: bool = False, auto_update: bool = False + query_time: int, play_sound: bool = False, auto_update: bool = False, + cmdr: Optional[str] = None ): super().__init__(query_time=query_time, play_sound=play_sound, auto_update=auto_update) self.capi_data: CAPIData = capi_data # Frontier CAPI response, possibly augmented (station query) + self.capi_data['request_cmdr'] = cmdr # Inject the CMDR name used for the original request into the data class EDMCCAPIFailedRequest(EDMCCAPIReturn): @@ -993,7 +997,8 @@ class Session(object): capi_data=capi_data, query_time=query.query_time, play_sound=query.play_sound, - auto_update=query.auto_update + auto_update=query.auto_update, + cmdr=query.cmdr ) ) @@ -1041,7 +1046,8 @@ class Session(object): tk_response_event=tk_response_event, query_time=query_time, play_sound=play_sound, - auto_update=auto_update + auto_update=auto_update, + cmdr=monitor.cmdr ) ) @@ -1070,7 +1076,8 @@ class Session(object): tk_response_event=tk_response_event, query_time=query_time, play_sound=play_sound, - auto_update=auto_update + auto_update=auto_update, + cmdr=monitor.cmdr ) ) ###################################################################### From 3c8eca16afd25d432f697ffd9edafeab4babfe46 Mon Sep 17 00:00:00 2001 From: aussig Date: Fri, 23 Dec 2022 13:01:47 +0000 Subject: [PATCH 13/20] Revert unnoticed whitespace modifications to PLUGINS.py --- PLUGINS.md | 97 ++++++++++++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 50 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 0deff4f5..df6d011f 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -31,7 +31,7 @@ then you'll need to be using an appropriate version of Python. The current version is listed in the [Environment section of Releasing.md](https://github.com/EDCD/EDMarketConnector/blob/main/docs/Releasing.md#environment). If you're developing your plugin simply against an install of EDMarketConnector -then you'll be relying on the bundled version of Python (it's baked +then you'll be relying on the bundled version of Python (it's baked into the .exe via the py2exe build process). Please be sure to read the [Avoiding potential pitfalls](#avoiding-potential-pitfalls) @@ -40,21 +40,21 @@ EDMarketConnector code including whole application crashes. ## Being aware of core application changes -It is highly advisable to ensure you are aware of all EDMarketConnector -releases, including the pre-releases. The -beta and -rc changelogs will +It is highly advisable to ensure you are aware of all EDMarketConnector +releases, including the pre-releases. The -beta and -rc changelogs will contain valuable information about any forthcoming changes that affect plugins. The easiest way is: 1. Login to [GitHub](https://github.com). 2. Navigate to [EDMarketConnector](https://github.com/EDCD/EDMarketConnector). - 3. Click the 'Watch' (or 'Unwatch' if you previously set up any watches on - us). It's currently (2021-05-13) the left-most button of 3 near the + 3. Click the 'Watch' (or 'Unwatch' if you previously set up any watches on + us). It's currently (2021-05-13) the left-most button of 3 near the top-right of the page. 4. Click 'Custom'. 5. Ensure 'Releases' is selected. 6. Click 'Apply'. -And, of course, either ensure you check your GitHub messages regularly, or +And, of course, either ensure you check your GitHub messages regularly, or have it set up to email you such notifications. You should also keep an eye on [our GitHub Discussions](https://github.com/EDCD/EDMarketConnector/discussions) @@ -113,13 +113,13 @@ from the original files unless specified as allowed in this section. Use `monitor.game_running()` as follows in case a plugin needs to know if we think the game is running. *NB: This is a function, and should be called as such. Using the bare word `game_running` will always be `True`.* - + ``` from monitor import monitor ... if monitor.game_running(): ... -``` +``` `import timeout_session` - provides a method called `new_session` that creates a requests.session with a default timeout on all requests. Recommended to @@ -128,7 +128,7 @@ reduce noise in HTTP requests `from ttkHyperlinkLabel import HyperlinkLabel` and `import myNotebook as nb` - For creating UI elements. -In addition to the above we also explicitly package the following python +In addition to the above we also explicitly package the following python modules for plugin use: - shutil @@ -245,7 +245,7 @@ include variables, and even the returns of functions, in the output. ## Checking core EDMC version -If you have code that needs to act differently under different versions of +If you have code that needs to act differently under different versions of this application then you can check utilise `config.appversion`. Prior to version 5.0.0 this was a simple string. From 5.0.0 onwards it is, @@ -306,7 +306,7 @@ Mac, and `$TMP/EDMarketConnector.log` on Linux. ## Avoiding potential pitfalls -There are a number of things that your code should either do or avoiding +There are a number of things that your code should either do or avoiding doing so as to play nicely with the core EDMarketConnector code and not risk causing application crashes or hangs. @@ -317,12 +317,12 @@ See the section on [packaging extra modules](#your-plugin-directory-name-must-be ### Use a thread for long-running code -By default, your plugin code will be running in the main thread. So, if you -perform some operation that takes significant time (more than a second) you -will be blocking both the core code from continuing *and* any other plugins +By default, your plugin code will be running in the main thread. So, if you +perform some operation that takes significant time (more than a second) you +will be blocking both the core code from continuing *and* any other plugins from running their main-thread code. -This includes any connections to remote services, such as a website or +This includes any connections to remote services, such as a website or remote database. So please place such code within its own thread. See the [EDSM plugin](https://github.com/EDCD/EDMarketConnector/blob/main/plugins/edsm.py) @@ -331,20 +331,20 @@ with a queue to send data, and telling the sub-thread to stop during shutdown. ### All tkinter calls in main thread -The only tkinter calls that should ever be made from a sub-thread are +The only tkinter calls that should ever be made from a sub-thread are `event_generate()` calls to send data back to the main thread. -Any attempt to manipulate tkinter UI elements directly from a sub-thread +Any attempt to manipulate tkinter UI elements directly from a sub-thread will most likely crash the whole program. See the [EDSM plugin](https://github.com/EDCD/EDMarketConnector/blob/main/plugins/edsm.py) -code for an example of using `event_generate()` to cause the plugin main -thread code to update a UI element. Start from the `plugin_app()` +code for an example of using `event_generate()` to cause the plugin main +thread code to update a UI element. Start from the `plugin_app()` implementation. ### Do not call tkinter `event_generate` during shutdown. -However, you must **not** make *any* tkinter `event_generate()` call whilst +However, you must **not** make *any* tkinter `event_generate()` call whilst the application is shutting down. The application shutdown sequence is itself triggered from the `<>` event @@ -352,8 +352,8 @@ handler, and generating another event from any code in, or called from, there causes the application to hang somewhere in the tk libraries. You can detect if the application is shutting down with the boolean -`config.shutting_down`. Note that although this is technically a function -its implementation is of a property on `config.AbstractConfig` and thus you +`config.shutting_down`. Note that although this is technically a function +its implementation is of a property on `config.AbstractConfig` and thus you should treat it as a variable. **Do NOT use**: @@ -365,7 +365,7 @@ should treat it as a variable. # During shutdown ``` -as this will cause the 'During shutdown' branch to *always* be taken, as in +as this will cause the 'During shutdown' branch to *always* be taken, as in this context you're testing if the function exists, and that is always True. So instead use: @@ -396,8 +396,8 @@ your plugin's settings in a platform-independent way. Previously this was done with a single set and two get methods, the new methods provide better type safety. -If you want to maintain compatibility with pre-5.0.0 versions of this -application (please encourage plugin users to update!) then you'll need to +If you want to maintain compatibility with pre-5.0.0 versions of this +application (please encourage plugin users to update!) then you'll need to include this code in at least once in your plugin (no harm in putting it in all modules/files): @@ -670,8 +670,8 @@ cause `state['NavRoute'] = None`, but if you open the galaxy map in-game and cause an automatic re-plot of last route, then a new `NavRoute` event will also be generated and passed to plugins. -[2] - Some data from the CAPI is sometimes returned as a `list` (when all -members are present) and other times as an integer-keyed `dict` (when at +[2] - Some data from the CAPI is sometimes returned as a `list` (when all +members are present) and other times as an integer-keyed `dict` (when at least one member is missing, so the indices are not contiguous). We choose to always convert to the integer-keyed `dict` form so that code utilising the data is simpler. @@ -697,7 +697,7 @@ Journal `ModuleInfo` event. `OnFoot` is an indication as to if the player is on-foot, rather than in a vehicle. -`Component`, `Item`, `Consumable` & `Data` are `dict`s tracking your +`Component`, `Item`, `Consumable` & `Data` are `dict`s tracking your Odyssey MicroResources in your Ship Locker. `BacKPack` contains `dict`s for the same when you're on-foot. @@ -706,10 +706,10 @@ relating to suits and their loadouts. New in version 5.0.1: -`Odyssey` boolean based on the presence of such a flag in the `LoadGame` +`Odyssey` boolean based on the presence of such a flag in the `LoadGame` event. Defaults to `False`, i.e. if no such key in the event. -The previously undocumented `Horizons` boolean is similarly from `LoadGame`, +The previously undocumented `Horizons` boolean is similarly from `LoadGame`, but blindly retrieves the value rather than having a strict default. There'd be an exception if it wasn't there, and the value would be `None`. Note that this is **NOT** the same as the return from @@ -786,8 +786,8 @@ react to either in your plugin code then either compare in a case insensitive manner or check for both. The difference in case allows you to differentiate between the two scenarios. -**NB: Any of these events are passing to `journal_entry_cqc` rather than to -`journal_entry` if player has loaded into Arena (CQC).** +**NB: Any of these events are passing to `journal_entry_cqc` rather than to +`journal_entry` if player has loaded into Arena (CQC).** This event is not sent when EDMarketConnector is running on a different machine so you should not *rely* on receiving this event. @@ -804,15 +804,15 @@ Examples of this are: 1. Every `NavRoute` event contains the full `Route` array as loaded from `NavRoute.json`. - + *NB: There is no indication available when a player cancels a route.* The game itself does not provide any such, not in a Journal event, not in a `Status.json` flag. - + The Journal documentation v28 is incorrect about the event and file being `Route(.json)` the word is `NavRoute`. Also the format of the data is, e.g. - + ```json { "timestamp":"2021-03-10T11:31:37Z", "event":"NavRoute", @@ -826,9 +826,9 @@ Examples of this are: ``` 1. Every `ModuleInfo` event contains the full data as loaded from the - `ModulesInfo.json` file. Note that we use the singular form here to + `ModulesInfo.json` file. Note that we use the singular form here to stay consistent with the Journal event name. - + --- ### Journal entry in CQC @@ -888,7 +888,7 @@ def dashboard_entry(cmdr: str, is_beta: bool, entry: Dict[str, Any]): sys.stderr.write("Hardpoints {}\n".format(is_deployed and "deployed" or "stowed")) ``` -`dashboard_entry()` is called with the latest data from the `Status.json` +`dashboard_entry()` is called with the latest data from the `Status.json` file when an update to that file is detected. This will be when something on the player's cockpit display changes - @@ -985,10 +985,7 @@ will always be the case**. In the `capi_fleetcarrier()` callback, the contents of `data` will be the response from the CAPI `/fleetcarrier` query. See [this documentation](https://github.com/Athanasius/fd-api/blob/main/docs/FrontierDevelopments-CAPI-endpoints.md) for details of the expected content structure and data. -In all cases, `data` will include a `request_cmdr` entry, which will be the -name of the active CMDR _at the point the request was made_. In the case of -a CAPI request taking a long time to return, the user may have switched -CMDR during the request, so this may be different to the current CMDR. +In all cases, `data` will include a `request_cmdr` entry, which will be the name of the active CMDR _at the point the request was made_. In the case of a CAPI request taking a long time to return, the user may have switched CMDR during the request, so this may be different to the current CMDR. If there is a killswitch in effect for some of the CAPI endpoints, then the data passed to this function might not be as complete as you expect. Code @@ -1202,11 +1199,11 @@ Any modules the core application code uses will naturally be packaged, and we explicitly include a small number of additional modules for the use of plugins. -Whilst we would like to make all of the `stdlib` of Python available it is -not automatically packaged into our releases by py2exe. We hope to address -this in the 5.3 release series. In the meantime, if there's anything -missing that you'd like to use, please ask. Yes, this very much means you -need to test your plugins against a Windows installation of the application +Whilst we would like to make all of the `stdlib` of Python available it is +not automatically packaged into our releases by py2exe. We hope to address +this in the 5.3 release series. In the meantime, if there's anything +missing that you'd like to use, please ask. Yes, this very much means you +need to test your plugins against a Windows installation of the application to be sure it will work. See @@ -1356,7 +1353,7 @@ versions of EDMarketConnector: [2to3](https://docs.python.org/3/library/2to3.html) tool can automate much of this work. -We advise *against* making any attempt to have a plugin's code work under -both Python 2.7 and 3.x. We no longer maintain the Python 2.7-based -versions of this application, and you shouldn't support use of them with +We advise *against* making any attempt to have a plugin's code work under +both Python 2.7 and 3.x. We no longer maintain the Python 2.7-based +versions of this application, and you shouldn't support use of them with your plugin. From 754367618ff273b94ae459544a7c73b86a182499 Mon Sep 17 00:00:00 2001 From: aussig Date: Sat, 24 Dec 2022 09:27:18 +0000 Subject: [PATCH 14/20] Fix for exceptions thrown on journal events --- EDMarketConnector.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index ad043c93..47a7110a 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1547,10 +1547,10 @@ class AppWindow(object): monitor.state ) - if err: - self.status['text'] = err - if not config.get_int('hotkey_mute'): - hotkeymgr.play_bad() + if err: + self.status['text'] = err + if not config.get_int('hotkey_mute'): + hotkeymgr.play_bad() auto_update = False # Only if auth callback is not pending From e22fa7c23bf264da5fad37ba1f8ea40050be3c9f Mon Sep 17 00:00:00 2001 From: aussig Date: Sat, 24 Dec 2022 09:32:45 +0000 Subject: [PATCH 15/20] Store cmdr name at request time in CAPIData instead of passing into EDMCCAPIRequest and EDMCCAPIResponse --- PLUGINS.md | 24 +++++++++++------------- companion.py | 23 +++++++++-------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index df6d011f..c9debd5b 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -970,22 +970,20 @@ def capi_fleetcarrier(data): | :-------- | :--------------: | :------------------------------------------------------------------------------------------------------- | | `data` | `CAPIData` | `/fleetcarrier` API response | -`CAPIData` is a class, which you can `from companion import CAPIDATA`, and is -based on `UserDict`. The actual data from CAPI queries is thus accessible -via python's normal `data['key']` syntax. However, being a class, it can also -have extra properties, such as `source_host`, as shown above. Plugin authors -are free to use *that* property, **but MUST NOT rely on any other extra -properties present in `CAPIData`, they are for internal use only.** -In the `cmdr_data()` callback, the contents of `data` will always have at least the data returned by a CAPI -`/profile` query. If the player is docked at a station, and the relevant -services are available then the `lastStarport` key's value will have been -augmented with `/market` and/or `/shipyard` data. **But do not assume this -will always be the case**. +`CAPIData` is a class, which you can `from companion import CAPIDATA`, and is based on `UserDict`. The actual data from CAPI queries is thus accessible via python's normal `data['key']` syntax. However, being a class, it can also have extra properties, such as `source_host`, as shown above. -In the `capi_fleetcarrier()` callback, the contents of `data` will be the response from the CAPI `/fleetcarrier` query. See [this documentation](https://github.com/Athanasius/fd-api/blob/main/docs/FrontierDevelopments-CAPI-endpoints.md) for details of the expected content structure and data. +#### Properties of CAPIData permitted for use by plugins -In all cases, `data` will include a `request_cmdr` entry, which will be the name of the active CMDR _at the point the request was made_. In the case of a CAPI request taking a long time to return, the user may have switched CMDR during the request, so this may be different to the current CMDR. +Plugin authors are free to use the following properties of `CAPIData`, **but MUST NOT rely on any other extra properties, they are for internal use only.** + +| Property | Type | Description | +| :------------- | :--------------: | :------------------------------------------------------------------------------------------------------- | +| `data` | `Dict` | The data returned by the CAPI query. For the `cmdr_data()` callback, if the player is docked at a station, and the relevant services are available then the `lastStarport` key's value will have been augmented with `/market` and/or `/shipyard` data. **But do not assume this will always be the case**. | +| `source_host` | `str` | `SERVER_LIVE` \| `SERVER_BETA` \| `SERVER_LEGACY` the current calaxy mode. | +| `request_cmdr` | `str` | The name of the active CMDR _at the point the request was made_. In the case of a CAPI request taking a long time to return, the user may have switched CMDR during the request, so this may be different to the current CMDR. | + +See [this documentation](https://github.com/Athanasius/fd-api/blob/main/docs/FrontierDevelopments-CAPI-endpoints.md) for details of the expected content structure and data for CAPI queries. If there is a killswitch in effect for some of the CAPI endpoints, then the data passed to this function might not be as complete as you expect. Code diff --git a/companion.py b/companion.py index f464e0d6..b7ccd093 100644 --- a/companion.py +++ b/companion.py @@ -68,7 +68,8 @@ class CAPIData(UserDict): self, data: Union[str, Dict[str, Any], 'CAPIData', None] = None, source_host: Optional[str] = None, - source_endpoint: Optional[str] = None + source_endpoint: Optional[str] = None, + request_cmdr: Optional[str] = None ) -> None: if data is None: super().__init__() @@ -83,6 +84,7 @@ class CAPIData(UserDict): self.source_host = source_host self.source_endpoint = source_endpoint + self.request_cmdr = request_cmdr if source_endpoint is None: return @@ -575,8 +577,7 @@ class EDMCCAPIRequest(EDMCCAPIReturn): self, capi_host: str, endpoint: str, query_time: int, tk_response_event: Optional[str] = None, - play_sound: bool = False, auto_update: bool = False, - cmdr: Optional[str] = None + play_sound: bool = False, auto_update: bool = False ): super().__init__( query_time=query_time, tk_response_event=tk_response_event, @@ -584,7 +585,6 @@ class EDMCCAPIRequest(EDMCCAPIReturn): ) self.capi_host: str = capi_host # The CAPI host to use. self.endpoint: str = endpoint # The CAPI query to perform. - self.cmdr: Optional[str] = cmdr # The CMDR name used for the request. class EDMCCAPIResponse(EDMCCAPIReturn): @@ -592,12 +592,10 @@ class EDMCCAPIResponse(EDMCCAPIReturn): def __init__( self, capi_data: CAPIData, - query_time: int, play_sound: bool = False, auto_update: bool = False, - cmdr: Optional[str] = None + query_time: int, play_sound: bool = False, auto_update: bool = False ): super().__init__(query_time=query_time, play_sound=play_sound, auto_update=auto_update) self.capi_data: CAPIData = capi_data # Frontier CAPI response, possibly augmented (station query) - self.capi_data['request_cmdr'] = cmdr # Inject the CMDR name used for the original request into the data class EDMCCAPIFailedRequest(EDMCCAPIReturn): @@ -821,7 +819,7 @@ class Session(object): # r.status_code = 401 # raise requests.HTTPError capi_json = r.json() - capi_data = CAPIData(capi_json, capi_host, capi_endpoint) + capi_data = CAPIData(capi_json, capi_host, capi_endpoint, monitor.cmdr) self.capi_raw_data.record_endpoint( capi_endpoint, r.content.decode(encoding='utf-8'), datetime.datetime.utcnow() @@ -997,8 +995,7 @@ class Session(object): capi_data=capi_data, query_time=query.query_time, play_sound=query.play_sound, - auto_update=query.auto_update, - cmdr=query.cmdr + auto_update=query.auto_update ) ) @@ -1046,8 +1043,7 @@ class Session(object): tk_response_event=tk_response_event, query_time=query_time, play_sound=play_sound, - auto_update=auto_update, - cmdr=monitor.cmdr + auto_update=auto_update ) ) @@ -1076,8 +1072,7 @@ class Session(object): tk_response_event=tk_response_event, query_time=query_time, play_sound=play_sound, - auto_update=auto_update, - cmdr=monitor.cmdr + auto_update=auto_update ) ) ###################################################################### From fc80d8ffd687e7bdf0eee6f20f73d7355d314c56 Mon Sep 17 00:00:00 2001 From: aussig Date: Sat, 24 Dec 2022 10:18:37 +0000 Subject: [PATCH 16/20] Revert "Fix for exceptions thrown on journal events" This reverts commit 754367618ff273b94ae459544a7c73b86a182499. --- EDMarketConnector.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 47a7110a..ad043c93 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1547,10 +1547,10 @@ class AppWindow(object): monitor.state ) - if err: - self.status['text'] = err - if not config.get_int('hotkey_mute'): - hotkeymgr.play_bad() + if err: + self.status['text'] = err + if not config.get_int('hotkey_mute'): + hotkeymgr.play_bad() auto_update = False # Only if auth callback is not pending From f6f122072bf500072ede4eafc5b2026d177eb43f Mon Sep 17 00:00:00 2001 From: aussig Date: Mon, 26 Dec 2022 11:20:37 +0000 Subject: [PATCH 17/20] Tweaks to plugins docs for fleetcarrier CAPI, including headings, typos and clarity on legacy galaxy --- PLUGINS.md | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index c9debd5b..e0e6f1fa 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -908,11 +908,11 @@ constants. --- -### Commander Data from Frontier CAPI -If a plugin has a `cmdr_data()` function it gets called when the application -has just fetched fresh Cmdr and station data from Frontier's CAPI servers, -**but not for the Legacy galaxy**. See `cmdr_data_legacy()` below for Legacy -data handling. +### Data from Frontier CAPI + +#### Commander, Market and Shipyard Data + +If a plugin has a `cmdr_data()` function it gets called when the application has just fetched fresh CMDR, station and shipyard data from Frontier's CAPI servers, **but not for the Legacy galaxy**. See `cmdr_data_legacy()` below for Legacy data handling. ```python from companion import CAPIData, SERVER_LIVE, SERVER_LEGACY, SERVER_BETA @@ -941,6 +941,8 @@ def cmdr_data(data, is_beta): | `data` | `CAPIData` | `/profile` API response, with `/market` and `/shipyard` added under the keys `marketdata` and `shipdata` | | `is_beta` | `bool` | If the game is currently in beta | +#### Fleet Carrier Data + If a plugin has a `capi_fleetcarrier()` function it gets called when the application has just fetched fresh Fleetcarrier data from Frontier's CAPI servers. This is done when `CarrierBuy`or `CarrierStats` events are detected in the Player Journal. To avoid flooding Frontier's CAPI server, a throttle is applied to ensure a significant interval between requests (currently 15 mins). Also be aware that calls to the `/fleetcarrier` CAPI endpoint have been reported to take a very long time to return, potentially up to 20 minutes. Delays in responses from this endpoint could delay other CAPI queries. ```python @@ -970,17 +972,16 @@ def capi_fleetcarrier(data): | :-------- | :--------------: | :------------------------------------------------------------------------------------------------------- | | `data` | `CAPIData` | `/fleetcarrier` API response | +#### CAPIData and Available Properties -`CAPIData` is a class, which you can `from companion import CAPIDATA`, and is based on `UserDict`. The actual data from CAPI queries is thus accessible via python's normal `data['key']` syntax. However, being a class, it can also have extra properties, such as `source_host`, as shown above. - -#### Properties of CAPIData permitted for use by plugins +`CAPIData` is a class, which you can `from companion import CAPIDATA`, and is based on `UserDict`. The actual data from CAPI queries is thus accessible via python's normal `data['key']` syntax. However, being a class, it can also have extra properties, such as `source_host`, as shown in the code example above. Plugin authors are free to use the following properties of `CAPIData`, **but MUST NOT rely on any other extra properties, they are for internal use only.** | Property | Type | Description | | :------------- | :--------------: | :------------------------------------------------------------------------------------------------------- | -| `data` | `Dict` | The data returned by the CAPI query. For the `cmdr_data()` callback, if the player is docked at a station, and the relevant services are available then the `lastStarport` key's value will have been augmented with `/market` and/or `/shipyard` data. **But do not assume this will always be the case**. | -| `source_host` | `str` | `SERVER_LIVE` \| `SERVER_BETA` \| `SERVER_LEGACY` the current calaxy mode. | +| `data` | `Dict` | The data returned by the CAPI query. For the `cmdr_data()` callback, if the player is docked at a station, and the relevant services are available then the `lastStarport` key's value will have been augmented with `/market` and/or `/shipyard` data. **Do not assume this will always be the case**. | +| `source_host` | `str` | `SERVER_LIVE` \| `SERVER_BETA` \| `SERVER_LEGACY` the current galaxy mode. | | `request_cmdr` | `str` | The name of the active CMDR _at the point the request was made_. In the case of a CAPI request taking a long time to return, the user may have switched CMDR during the request, so this may be different to the current CMDR. | See [this documentation](https://github.com/Athanasius/fd-api/blob/main/docs/FrontierDevelopments-CAPI-endpoints.md) for details of the expected content structure and data for CAPI queries. @@ -989,12 +990,9 @@ If there is a killswitch in effect for some of the CAPI endpoints, then the data passed to this function might not be as complete as you expect. Code defensively. - #### CAPI data for Legacy -When CAPI data has been retrieved from the separate CAPI host for the Legacy -galaxy, because the Journal gameversion indicated the player is playing/last -played in that galaxy, a different function will be called, -`cmdr_data_legacy()`. + +When CAPI data has been retrieved from the separate CAPI host for the Legacy galaxy, because the Journal gameversion indicated the player is playing last played in that galaxy, a different function will be called, `cmdr_data_legacy()`. Note that there is no legacy equivalent to `capi_fleetcarrier()`, so always use the `source_host` property to determine the user's galaxy. ```python def cmdr_data_legacy(data, is_beta): From 16055a311fdf195b9aebe796461b005998fa2df6 Mon Sep 17 00:00:00 2001 From: aussig Date: Mon, 26 Dec 2022 11:23:44 +0000 Subject: [PATCH 18/20] Typo in plugins docs --- PLUGINS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PLUGINS.md b/PLUGINS.md index e0e6f1fa..eb5cf57c 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -992,7 +992,7 @@ defensively. #### CAPI data for Legacy -When CAPI data has been retrieved from the separate CAPI host for the Legacy galaxy, because the Journal gameversion indicated the player is playing last played in that galaxy, a different function will be called, `cmdr_data_legacy()`. Note that there is no legacy equivalent to `capi_fleetcarrier()`, so always use the `source_host` property to determine the user's galaxy. +When CAPI data has been retrieved from the separate CAPI host for the Legacy galaxy, because the Journal gameversion indicated the player is playing / last played in that galaxy, a different function will be called, `cmdr_data_legacy()`. Note that there is no legacy equivalent to `capi_fleetcarrier()`, so always use the `source_host` property to determine the user's galaxy. ```python def cmdr_data_legacy(data, is_beta): From 4a2401983da7909629b5e5c4b82846c85717f1b9 Mon Sep 17 00:00:00 2001 From: aussig Date: Thu, 29 Dec 2022 14:50:34 +0000 Subject: [PATCH 19/20] Add preference for enabling / disabling CAPI fleetcarrier endpoint --- EDMarketConnector.py | 2 +- prefs.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index eaba8797..cb704124 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1582,7 +1582,7 @@ class AppWindow(object): if not should_return: self.w.after(int(SERVER_RETRY * 1000), self.capi_request_data) - if entry['event'] in ('CarrierBuy', 'CarrierStats'): + if entry['event'] in ('CarrierBuy', 'CarrierStats') and config.get_int('capi_fleetcarrier') == 1: should_return, new_data = killswitch.check_killswitch('capi.request.fleetcarrier', {}) if not should_return: self.w.after(int(SERVER_RETRY * 1000), self.capi_request_fleetcarrier_data) diff --git a/prefs.py b/prefs.py index e5ab242b..b84e03e0 100644 --- a/prefs.py +++ b/prefs.py @@ -470,6 +470,25 @@ class PreferencesDialog(tk.Toplevel): state=tk.NORMAL if config.get_str('journaldir') else tk.DISABLED ).grid(column=2, pady=self.PADY, sticky=tk.EW, row=row.get()) + # CAPI settings + self.capi_fleetcarrier = tk.IntVar(value=config.get_int('capi_fleetcarrier')) + + ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid( + columnspan=4, padx=self.PADX, pady=self.PADY*4, sticky=tk.EW, row=row.get() + ) + + nb.Label( + config_frame, + text=_('CAPI Settings') # LANG: Settings > Configuration - Label for CAPI section + ).grid(padx=self.PADX, sticky=tk.W, row=row.get()) + + nb.Checkbutton( + config_frame, + # LANG: Configuration - Enable or disable the Fleet Carrier CAPI calls + text=_('Enable Fleetcarrier CAPI Queries'), + variable=self.capi_fleetcarrier + ).grid(columnspan=4, padx=self.PADX, pady=(5, 0), sticky=tk.W, row=row.get()) + if sys.platform in ('darwin', 'win32'): ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid( columnspan=4, padx=self.PADX, pady=self.PADY*4, sticky=tk.EW, row=row.get() @@ -1244,6 +1263,8 @@ class PreferencesDialog(tk.Toplevel): else: config.set('journaldir', logdir) + config.set('capi_fleetcarrier', int(self.capi_fleetcarrier.get())) + if sys.platform in ('darwin', 'win32'): config.set('hotkey_code', self.hotkey_code) config.set('hotkey_mods', self.hotkey_mods) From 933520c5afc79951bd496796c48d2d933060d652 Mon Sep 17 00:00:00 2001 From: aussig Date: Fri, 30 Dec 2022 15:39:32 +0000 Subject: [PATCH 20/20] Switch to using a bool config value for fleetcarrier CAPI instead of int --- EDMarketConnector.py | 2 +- prefs.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index cb704124..2ce58201 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1582,7 +1582,7 @@ class AppWindow(object): if not should_return: self.w.after(int(SERVER_RETRY * 1000), self.capi_request_data) - if entry['event'] in ('CarrierBuy', 'CarrierStats') and config.get_int('capi_fleetcarrier') == 1: + if entry['event'] in ('CarrierBuy', 'CarrierStats') and config.get_bool('capi_fleetcarrier'): should_return, new_data = killswitch.check_killswitch('capi.request.fleetcarrier', {}) if not should_return: self.w.after(int(SERVER_RETRY * 1000), self.capi_request_fleetcarrier_data) diff --git a/prefs.py b/prefs.py index b84e03e0..a356848b 100644 --- a/prefs.py +++ b/prefs.py @@ -471,7 +471,7 @@ class PreferencesDialog(tk.Toplevel): ).grid(column=2, pady=self.PADY, sticky=tk.EW, row=row.get()) # CAPI settings - self.capi_fleetcarrier = tk.IntVar(value=config.get_int('capi_fleetcarrier')) + self.capi_fleetcarrier = tk.BooleanVar(value=config.get_bool('capi_fleetcarrier')) ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid( columnspan=4, padx=self.PADX, pady=self.PADY*4, sticky=tk.EW, row=row.get() @@ -1263,7 +1263,7 @@ class PreferencesDialog(tk.Toplevel): else: config.set('journaldir', logdir) - config.set('capi_fleetcarrier', int(self.capi_fleetcarrier.get())) + config.set('capi_fleetcarrier', self.capi_fleetcarrier.get()) if sys.platform in ('darwin', 'win32'): config.set('hotkey_code', self.hotkey_code)