From eed09cfc4b605811d81741a5936489754cfcd782 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 18 May 2021 22:40:51 +0100 Subject: [PATCH 01/35] ChangeLog: Duplicate "Windows 7 no worky now" at the top Having it only in the 5.0.0 section is going to be less and less visible to people who might not install every version. --- ChangeLog.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 240890c3..3f33470f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,22 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in reverse chronological order (latest first). --- +* We now test against, and package with, Python 3.9.5. + + **As a consequence of this we no longer support Windows 7. + This is due to + [Python 3.9.x itself not supporting Windows 7](https://www.python.org/downloads/windows/). + The application (both EDMarketConnector.exe and EDMC.exe) will crash on + startup due to a missing DLL.** + + This should have no other impact on users or plugin developers, other + than the latter now being free to use features that were introduced since the + Python 3.7 series. + + Developers can check the contents of the `.python-version` file + in the source (it's not distributed with the Windows installer) for the + currently used version in a given branch. + Release 5.0.1 === From 58619a499f20f0aff0a30930ae82670a568e0031 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 18 May 2021 14:55:57 +0100 Subject: [PATCH 02/35] edmc_data: Add Jounal v31 extra Flags2 values --- edmc_data.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/edmc_data.py b/edmc_data.py index be1db743..7aea749b 100644 --- a/edmc_data.py +++ b/edmc_data.py @@ -429,6 +429,11 @@ Flags2Cold = 1 << 8 Flags2Hot = 1 << 9 Flags2VeryCold = 1 << 10 Flags2VeryHot = 1 << 11 +Flags2GlideMode = 1 << 12 +Flags2OnFootInHangar = 1 << 13 +Flags2OnFootSocialSpace = 1 << 14 +Flags2OnFootExterior = 1 << 15 +Flags2BreathableAtmosphere = 1 << 16 # Dashboard GuiFocus constants GuiFocusNoFocus = 0 From bcbf37599eb7602b92b80d1d9037ece81338c9d1 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 18 May 2021 16:39:06 +0100 Subject: [PATCH 03/35] Odyssey Backpack: Add support for v31 `Backpack` and `BackpackChange` Leaving the, meant to be defunct, `BackPackMaterials` code in for now, as it's only one extra conditional that will never be stepped into if the event never happens. Yes, Frontier now uses 'Backpack' not 'BackPack', but we've already published our plugin API as using the CamelCase version, so not changing the monitor.state key. --- monitor.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/monitor.py b/monitor.py index d496a2ff..0da62cc9 100644 --- a/monitor.py +++ b/monitor.py @@ -835,6 +835,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below {self.canonicalise(x['Name']): x['Count'] for x in clean_data} ) + # Journal v31 implies this was removed before Odyssey launch elif event_type == 'BackPackMaterials': # alpha4 - # Lists the contents of the backpack, eg when disembarking from ship @@ -865,6 +866,76 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below {self.canonicalise(x['Name']): x['Count'] for x in clean_data} ) + elif event_type == 'Backpack': + # TODO: v31 doc says this is`backpack.json` ... but Howard Chalkley + # said it's `Backpack.json` + with open(join(self.currentdir, 'Backpack.json'), 'rb') as backpack: # type: ignore + try: + entry = json.load(backpack) + + except json.JSONDecodeError: + logger.exception('Failed decoding Backpack.json', exc_info=True) + + else: + # Assume this reflects the current state when written + self.state['BackPack']['Component'] = defaultdict(int) + self.state['BackPack']['Consumable'] = defaultdict(int) + self.state['BackPack']['Item'] = defaultdict(int) + self.state['BackPack']['Data'] = defaultdict(int) + + clean_components = self.coalesce_cargo(entry['Components']) + self.state['BackPack']['Component'].update( + {self.canonicalise(x['Name']): x['Count'] for x in clean_components} + ) + + clean_consumables = self.coalesce_cargo(entry['Consumables']) + self.state['BackPack']['Consumable'].update( + {self.canonicalise(x['Name']): x['Count'] for x in clean_consumables} + ) + + clean_items = self.coalesce_cargo(entry['Items']) + self.state['BackPack']['Item'].update( + {self.canonicalise(x['Name']): x['Count'] for x in clean_items} + ) + + clean_data = self.coalesce_cargo(entry['Data']) + self.state['BackPack']['Data'].update( + {self.canonicalise(x['Name']): x['Count'] for x in clean_data} + ) + + elif event_type == 'BackpackChange': + # Changes to Odyssey Backpack contents *other* than from a Transfer + # See TransferMicroResources event for that. + + if entry.get('Added') is not None: + changes = 'Added' + + elif entry.get('Removed') is not None: + changes = 'Removed' + + else: + logger.warning(f'BackpackChange with neither Added nor Removed: {entry=}') + changes = '' + + if changes != '': + for c in entry[changes]: + category = self.category(c['Type']) + name = self.canonicalise(c['Name']) + + if changes == 'Removed': + self.state['BackPack'][category][name] -= c['Count'] + + elif changes == 'Added': + self.state['BackPack'][category][name] += c['Count'] + + # Paranoia check to see if anything has gone negative. + # As of Odyssey Alpha Phase 1 Hotfix 2 keeping track of BackPack + # materials is impossible when used/picked up anyway. + for c in self.state['BackPack']: + for m in self.state['BackPack'][c]: + if self.state['BackPack'][c][m] < 0: + self.state['BackPack'][c][m] = 0 + elif event_type == 'BuyMicroResources': # Buying from a Pioneer Supplies, goes directly to ShipLocker. # One event per Item, not an array. @@ -961,6 +1032,11 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below if self.state['BackPack']['Consumable'][c] < 0: self.state['BackPack']['Consumable'][c] = 0 + # TODO: + # + # also there's one additional journal event that was missed out from + # this version of the docs: "SuitLoadout": # when starting on foot, or + # when disembarking from a ship, with the same info as found in "CreateSuitLoadout" elif event_type == 'SwitchSuitLoadout': loadoutid = entry['LoadoutID'] new_slot = self.suit_loadout_id_from_loadoutid(loadoutid) From a50ed31450811ee686e1a0529efd322394ed1f07 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 18 May 2021 16:58:20 +0100 Subject: [PATCH 04/35] Add note about maybe not wanting to use UseConsumable to track Backpack *Hopefully* `BackpackChange` will be 100% reliable in place of this. Test once live Odyssey is available! --- monitor.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/monitor.py b/monitor.py index 0da62cc9..47b82082 100644 --- a/monitor.py +++ b/monitor.py @@ -1025,6 +1025,12 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # Parameters: # • Name # • Type + + # TODO: XXX: From v31 doc + # 12.2 BackpackChange + # This is written when there is any change to the contents of the + # suit backpack – note this can be written at the same time as other + # events like UseConsumable for c in self.state['BackPack']['Consumable']: if c == entry['Name']: self.state['BackPack']['Consumable'][c] -= 1 From eeed2cf7af6e4c9950b7f14b5003de4c51aa1df4 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 18 May 2021 17:02:45 +0100 Subject: [PATCH 05/35] Initial attempt at `SuitLoadout` event support Need to see examples to be sure. --- monitor.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/monitor.py b/monitor.py index 47b82082..82cfab7a 100644 --- a/monitor.py +++ b/monitor.py @@ -1043,6 +1043,22 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # also there's one additional journal event that was missed out from # this version of the docs: "SuitLoadout": # when starting on foot, or # when disembarking from a ship, with the same info as found in "CreateSuitLoadout" + elif event_type == 'SuitLoadout': + new_loadout = { + 'loadoutSlotId': self.suit_loadout_id_from_loadoutid(entry['LoadoutID']), + 'suit': { + 'name': entry['SuitName'], + 'locName': entry.get('SuitName_Localised', entry['SuitName']), + 'suitId': entry['SuitID'], + }, + 'name': entry['LoadoutName'], + 'slots': self.suit_loadout_slots_array_to_dict(entry['Modules']), + } + self.state['SuitLoadouts'][new_loadout['loadoutSlotId']] = new_loadout + self.state['SuitLoadoutCurrent'] = new_loadout + # TODO: Well we know about the **SUIT**, as opposed to the Loadout ? + # self.state['SuitCurrent'] = self.state['Suits'][f'{new_suitid}'] + elif event_type == 'SwitchSuitLoadout': loadoutid = entry['LoadoutID'] new_slot = self.suit_loadout_id_from_loadoutid(loadoutid) From 6448d03ab3845b6a1099e71f36edbc8a98d4e184 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 18 May 2021 23:18:30 +0100 Subject: [PATCH 06/35] PLUGINS.md: Document packaging extra modules --- PLUGINS.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index fae13d0d..430bb832 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -270,6 +270,8 @@ There are a number of things that your code should either do or avoiding doing so as to play nicely with the core EDMC code and not risk causing application crashes or hangs. +### Be careful about the name of your plugin directory + ### Use a thread for long-running code By default, your plugin code will be running in the main thread. So, if you @@ -924,12 +926,91 @@ plugin's folder: → Compressed (zipped) folder. - Mac: In Finder right click on your plugin's folder and choose Compress. -If there are any external dependencies then include them in the plugin's -folder. +If there are any external dependencies then +[include them](#packaging-extra-modules) in the plugin's folder. Optionally, for tidiness delete any `.pyc` and `.pyo` files in the archive, as well as the `__pycache__` directory. + +--- + +## Packaging extra modules +EDMarketConnector's Windows installs only package a minimal set of modules. +All of the 'stdlib' of Python is provided, plus any modules the core +application code uses and a small number of additional modules for the +use of plugins. See +[Plugins:Available imports](https://github.com/EDCD/EDMarketConnector/blob/main/PLUGINS.md#available-imports) +for a list. + +As such if your plugin requires additional modules you will need to package +them with your plugin. There is no general Python interpreter in which to +rely on [pip](https://pypi.org/project/pip/) to install them. + +### Environment + +It will be easier of you are using a Python +[virtual environment](https://docs.python.org/3/library/venv.html) for +actually testing the plugin. This is so that you can be sure it is working +*because* you have copied all the correct Python modules inside your +plugin, and not because they are installed within the Python site-packages +in some applicable location (system level or user level). + +So, setup a virtual environment to use when running EDMarketConnector +code to test your plugin, and use the 'system' non-virtual Python to +install modules in order to have somewhere to copy them from. + +NB: If you use PyCharm it's possible to have it do the work of +[creating a virtual environment](https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html) +for your project. + +### Install the modules for the system Python +Technically you could also do this within an additional virtual environment. +If they were in your plugin testing virtual environment then you can't be +sure you have all the necessary files copied into your plugin so it will +work within a vanilla Windows EDMarketConnector install. + +We'll use `xml_dataclasses` for this example. + + pip install xml_dataclasses + +### Copy the module files into your plugin directory + +1. Assuming it's a 'simple' module with no caveats, now we copy: + 1. `pip show xml_dataclasses` - `Location` is where it was installed. + 1. If you have a POSIX-compliant command-line environment: + + cp -pr + + or just use Windows File Explorer, or other GUI means, to copy. + +### Your plugin directory name **must** be importable +You're going to have to refer to your plugin directory in order to import +anything within it. This means it should be compatible with such. + +1. Do **not** use hyphens (`-`) as word separators, or full-stops (`.`). +1. You can use underscore (`_`) as a word separator. + +So: + + - `EDMC-My-Plugin` **BAD**. + - `EDMC.My.Plugin` **BAD**. + - `EDMC_My_Plugin` **GOOD**. + +NB: No, you can't use `from . import xml_dataclasses` because the way +EDMarketConnector:plug.py loads 'found' plugins prevents this from working. + +### Test the module import + +Add an import of this module to your plugin code: + + from EDMC_My_Plugin import xml_dataclasses + +If you're lucky you won't have the "surprise!" of learning your chosen +extra module itself requires other modules. If you are gifted such a surprise +then you will need to repeat the [Copy](#copy-the-module-files-into-your-plugin-directory) +step for the extra module(s) until it works. + --- ## Disable a plugin From 9c4058da606b1207d68e1d48147e2bf389970ec9 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 18 May 2021 23:21:05 +0100 Subject: [PATCH 07/35] PLUGINS.md: Actually have something in Avoiding pitfalls about dir name --- PLUGINS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/PLUGINS.md b/PLUGINS.md index 430bb832..6a14b445 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -272,6 +272,9 @@ application crashes or hangs. ### Be careful about the name of your plugin directory +You might want your plugin directory name to be usable in import statements. +See the section on [packaging extra modules](#your-plugin-directory-name-must-be-importable). + ### Use a thread for long-running code By default, your plugin code will be running in the main thread. So, if you From 56f016604cd6d08cbd0dcb304283373a63749dc6 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 19 May 2021 00:00:54 +0100 Subject: [PATCH 08/35] Fix market CSV output filenames format I must have had a brainfart when refactoring this, leaving the trailing commas when I split this out into multi-statement rather than one big f-string. --- commodity.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/commodity.py b/commodity.py index a7c39e79..6b167fa1 100644 --- a/commodity.py +++ b/commodity.py @@ -24,9 +24,9 @@ def export(data, kind=COMMODITY_DEFAULT, filename=None) -> None: querytime = config.get_int('querytime', default=int(time.time())) if not filename: - filename_system = data['lastSystem']['name'].strip(), - filename_starport = data['lastStarport']['name'].strip(), - filename_time = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)), + filename_system = data['lastSystem']['name'].strip() + filename_starport = data['lastStarport']['name'].strip() + filename_time = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)) filename_kind = 'csv' filename = f'{filename_system}.{filename_starport}.{filename_time}.{filename_kind}' filename = join(config.get_str('outdir'), filename) From ecd65187307569def23c996a7c1ef16886617db8 Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 19 May 2021 18:19:57 +0200 Subject: [PATCH 09/35] Fixed showing unset shipyard provider as False The code around the dropdown menu in prefs for shipyard providers was a bit too clever for its own good. This brings it in line with the behaviour of the other two --- prefs.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/prefs.py b/prefs.py index 65a018a1..385780d3 100644 --- a/prefs.py +++ b/prefs.py @@ -535,10 +535,9 @@ class PreferencesDialog(tk.Toplevel): ) with row as cur_row: + shipyard_provider = config.get_str('shipyard_provider') self.shipyard_provider = tk.StringVar( - value=str( - config.get_str('shipyard_provider') in plug.provides('shipyard_url') - and config.get_str('shipyard_provider', default='EDSY')) + value=str(shipyard_provider if shipyard_provider in plug.provides('shipyard_url') else 'EDSY') ) # Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis nb.Label(config_frame, text=_('Shipyard')).grid(padx=self.PADX, pady=2*self.PADY, sticky=tk.W, row=cur_row) From af2effb317bb60bdd222ae37ec1e0bf3ed0c6f7e Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 19 May 2021 18:06:02 +0100 Subject: [PATCH 10/35] td.py: Pre-empty the 'dangling commas' brainfart when this gets refactored/cleaned --- td.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/td.py b/td.py index 13c32edd..5f05b732 100644 --- a/td.py +++ b/td.py @@ -25,7 +25,15 @@ def export(data): querytime = config.get_int('querytime', default=int(time.time())) + # + # When this is refactored into multi-line CHECK IT WORKS, avoiding the + # brainfart we had with dangling commas in commodity.py:export() !!! + # filename = join(config.get_str('outdir'), '%s.%s.%s.prices' % (data['lastSystem']['name'].strip(), data['lastStarport']['name'].strip(), time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)))) + # + # When this is refactored into multi-line CHECK IT WORKS, avoiding the + # brainfart we had with dangling commas in commodity.py:export() !!! + # timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.strptime(data['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) From ea1882d3120c7c7612d30508e7a8421903cacb06 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 19 May 2021 18:12:45 +0100 Subject: [PATCH 11/35] loadout.py: Pre-empt the 'dangling commas' brainfart when this gets refactored/cleaned --- loadout.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/loadout.py b/loadout.py index d3a08743..92c9ff3d 100644 --- a/loadout.py +++ b/loadout.py @@ -32,6 +32,14 @@ def export(data, filename=None): querytime = config.get_int('querytime', default=int(time.time())) # Write + # + # When this is refactored into multi-line CHECK IT WORKS, avoiding the + # brainfart we had with dangling commas in commodity.py:export() !!! + # filename = join(config.get_str('outdir'), '%s.%s.txt' % (ship, time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)))) + # + # When this is refactored into multi-line CHECK IT WORKS, avoiding the + # brainfart we had with dangling commas in commodity.py:export() !!! + # with open(filename, 'wt') as h: h.write(string) From 5a929b4c0b1eb78cbe0a9fc8ad0c3a7e5fc91337 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 May 2021 05:39:22 +0000 Subject: [PATCH 12/35] build(deps): bump watchdog from 2.1.1 to 2.1.2 Bumps [watchdog](https://github.com/gorakhargosh/watchdog) from 2.1.1 to 2.1.2. - [Release notes](https://github.com/gorakhargosh/watchdog/releases) - [Changelog](https://github.com/gorakhargosh/watchdog/blob/master/changelog.rst) - [Commits](https://github.com/gorakhargosh/watchdog/compare/v2.1.1...v2.1.2) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ee9255db..b72dc3f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ certifi==2020.12.5 requests==2.25.1 -watchdog==2.1.1 +watchdog==2.1.2 # Commented out because this doesn't package well with py2exe # infi.systray==0.1.12; sys_platform == 'win32' # argh==0.26.2 watchdog dep From 652fecc3e86feccc8e6c87a5ed28a1bd0e31dde6 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 May 2021 10:51:49 +0100 Subject: [PATCH 13/35] SuitLoadout: Attempt to get all state set correctly. I think this is now correct, but not yet triggering the unhide of the 'Suit' UI line, will check that next. --- monitor.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/monitor.py b/monitor.py index 82cfab7a..9787f192 100644 --- a/monitor.py +++ b/monitor.py @@ -1044,20 +1044,34 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # this version of the docs: "SuitLoadout": # when starting on foot, or # when disembarking from a ship, with the same info as found in "CreateSuitLoadout" elif event_type == 'SuitLoadout': + suit_slotid = self.suit_loadout_id_from_loadoutid(entry['LoadoutID']) + # Initial suit containing just the data that is then embedded in + # the loadout + new_suit = { + 'name': entry['SuitName'], + 'locName': entry.get('SuitName_Localised', entry['SuitName']), + 'suitId': entry['SuitID'], + } + + # Make the new loadout, in the CAPI format new_loadout = { - 'loadoutSlotId': self.suit_loadout_id_from_loadoutid(entry['LoadoutID']), - 'suit': { - 'name': entry['SuitName'], - 'locName': entry.get('SuitName_Localised', entry['SuitName']), - 'suitId': entry['SuitID'], - }, + 'loadoutSlotId': suit_slotid, + 'suit': new_suit, 'name': entry['LoadoutName'], 'slots': self.suit_loadout_slots_array_to_dict(entry['Modules']), } + + # Assign this loadout into our state self.state['SuitLoadouts'][new_loadout['loadoutSlotId']] = new_loadout self.state['SuitLoadoutCurrent'] = new_loadout - # TODO: Well we know about the **SUIT**, as opposed to the Loadout ? - # self.state['SuitCurrent'] = self.state['Suits'][f'{new_suitid}'] + + # Now add in the extra fields for new_suit to be a 'full' Suit structure + new_suit['id'] = None # Not available in 4.0.0.100 journal event + new_suit['slots'] = new_loadout['slots'] # 'slots', not 'Modules', to match CAPI + + # Ensure new_suit is in self.state['Suits'] + self.state['Suits'][suit_slotid] = new_suit + self.state['SuitCurrent'] = new_suit elif event_type == 'SwitchSuitLoadout': loadoutid = entry['LoadoutID'] From 8b3094dd6baacfd9acb4a29f2b14695f2ab6849e Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 May 2021 11:09:32 +0100 Subject: [PATCH 14/35] SuitLoadout: Now showing Suit UI if set --- EDMarketConnector.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 4fad261b..77ae8772 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -633,6 +633,14 @@ class AppWindow(object): loadout_name = suitloadout['name'] self.suit['text'] = f'{suitname} ({loadout_name})' + def suit_show_if_set(self) -> None: + """Show UI Suit row if we have data, else hide.""" + if self.suit['text'] != '': + self.toggle_suit_row(visible=True) + + else: + self.toggle_suit_row(visible=False) + def toggle_suit_row(self, visible: Optional[bool] = None) -> None: """ Toggle the visibility of the 'Suit' row. @@ -922,10 +930,7 @@ class AppWindow(object): self.suit['text'] = f'{suitname} ({loadout_name})' - self.toggle_suit_row(visible=True) - - else: - self.toggle_suit_row(visible=False) + self.suit_show_if_set() if data['commander'].get('credits') is not None: monitor.state['Credits'] = data['commander']['credits'] @@ -1052,6 +1057,7 @@ class AppWindow(object): self.cmdr['text'] += ' (beta)' self.update_suit_text() + self.suit_show_if_set() self.edit_menu.entryconfigure(0, state=monitor.system and tk.NORMAL or tk.DISABLED) # Copy From 0fd2264d5388a4c21d519cc1267e8ec4376d40e1 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 May 2021 11:26:04 +0100 Subject: [PATCH 15/35] UseConsumable: Rely on `BackpackChange` instead, to avoid double-accounting --- monitor.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/monitor.py b/monitor.py index 9787f192..0890b680 100644 --- a/monitor.py +++ b/monitor.py @@ -1019,24 +1019,28 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.state['BackPack'][entry['Type']][i] = 0 elif event_type == 'UseConsumable': - # alpha4 - # When using an item from the player’s inventory (backpack) - # - # Parameters: - # • Name - # • Type - # TODO: XXX: From v31 doc # 12.2 BackpackChange # This is written when there is any change to the contents of the # suit backpack – note this can be written at the same time as other # events like UseConsumable - for c in self.state['BackPack']['Consumable']: - if c == entry['Name']: - self.state['BackPack']['Consumable'][c] -= 1 - # Paranoia in case we lost track - if self.state['BackPack']['Consumable'][c] < 0: - self.state['BackPack']['Consumable'][c] = 0 + + # In 4.0.0.100 it is observed that: + # + # 1. Throw of any grenade type *only* causes a BackpackChange event, no + # accompanying 'UseConsumable'. + # 2. Using an Energy Cell causes both UseConsumable and BackpackChange, + # in that order. + # 3. Medkit acts the same as Energy Cell. + # + # Thus we'll just ignore 'UseConsumable' for now. + # for c in self.state['BackPack']['Consumable']: + # if c == entry['Name']: + # self.state['BackPack']['Consumable'][c] -= 1 + # # Paranoia in case we lost track + # if self.state['BackPack']['Consumable'][c] < 0: + # self.state['BackPack']['Consumable'][c] = 0 + ... # TODO: # From 35a0edde58a8cad91a2f7224d06d011c6a48524c Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 May 2021 11:28:15 +0100 Subject: [PATCH 16/35] Use `pass` for "no code intended" `...` is better suited to "under construction, more to come". --- monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index 0890b680..49028521 100644 --- a/monitor.py +++ b/monitor.py @@ -1040,7 +1040,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # # Paranoia in case we lost track # if self.state['BackPack']['Consumable'][c] < 0: # self.state['BackPack']['Consumable'][c] = 0 - ... + pass # TODO: # From e650308e3d0d6400581da070f0bbfe771ff0ac2e Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 May 2021 15:28:52 +0100 Subject: [PATCH 17/35] EngineerProgress: Extra validation paranoia This is an example of just how icky it gets validating an event by hand. Technically we need a function, many would probably be lengthier and more complex than this one, for **every single journal event type**. --- monitor.py | 75 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/monitor.py b/monitor.py index 49028521..a32a6a61 100644 --- a/monitor.py +++ b/monitor.py @@ -762,20 +762,24 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.state[event_type] = payload elif event_type == 'EngineerProgress': - engineers = self.state['Engineers'] - if 'Engineers' in entry: # Startup summary - self.state['Engineers'] = { - e['Engineer']: ((e['Rank'], e.get('RankProgress', 0)) if 'Rank' in e else e['Progress']) - for e in entry['Engineers'] - } + # Sanity check - at least once the 'Engineer' (name) was missing from this in early + # Odyssey 4.0.0.100. Might only have been a server issue causing incomplete data. - else: # Promotion - engineer = entry['Engineer'] - if 'Rank' in entry: - engineers[engineer] = (entry['Rank'], entry.get('RankProgress', 0)) + if self.event_valid_engineerprogress(entry): + engineers = self.state['Engineers'] + if 'Engineers' in entry: # Startup summary + self.state['Engineers'] = { + e['Engineer']: ((e['Rank'], e.get('RankProgress', 0)) if 'Rank' in e else e['Progress']) + for e in entry['Engineers'] + } - else: - engineers[engineer] = entry['Progress'] + else: # Promotion + engineer = entry['Engineer'] + if 'Rank' in entry: + engineers[engineer] = (entry['Rank'], entry.get('RankProgress', 0)) + + else: + engineers[engineer] = entry['Progress'] elif event_type == 'Cargo' and entry.get('Vessel') == 'Ship': self.state['Cargo'] = defaultdict(int) @@ -1629,6 +1633,53 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below logger.debug(f'Invalid journal entry:\n{line!r}\n', exc_info=ex) return {'event': None} + # TODO: *This* will need refactoring and a proper validation infrastructure + # designed for this in the future. This is a bandaid for a known issue. + def event_valid_engineerprogress(self, entry) -> bool: # noqa: CCR001 + """ + Check an `EngineerProgress` Journal event for validity. + + :param entry: Journal event dict + :return: True if passes validation, else False. + """ + # The event should have at least one of thes + if 'Engineers' not in entry and 'Progress' not in entry: + logger.warning(f"EngineerProgress has neither 'Engineers' nor 'Progress': {entry=}") + return False + + # But not both of them + if 'Engineers' in entry and 'Progress' in entry: + logger.warning(f"EngineerProgress has BOTH 'Engineers' and 'Progress': {entry=}") + return False + + if 'Engineers' in entry: + # 'Engineers' version should have a list as value + if not isinstance(entry['Engineers'], list): + logger.warning(f"EngineerProgress 'Engineers' is not a list: {entry=}") + return False + + # It should have at least one entry? This might still be valid ? + if len(entry['Engineers']) < 1: + logger.warning(f"EngineerProgress 'Engineers' list is empty ?: {entry=}") + # TODO: As this might be valid, we might want to only log + return False + + # And that list should have all of these keys + for e in entry['Engineers']: + for f in ('Engineer', 'EngineerID', 'Rank', 'Progress', 'RankProgress'): + if f not in e: + logger.warning(f"Engineer entry without '{f=}' key: {e=}") + return False + + if 'Progress' in entry: + for e in entry['Engineers']: + for f in ('Engineer', 'EngineerID', 'Rank', 'Progress', 'RankProgress'): + if f not in e: + logger.warning(f"Engineer entry without '{f=}' key: {e=}") + return False + + return True + def suit_loadout_id_from_loadoutid(self, journal_loadoutid: int) -> int: """ Determine the CAPI-oriented numeric slot id for a Suit Loadout. From a82099fe531dbd289e58ff3c6de8823281790e4f Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 May 2021 15:38:06 +0100 Subject: [PATCH 18/35] EngineerProgress: Don't need `f=`, just `f` here. --- monitor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monitor.py b/monitor.py index a32a6a61..2b6b1f9e 100644 --- a/monitor.py +++ b/monitor.py @@ -1668,14 +1668,14 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below for e in entry['Engineers']: for f in ('Engineer', 'EngineerID', 'Rank', 'Progress', 'RankProgress'): if f not in e: - logger.warning(f"Engineer entry without '{f=}' key: {e=}") + logger.warning(f"Engineer entry without '{f}' key: {e=}") return False if 'Progress' in entry: for e in entry['Engineers']: for f in ('Engineer', 'EngineerID', 'Rank', 'Progress', 'RankProgress'): if f not in e: - logger.warning(f"Engineer entry without '{f=}' key: {e=}") + logger.warning(f"Engineer entry without '{f}' key: {e=}") return False return True From 2184afba9b1b27d0eb2a656279fcf7c616dd3256 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 May 2021 15:51:28 +0100 Subject: [PATCH 19/35] EngineerProgress: Rank/RankProgress is complicated. And, yes, flake8 checks, validating these events *is* complex. --- monitor.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/monitor.py b/monitor.py index 2b6b1f9e..4c6bc957 100644 --- a/monitor.py +++ b/monitor.py @@ -1635,7 +1635,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # TODO: *This* will need refactoring and a proper validation infrastructure # designed for this in the future. This is a bandaid for a known issue. - def event_valid_engineerprogress(self, entry) -> bool: # noqa: CCR001 + def event_valid_engineerprogress(self, entry) -> bool: # noqa: CCR001 C901 """ Check an `EngineerProgress` Journal event for validity. @@ -1668,14 +1668,20 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below for e in entry['Engineers']: for f in ('Engineer', 'EngineerID', 'Rank', 'Progress', 'RankProgress'): if f not in e: - logger.warning(f"Engineer entry without '{f}' key: {e=}") + # For some Progress there's no Rank/RankProgress yet + if f in ('Rank', 'RankProgress'): + if (progress := e.get('Progress', None)) is not None: + if progress in ('Invited', 'Known'): + continue + + logger.warning(f"Engineer entry without '{f}' key: {e=} in {entry=}") return False if 'Progress' in entry: for e in entry['Engineers']: for f in ('Engineer', 'EngineerID', 'Rank', 'Progress', 'RankProgress'): if f not in e: - logger.warning(f"Engineer entry without '{f}' key: {e=}") + logger.warning(f"Engineer entry without '{f}' key: {e=} in {entry=}") return False return True From 278a78c09a1a0b27b7f55450c1a0b075482941ff Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 May 2021 17:34:09 +0100 Subject: [PATCH 20/35] plugins/inara: Don't try to send None-system location I'm not sure normal use can cause this, but it's an easy obvious check. --- plugins/inara.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index e3fe6a4f..5e08cf74 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -449,14 +449,17 @@ def journal_entry( # noqa: C901, CCR001 ) # Update location - new_add_event( - 'setCommanderTravelLocation', - entry['timestamp'], - OrderedDict([ - ('starsystemName', system), - ('stationName', station), # Can be None - ]) - ) + # Might not be available if this event is a 'StartUp' and we're replaying + # a log. + if system: + new_add_event( + 'setCommanderTravelLocation', + entry['timestamp'], + OrderedDict([ + ('starsystemName', system), + ('stationName', station), # Can be None + ]) + ) # Update ship if state['ShipID']: # Unknown if started in Fighter or SRV From 143a205fb9074bb9314fb59441a71a279e1dc8da Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 May 2021 18:13:51 +0100 Subject: [PATCH 21/35] Backpack: event name is CamelCase, stored in monitor.state, doc * Surprise! The new event is `BackPack`, not `Backpack`, although the filename *is* `Backpack.json`. * Store the loaded JSON dict in `monitor.state['BackpackJSON']`. That `p` is lower case to match with the filename, not the event name. * Document this in PLUGINS.md. Unless EDSM is telling us to discard this we should now be sending it. --- PLUGINS.md | 1 + monitor.py | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 6a14b445..1eb2a4a2 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -599,6 +599,7 @@ Content of `state` (updated to the current journal entry): | `Consumable` | `dict` | 'Consumable' MicroResources in Odyssey, `int` count each. | | `Data` | `dict` | 'Data' MicroResources in Odyssey, `int` count each. | | `BackPack` | `dict` | `dict` of Odyssey MicroResources in backpack. | +| `BackpackJSON` | `dict` | Content of Backpack.json as of last read. | | `SuitCurrent` | `dict` | CAPI-returned data of currently worn suit. NB: May be `None` if no data. | | `Suits` | `dict`[1] | CAPI-returned data of owned suits. NB: May be `None` if no data. | | `SuitLoadoutCurrent` | `dict` | CAPI-returned data of current Suit Loadout. NB: May be `None` if no data. | diff --git a/monitor.py b/monitor.py index 49028521..899a28a5 100644 --- a/monitor.py +++ b/monitor.py @@ -151,6 +151,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below 'Item': defaultdict(int), # BackPack Items 'Data': defaultdict(int), # Backpack Data }, + 'BackpackJSON': None, # Raw JSON from `Backpack.json` file, if available 'SuitCurrent': None, 'Suits': {}, 'SuitLoadoutCurrent': None, @@ -866,17 +867,21 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below {self.canonicalise(x['Name']): x['Count'] for x in clean_data} ) - elif event_type == 'Backpack': + elif event_type == 'BackPack': # TODO: v31 doc says this is`backpack.json` ... but Howard Chalkley # said it's `Backpack.json` with open(join(self.currentdir, 'Backpack.json'), 'rb') as backpack: # type: ignore try: - entry = json.load(backpack) + # Preserve property order because why not? + entry = json.load(backpack, object_pairs_hook=OrderedDict) except json.JSONDecodeError: logger.exception('Failed decoding Backpack.json', exc_info=True) else: + # Store in monitor.state + self.state['BackpackJSON'] = entry + # Assume this reflects the current state when written self.state['BackPack']['Component'] = defaultdict(int) self.state['BackPack']['Consumable'] = defaultdict(int) From e5909e90240121c37f02c67aa7bb9de2ff59d3b3 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 May 2021 18:26:51 +0100 Subject: [PATCH 22/35] EDSM: Use loaded BackPack data, not the empty event. --- plugins/edsm.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/edsm.py b/plugins/edsm.py index 66b833c6..422d0b56 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -432,6 +432,11 @@ def journal_entry( elif entry['event'] == 'NavBeaconScan': this.navbeaconscan = entry['NumBodies'] + elif entry['event'] == 'BackPack': + # Use the stored file contents, not the empty journal event + if state['BackpackJSON']: + entry = state['BackpackJSON'] + # Send interesting events to EDSM if ( config.get_int('edsm_out') and not is_beta and not this.multicrew and credentials(cmdr) and From be100d97dee1e5235acfd4faf2963c8acc300920 Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 21 May 2021 08:24:00 +0200 Subject: [PATCH 23/35] Update credits on SuitUpdate Closes #1079 --- monitor.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/monitor.py b/monitor.py index 49028521..76394e6c 100644 --- a/monitor.py +++ b/monitor.py @@ -1229,10 +1229,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # • SuitID # • Class # • Cost - # Update credits total ? It shouldn't even involve credits! - # Actual alpha4 - need to grind mats - # if self.state['Suits']: - pass + # TODO: Update self.state['Suits'] when we have an example to work from + self.state['Credits'] -= entry.get('Cost', 0) elif event_type == 'LoadoutEquipModule': # alpha4: From 924221f063b6e05cb4fc506f8bb4c36307b8fc46 Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 21 May 2021 08:39:13 +0200 Subject: [PATCH 24/35] Show whether or not the git wdir is dirty in version Closes #1073 --- config.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/config.py b/config.py index 3476095d..85dadc5e 100644 --- a/config.py +++ b/config.py @@ -96,9 +96,21 @@ def git_shorthash_from_head() -> str: """ Determine short hash for current git HEAD. + Includes -DIRTY if any changes have been made from HEAD + :return: str - None if we couldn't determine the short hash. """ shorthash: str = None # type: ignore + dirty = False + + with contextlib.suppress(Exception): + result = subprocess.run('git diff --stat HEAD'.split(), capture_output=True) + if len(result.stdout) > 0: + dirty = True + + if len(result.stderr) > 0: + logger.warning(f'Data from git on stderr:\n{str(result.stderr)}') + try: git_cmd = subprocess.Popen('git rev-parse --short HEAD'.split(), stdout=subprocess.PIPE, @@ -115,7 +127,7 @@ def git_shorthash_from_head() -> str: logger.error(f"'{shorthash}' doesn't look like a valid git short hash, forcing to None") shorthash = None # type: ignore - return shorthash + return shorthash + ('-WORKING-DIR-IS-DIRTY' if dirty else '') def appversion() -> semantic_version.Version: From 0219098d054af7ade6e8461ca5b35a8bd1e50a5e Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 21 May 2021 11:42:03 +0100 Subject: [PATCH 25/35] UI Suit: Keep line hidden if Odyssey not detected Else we'll show the line with '' always in Horizons --- EDMarketConnector.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 77ae8772..2f94f660 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -620,6 +620,11 @@ class AppWindow(object): def update_suit_text(self) -> None: """Update the suit text for current type and loadout.""" + if not monitor.state['Odyssey']: + # Odyssey not detected, no text should be set so it will hide + self.suit['text'] = '' + return + if (suit := monitor.state.get('SuitCurrent')) is None: self.suit['text'] = f'<{_("Unknown")}>' return From 3a92ff9f96b861e8c49a4be7c89bd8e5089a0b21 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 21 May 2021 11:42:59 +0100 Subject: [PATCH 26/35] SwitchSuitLoadout: Example from 4.0.0.101 added in comments --- monitor.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/monitor.py b/monitor.py index 907ccf04..4f56d7f5 100644 --- a/monitor.py +++ b/monitor.py @@ -1087,6 +1087,19 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.state['SuitCurrent'] = new_suit elif event_type == 'SwitchSuitLoadout': + # 4.0.0.101 + # + # { "timestamp":"2021-05-21T10:39:43Z", "event":"SwitchSuitLoadout", + # "SuitID":1700217809818876, "SuitName":"utilitysuit_class1", + # "SuitName_Localised":"Maverick Suit", "LoadoutID":4293000002, + # "LoadoutName":"K/P", "Modules":[ { "SlotName":"PrimaryWeapon1", + # "SuitModuleID":1700217863661544, + # "ModuleName":"wpn_m_assaultrifle_kinetic_fauto", + # "ModuleName_Localised":"Karma AR-50" }, + # { "SlotName":"SecondaryWeapon", "SuitModuleID":1700216180036986, + # "ModuleName":"wpn_s_pistol_plasma_charged", + # "ModuleName_Localised":"Manticore Tormentor" } ] } + # loadoutid = entry['LoadoutID'] new_slot = self.suit_loadout_id_from_loadoutid(loadoutid) # If this application is run with the latest Journal showing such an event then we won't From f923ef64ca568f5c8237690e3c1a786930cbb5c9 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 21 May 2021 12:08:22 +0100 Subject: [PATCH 27/35] Suits: Refactor 'SuitLoadout' to also be used in 'SwitchSuitLoadout' --- monitor.py | 91 ++++++++++++++++++++++-------------------------------- 1 file changed, 37 insertions(+), 54 deletions(-) diff --git a/monitor.py b/monitor.py index 4f56d7f5..4374e368 100644 --- a/monitor.py +++ b/monitor.py @@ -1057,34 +1057,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # this version of the docs: "SuitLoadout": # when starting on foot, or # when disembarking from a ship, with the same info as found in "CreateSuitLoadout" elif event_type == 'SuitLoadout': - suit_slotid = self.suit_loadout_id_from_loadoutid(entry['LoadoutID']) - # Initial suit containing just the data that is then embedded in - # the loadout - new_suit = { - 'name': entry['SuitName'], - 'locName': entry.get('SuitName_Localised', entry['SuitName']), - 'suitId': entry['SuitID'], - } - - # Make the new loadout, in the CAPI format - new_loadout = { - 'loadoutSlotId': suit_slotid, - 'suit': new_suit, - 'name': entry['LoadoutName'], - 'slots': self.suit_loadout_slots_array_to_dict(entry['Modules']), - } - - # Assign this loadout into our state - self.state['SuitLoadouts'][new_loadout['loadoutSlotId']] = new_loadout - self.state['SuitLoadoutCurrent'] = new_loadout - - # Now add in the extra fields for new_suit to be a 'full' Suit structure - new_suit['id'] = None # Not available in 4.0.0.100 journal event - new_suit['slots'] = new_loadout['slots'] # 'slots', not 'Modules', to match CAPI - - # Ensure new_suit is in self.state['Suits'] - self.state['Suits'][suit_slotid] = new_suit - self.state['SuitCurrent'] = new_suit + self.store_suitloadout_from_event(entry) elif event_type == 'SwitchSuitLoadout': # 4.0.0.101 @@ -1100,32 +1073,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # "ModuleName":"wpn_s_pistol_plasma_charged", # "ModuleName_Localised":"Manticore Tormentor" } ] } # - loadoutid = entry['LoadoutID'] - new_slot = self.suit_loadout_id_from_loadoutid(loadoutid) - # If this application is run with the latest Journal showing such an event then we won't - # yet have the CAPI data, so no idea about Suits or Loadouts. - if self.state['Suits'] and self.state['SuitLoadouts']: - try: - self.state['SuitLoadoutCurrent'] = self.state['SuitLoadouts'][f'{new_slot}'] - - except KeyError: - logger.debug(f"KeyError getting suit loadout after switch, bad slot: {new_slot} ({loadoutid})") - self.state['SuitCurrent'] = None - self.state['SuitLoadoutCurrent'] = None - - else: - try: - new_suitid = self.state['SuitLoadoutCurrent']['suit']['suitId'] - - except KeyError: - logger.debug(f"KeyError getting switched-to suit ID from slot {new_slot} ({loadoutid})") - - else: - try: - self.state['SuitCurrent'] = self.state['Suits'][f'{new_suitid}'] - - except KeyError: - logger.debug(f"KeyError getting switched-to suit from slot {new_slot} ({loadoutid}") + self.store_suitloadout_from_event(entry) elif event_type == 'CreateSuitLoadout': # We know we won't have data for this new one @@ -1649,6 +1597,41 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below logger.debug(f'Invalid journal entry:\n{line!r}\n', exc_info=ex) return {'event': None} + def store_suitloadout_from_event(self, entry) -> None: + """ + Store Suit and SuitLoadout data from a journal event. + + Also use set currently in-use instances of them as being as per this + event. + + :param entry: Journal entry - 'SwitchSuitLoadout' or 'SuitLoadout' + """ + suit_slotid = self.suit_loadout_id_from_loadoutid(entry['LoadoutID']) + # Initial suit containing just the data that is then embedded in + # the loadout + new_suit = { + 'name': entry['SuitName'], + 'locName': entry.get('SuitName_Localised', entry['SuitName']), + 'suitId': entry['SuitID'], + } + # Make the new loadout, in the CAPI format + new_loadout = { + 'loadoutSlotId': suit_slotid, + 'suit': new_suit, + 'name': entry['LoadoutName'], + 'slots': self.suit_loadout_slots_array_to_dict( + entry['Modules']), + } + # Assign this loadout into our state + self.state['SuitLoadouts'][f"{new_loadout['loadoutSlotId']}"] = new_loadout + self.state['SuitLoadoutCurrent'] = new_loadout + # Now add in the extra fields for new_suit to be a 'full' Suit structure + new_suit['id'] = None # Not available in 4.0.0.100 journal event + new_suit['slots'] = new_loadout['slots'] # 'slots', not 'Modules', to match CAPI + # Ensure new_suit is in self.state['Suits'] + self.state['Suits'][f"{suit_slotid}"] = new_suit + self.state['SuitCurrent'] = new_suit + # TODO: *This* will need refactoring and a proper validation infrastructure # designed for this in the future. This is a bandaid for a known issue. def event_valid_engineerprogress(self, entry) -> bool: # noqa: CCR001 C901 From 43d261f290e4753aa01bdd9f76ed5aff47ea620d Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 21 May 2021 12:14:18 +0100 Subject: [PATCH 28/35] CreateSuitLoadout: 4.0.0.101 example comment --- monitor.py | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/monitor.py b/monitor.py index 4374e368..a108d7df 100644 --- a/monitor.py +++ b/monitor.py @@ -1076,27 +1076,17 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.store_suitloadout_from_event(entry) elif event_type == 'CreateSuitLoadout': - # We know we won't have data for this new one - # Parameters: - # • SuitID - # • SuitName - # • LoadoutID - # • LoadoutName - # alpha4: - # { "timestamp":"2021-04-29T09:37:08Z", "event":"CreateSuitLoadout", "SuitID":1698364940285172, - # "SuitName":"tacticalsuit_class1", "SuitName_Localised":"Dominator Suit", "LoadoutID":4293000001, - # "LoadoutName":"Dom L/K/K", "Modules":[ - # { - # "SlotName":"PrimaryWeapon1", - # "SuitModuleID":1698364962722310, - # "ModuleName":"wpn_m_assaultrifle_laser_fauto", - # "ModuleName_Localised":"TK Aphelion" - # }, - # { "SlotName":"PrimaryWeapon2", - # "SuitModuleID":1698364956302993, "ModuleName":"wpn_m_assaultrifle_kinetic_fauto", - # "ModuleName_Localised":"Karma AR-50" }, { "SlotName":"SecondaryWeapon", - # "SuitModuleID":1698292655291850, "ModuleName":"wpn_s_pistol_kinetic_sauto", - # "ModuleName_Localised":"Karma P-15" } ] } + # 4.0.0.101 + # + # { "timestamp":"2021-05-21T11:13:15Z", "event":"CreateSuitLoadout", "SuitID":1700216165682989, + # "SuitName":"tacticalsuit_class1", "SuitName_Localised":"Dominator Suit", "LoadoutID":4293000004, + # "LoadoutName":"P/P/K", "Modules":[ { "SlotName":"PrimaryWeapon1", "SuitModuleID":1700216182854765, + # "ModuleName":"wpn_m_assaultrifle_plasma_fauto", "ModuleName_Localised":"Manticore Oppressor" }, + # { "SlotName":"PrimaryWeapon2", "SuitModuleID":1700216190363340, + # "ModuleName":"wpn_m_shotgun_plasma_doublebarrel", "ModuleName_Localised":"Manticore Intimidator" }, + # { "SlotName":"SecondaryWeapon", "SuitModuleID":1700217869872834, + # "ModuleName":"wpn_s_pistol_kinetic_sauto", "ModuleName_Localised":"Karma P-15" } ] } + # new_loadout = { 'loadoutSlotId': self.suit_loadout_id_from_loadoutid(entry['LoadoutID']), 'suit': { From 7e064374d21404615612d8f096b82e60b802dd49 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 21 May 2021 12:27:16 +0100 Subject: [PATCH 29/35] Suits: Refactor suit/loadout set-current away from store This way we can have common code for SuitLoadout, SwitchSuitLoadout and CreateSuitLoadout, with the first two then calling the new `self.suit_and_loadout_setcurrent()` to set the seen data as also the currently in use suit/loadout. --- monitor.py | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/monitor.py b/monitor.py index a108d7df..ad0385d9 100644 --- a/monitor.py +++ b/monitor.py @@ -1057,7 +1057,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # this version of the docs: "SuitLoadout": # when starting on foot, or # when disembarking from a ship, with the same info as found in "CreateSuitLoadout" elif event_type == 'SuitLoadout': - self.store_suitloadout_from_event(entry) + suit_slotid, suitloadout_slotid = self.suitloadout_store_from_event(entry) + self.suit_and_loadout_setcurrent(suit_slotid, suitloadout_slotid) elif event_type == 'SwitchSuitLoadout': # 4.0.0.101 @@ -1073,7 +1074,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # "ModuleName":"wpn_s_pistol_plasma_charged", # "ModuleName_Localised":"Manticore Tormentor" } ] } # - self.store_suitloadout_from_event(entry) + suit_slotid, suitloadout_slotid = self.suitloadout_store_from_event(entry) + self.suit_and_loadout_setcurrent(suit_slotid, suitloadout_slotid) elif event_type == 'CreateSuitLoadout': # 4.0.0.101 @@ -1087,17 +1089,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # { "SlotName":"SecondaryWeapon", "SuitModuleID":1700217869872834, # "ModuleName":"wpn_s_pistol_kinetic_sauto", "ModuleName_Localised":"Karma P-15" } ] } # - new_loadout = { - 'loadoutSlotId': self.suit_loadout_id_from_loadoutid(entry['LoadoutID']), - 'suit': { - 'name': entry['SuitName'], - 'locName': entry.get('SuitName_Localised', entry['SuitName']), - 'suitId': entry['SuitID'], - }, - 'name': entry['LoadoutName'], - 'slots': self.suit_loadout_slots_array_to_dict(entry['Modules']), - } - self.state['SuitLoadouts'][new_loadout['loadoutSlotId']] = new_loadout + _, _ = self.suitloadout_store_from_event(entry) elif event_type == 'DeleteSuitLoadout': # alpha4: @@ -1587,7 +1579,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below logger.debug(f'Invalid journal entry:\n{line!r}\n', exc_info=ex) return {'event': None} - def store_suitloadout_from_event(self, entry) -> None: + def suitloadout_store_from_event(self, entry) -> Tuple[int, int]: """ Store Suit and SuitLoadout data from a journal event. @@ -1595,6 +1587,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below event. :param entry: Journal entry - 'SwitchSuitLoadout' or 'SuitLoadout' + :return Tuple[suit_slotid, suitloadout_slotid]: The IDs we set data for. """ suit_slotid = self.suit_loadout_id_from_loadoutid(entry['LoadoutID']) # Initial suit containing just the data that is then embedded in @@ -1614,13 +1607,36 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below } # Assign this loadout into our state self.state['SuitLoadouts'][f"{new_loadout['loadoutSlotId']}"] = new_loadout - self.state['SuitLoadoutCurrent'] = new_loadout + # Now add in the extra fields for new_suit to be a 'full' Suit structure new_suit['id'] = None # Not available in 4.0.0.100 journal event new_suit['slots'] = new_loadout['slots'] # 'slots', not 'Modules', to match CAPI # Ensure new_suit is in self.state['Suits'] self.state['Suits'][f"{suit_slotid}"] = new_suit - self.state['SuitCurrent'] = new_suit + + return suit_slotid, new_loadout['loadoutSlotId'] + + def suit_and_loadout_setcurrent(self, suit_slotid: int, suitloadout_slotid: int) -> bool: + """ + Set self.state for SuitCurrent and SuitLoadoutCurrent as requested. + + If the specified slots are unknown we abort and return False, else + return True. + + :param suit_slotid: Numeric ID of the slot for the suit. + :param suitloadout_slotid: Numeric ID of the slot for the suit loadout. + :return: True if we could do this, False if not. + """ + str_suitid = f"{suit_slotid}" + str_suitloadoutid = f"{suitloadout_slotid}" + + if (self.state['Suits'].get(str_suitid, False) + and self.state['SuitLoadouts'].get(str_suitloadoutid, False)): + self.state['SuitCurrent'] = self.state['Suits'][str_suitid] + self.state['SuitLoadoutCurrent'] = self.state['SuitLoadouts'][str_suitloadoutid] + return True + + return False # TODO: *This* will need refactoring and a proper validation infrastructure # designed for this in the future. This is a bandaid for a known issue. From da94b729bd553a8c3fbc7adbd1dc987fdd6ad369 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 21 May 2021 12:31:08 +0100 Subject: [PATCH 30/35] Suits: Log if we try to set unknown suit/loadout --- monitor.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/monitor.py b/monitor.py index ad0385d9..eeedfbd6 100644 --- a/monitor.py +++ b/monitor.py @@ -1058,7 +1058,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # when disembarking from a ship, with the same info as found in "CreateSuitLoadout" elif event_type == 'SuitLoadout': suit_slotid, suitloadout_slotid = self.suitloadout_store_from_event(entry) - self.suit_and_loadout_setcurrent(suit_slotid, suitloadout_slotid) + if not self.suit_and_loadout_setcurrent(suit_slotid, suitloadout_slotid): + logger.error(f"Event was: {entry}") elif event_type == 'SwitchSuitLoadout': # 4.0.0.101 @@ -1075,7 +1076,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # "ModuleName_Localised":"Manticore Tormentor" } ] } # suit_slotid, suitloadout_slotid = self.suitloadout_store_from_event(entry) - self.suit_and_loadout_setcurrent(suit_slotid, suitloadout_slotid) + if not self.suit_and_loadout_setcurrent(suit_slotid, suitloadout_slotid): + logger.error(f"Event was: {entry}") elif event_type == 'CreateSuitLoadout': # 4.0.0.101 @@ -1636,6 +1638,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.state['SuitLoadoutCurrent'] = self.state['SuitLoadouts'][str_suitloadoutid] return True + logger.error(f"Tried to set a suit and suitloadout where we didn't know about both: {suit_slotid=}, " + f"{str_suitloadoutid=}") return False # TODO: *This* will need refactoring and a proper validation infrastructure From 9cfa60ae267f87224a71867a72c7181b1b4e6960 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 21 May 2021 14:23:35 +0100 Subject: [PATCH 31/35] CAPI: Make user-visible error text more obvious Mostly call out this is 'Frontier CAPI' related. --- companion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion.py b/companion.py index ca0a529c..4898cd84 100644 --- a/companion.py +++ b/companion.py @@ -541,7 +541,7 @@ class Session(object): except Exception as e: logger.debug('Attempting GET', exc_info=e) - raise ServerError(f'unable to get endpoint {endpoint}') from e + raise ServerError(f'Frontier CAPI query failure: {endpoint}') from e if r.url.startswith(SERVER_AUTH): logger.info('Redirected back to Auth Server') From 89c486401e2afb7b9164a2cee2b34328c153a5f3 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 21 May 2021 14:25:51 +0100 Subject: [PATCH 32/35] Translations: companion.py "Frontier CAPI query failure" --- L10n/en.template | 3 +++ companion.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/L10n/en.template b/L10n/en.template index 11ed447a..4ccf2c6d 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -230,6 +230,9 @@ /* Section heading in settings. [prefs.py] */ "File location" = "File location"; +/* Failures to access Frontier CAPI endpoints [companion.py] */ +"Frontier CAPI query failure" = "Frontier CAPI query failure"; + /* CQC rank. [stats.py] */ "Gladiator" = "Gladiator"; diff --git a/companion.py b/companion.py index 4898cd84..79858b90 100644 --- a/companion.py +++ b/companion.py @@ -541,7 +541,7 @@ class Session(object): except Exception as e: logger.debug('Attempting GET', exc_info=e) - raise ServerError(f'Frontier CAPI query failure: {endpoint}') from e + raise ServerError(f'{_("Frontier CAPI query failure")}: {endpoint}') from e if r.url.startswith(SERVER_AUTH): logger.info('Redirected back to Auth Server') From 431d83b5ca59d4392d9c948beeb17430b8c34614 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 21 May 2021 14:32:26 +0100 Subject: [PATCH 33/35] companion: Tweak 'server returned an error' message & translate --- L10n/en.template | 3 +++ companion.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/L10n/en.template b/L10n/en.template index 4ccf2c6d..0065c1e7 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -233,6 +233,9 @@ /* Failures to access Frontier CAPI endpoints [companion.py] */ "Frontier CAPI query failure" = "Frontier CAPI query failure"; +/* Server errors from Frontier CAPI server [companion.py] */ +"Frontier CAPI server error" = "Frontier CAPI server error"; + /* CQC rank. [stats.py] */ "Gladiator" = "Gladiator"; diff --git a/companion.py b/companion.py index 79858b90..953e5fb4 100644 --- a/companion.py +++ b/companion.py @@ -556,7 +556,7 @@ class Session(object): # Server error. Typically 500 "Internal Server Error" if server is down logger.debug('500 status back from CAPI') self.dump(r) - raise ServerError(f'Received error {r.status_code} from server') + raise ServerError(f'{_("Frontier CAPI server error")}: {r.status_code}') try: r.raise_for_status() # Typically 403 "Forbidden" on token expiry From fd5a721b813e6e8f18729fc3d248de6a053af218 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 21 May 2021 14:57:27 +0100 Subject: [PATCH 34/35] Release 5.0.2: appversion and changelog --- ChangeLog.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ config.py | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 3f33470f..c73af664 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -17,6 +17,60 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in the source (it's not distributed with the Windows installer) for the currently used version in a given branch. +Release 5.0.2 +=== + +This release is primarily aimed at getting the UI "`Suit: ...`" line working +properly. + +* The "`Suit: ...`" UI line should now function as best it can given the + available data from the game. It should not appear if you have launched + the Horizons version of the game, even if your account has Odyssey + enabled. You might see "``" as the text when this application + does not yet have the required data. + +* Changed the less than obvious "`unable to get endpoint: /profile`" error + message to "`Frontier CAPI query failure: /profile`", and similarly for the + other CAPI endpoints we attempt to access. This new form is potentially + translated, but translators need time to do that. + + In addition the old message "`Received error {r.status_code} from server`" + has been changed to "`Frontier CAPI server error: {r.status_code}`" and is + potentially translated. + +* The filenames used for 'Market data in CSV format file' will now be sane, + and as they were before 5.0.0. + +* Linux: 'Shipyard provider' will no longer default to showing 'False' if + no specific provider has been selected. + +Plugin Developers +--- + +* Extra `Flagse` values added in the live release of Odyssey have been added to + `edmc_data.py`. + +* Odyssey 'BackPack' values should now track better, but might still not be + perfect due to Journal bugs/shortcomings. + +* `state` passed to `journal_entry()` now has a `BackpackJSON` (note the case) + member which is a copy of the data from the `Backpack.json` (yes, that's + currently the correct case) file that is written when there's a `BackPack` + (guess what, yes, that is currently the correct case) event written to + the Journal. + +* `state['Credits']` tracking is almost certainly not perfect. We're + accounting for the credits component of `SuitUpgrade` now, but there + might be other such we've yet accounted for. + +* `state['Suits']` and associated other keys should now be tracking from + Journal events, where possible, as well as CAPI data. + +* There is a section in PLUGINS.md about how to package an extra Python + module with your plugin. Note the new caveat in + [PLUGINS.md:Avoiding-pitfalls](./PLUGINS.md#avoiding-potential-pitfalls) + about the name of your plugin's directory. + Release 5.0.1 === diff --git a/config.py b/config.py index 85dadc5e..8c78e64e 100644 --- a/config.py +++ b/config.py @@ -33,7 +33,7 @@ appcmdname = 'EDMC' # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.0.1' +_static_appversion = '5.0.2' copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD' update_feed = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' From a04a31dc048df53027d7eb38eba9c9941567f25f Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 21 May 2021 15:03:35 +0100 Subject: [PATCH 35/35] Translations: Updated from OneSky --- L10n/cs.strings | 6 +++--- L10n/de.strings | 6 +++--- L10n/es.strings | 18 ++++++++++++++++++ L10n/ja.strings | 21 +++++++++++++++++++++ L10n/ko.strings | 15 +++++++++++++++ L10n/pl.strings | 15 +++++++++++++++ L10n/pt-BR.strings | 12 +++++++++--- L10n/pt-PT.strings | 12 +++++++++--- L10n/ru.strings | 6 +++--- L10n/sr-Latn-BA.strings | 6 +++--- L10n/sr-Latn.strings | 6 +++--- 11 files changed, 102 insertions(+), 21 deletions(-) diff --git a/L10n/cs.strings b/L10n/cs.strings index 1a1fa201..35a85378 100644 --- a/L10n/cs.strings +++ b/L10n/cs.strings @@ -1,6 +1,3 @@ -/* Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier CAPI didn't respond" = "Chyba: Frontier CAPI neodpovídá"; - /* Language name */ "!Language" = "Čeština"; @@ -185,6 +182,9 @@ "Error: EDSM {MSG}" = "Chyba: EDSM {MSG}"; +/* Raised when cannot contact the Companion API server. [companion.py] */ +"Error: Frontier CAPI didn't respond" = "Chyba: Frontier CAPI neodpovídá"; + /* OLD: Raised when cannot contact the Companion API server. [companion.py] */ "Error: Frontier server is down" = "Chyba: Server Frontieru nejede"; diff --git a/L10n/de.strings b/L10n/de.strings index e2c15797..cf994243 100644 --- a/L10n/de.strings +++ b/L10n/de.strings @@ -1,6 +1,3 @@ -/* Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier CAPI didn't respond" = "Fehler: Frontier CAPI antwortet nicht"; - /* Language name */ "!Language" = "Deutsch"; @@ -185,6 +182,9 @@ "Error: EDSM {MSG}" = "Fehler: EDSM {MSG}"; +/* Raised when cannot contact the Companion API server. [companion.py] */ +"Error: Frontier CAPI didn't respond" = "Fehler: Frontier CAPI antwortet nicht"; + /* OLD: Raised when cannot contact the Companion API server. [companion.py] */ "Error: Frontier server is down" = "Fehler: Frontier-Server ist offline"; diff --git a/L10n/es.strings b/L10n/es.strings index 9730f672..f0d59d08 100644 --- a/L10n/es.strings +++ b/L10n/es.strings @@ -124,6 +124,15 @@ /* Location of the new Journal file in E:D 2.2. [EDMarketConnector.py] */ "E:D journal file location" = "Localización del archivo de Journal de E:D"; +/* When EDDB functionality has been killswitched. [plugins/eddb.py] */ +"EDDB Journal processing disabled. See Log." = "Procesamiento de Journal para EDDB desactivado. Ver Log."; + +/* When EDDN journal handling has been killswitched [plugins/eddn.py] */ +"EDDN journal handler disabled. See Log." = "Manejo de Journal para EDDN desactivado. Ver Log."; + +/* When EDSM functionality has been killswitched [plugins/edsm.py] */ +"EDSM Handler disabled. See Log." = "Manejo de Journal para EDSM desactivado. Ver Log."; + /* Empire rank. [stats.py] */ "Earl" = "Conde Palatino"; @@ -173,6 +182,9 @@ "Error: EDSM {MSG}" = "Error: EDSM {MSG}"; +/* Raised when cannot contact the Companion API server. [companion.py] */ +"Error: Frontier CAPI didn't respond" = "Error: El CAPI de Frontier no respondió"; + /* OLD: Raised when cannot contact the Companion API server. [companion.py] */ "Error: Frontier server is down" = "Error: El servidor de Frontier está caído"; @@ -251,6 +263,9 @@ /* Section heading in settings. [inara.py] */ "Inara credentials" = "Credenciales de Inara"; +/* When Inara support has been killswitched [plugins/inara.py] */ +"Inara disabled. See Log." = "Inara desactivado. Ver Log."; + /* Settings>Plugins>Information on migrating plugins [prefs.py] */ "Information on migrating plugins" = "Información sobre migración de plugins"; @@ -506,6 +521,9 @@ /* Menu item. [EDMarketConnector.py] */ "Status" = "Estado"; +/* 'Suit' label in main UI' [EDMarketConnector.py] */ +"Suit" = "Traje"; + /* Explorer rank. [stats.py] */ "Surveyor" = "Topógrafo"; diff --git a/L10n/ja.strings b/L10n/ja.strings index 88f34fab..4779a177 100644 --- a/L10n/ja.strings +++ b/L10n/ja.strings @@ -124,6 +124,15 @@ /* Location of the new Journal file in E:D 2.2. [EDMarketConnector.py] */ "E:D journal file location" = "E:Dゲームクライアントのジャーナルファイル出力先"; +/* When EDDB functionality has been killswitched. [plugins/eddb.py] */ +"EDDB Journal processing disabled. See Log." = "EDDBジャーナル処理が無効です。ログを確認してください。"; + +/* When EDDN journal handling has been killswitched [plugins/eddn.py] */ +"EDDN journal handler disabled. See Log." = "EDDNジャーナルハンドラーが無効です。ログを確認してください。"; + +/* When EDSM functionality has been killswitched [plugins/edsm.py] */ +"EDSM Handler disabled. See Log." = "EDSMハンドラーが無効です。ログを確認してください。"; + /* Empire rank. [stats.py] */ "Earl" = "Earl"; @@ -173,6 +182,9 @@ "Error: EDSM {MSG}" = "エラー: EDSM {MSG}"; +/* Raised when cannot contact the Companion API server. [companion.py] */ +"Error: Frontier CAPI didn't respond" = "エラー: Frontier CAPIからの応答がありません"; + /* OLD: Raised when cannot contact the Companion API server. [companion.py] */ "Error: Frontier server is down" = "エラー: Frontier社のサーバがダウンしています"; @@ -218,6 +230,12 @@ /* Section heading in settings. [prefs.py] */ "File location" = "ファイルの出力先"; +/* Failures to access Frontier CAPI endpoints [companion.py] */ +"Frontier CAPI query failure" = "Frontier CAPIクエリ失敗"; + +/* Server errors from Frontier CAPI server [companion.py] */ +"Frontier CAPI server error" = "Frontier CAPIサーバエラー"; + /* CQC rank. [stats.py] */ "Gladiator" = "Gladiator"; @@ -251,6 +269,9 @@ /* Section heading in settings. [inara.py] */ "Inara credentials" = "Inara認証情報"; +/* When Inara support has been killswitched [plugins/inara.py] */ +"Inara disabled. See Log." = "Inaraが無効です。ログを確認してください。"; + /* Settings>Plugins>Information on migrating plugins [prefs.py] */ "Information on migrating plugins" = "プラグインの移行に関する情報"; diff --git a/L10n/ko.strings b/L10n/ko.strings index a0723345..088231f6 100644 --- a/L10n/ko.strings +++ b/L10n/ko.strings @@ -124,6 +124,15 @@ /* Location of the new Journal file in E:D 2.2. [EDMarketConnector.py] */ "E:D journal file location" = "E:D 저널(journal) 파일 위치"; +/* When EDDB functionality has been killswitched. [plugins/eddb.py] */ +"EDDB Journal processing disabled. See Log." = "EDDB 저널 처리 비활성화됨. 로그 참고 바람."; + +/* When EDDN journal handling has been killswitched [plugins/eddn.py] */ +"EDDN journal handler disabled. See Log." = "EDDN 저널 처리 비활성화됨. 로그 참고 바람."; + +/* When EDSM functionality has been killswitched [plugins/edsm.py] */ +"EDSM Handler disabled. See Log." = "EDSM Handler 비활성화됨. 로그 참고 바람."; + /* Empire rank. [stats.py] */ "Earl" = "Earl"; @@ -173,6 +182,9 @@ "Error: EDSM {MSG}" = "오류: EDSM {MSG}"; +/* Raised when cannot contact the Companion API server. [companion.py] */ +"Error: Frontier CAPI didn't respond" = "오류: Frontier CAPI가 응답이 없음"; + /* OLD: Raised when cannot contact the Companion API server. [companion.py] */ "Error: Frontier server is down" = "오류: Frontier 서버 CAPI 연결 불가"; @@ -251,6 +263,9 @@ /* Section heading in settings. [inara.py] */ "Inara credentials" = "Inara 계정 정보"; +/* When Inara support has been killswitched [plugins/inara.py] */ +"Inara disabled. See Log." = "Inara 비활성화됨. 로그 참고 바람."; + /* Settings>Plugins>Information on migrating plugins [prefs.py] */ "Information on migrating plugins" = "플러그인 위치 변경에 대한 정보"; diff --git a/L10n/pl.strings b/L10n/pl.strings index 3958ea31..6e84ce7c 100644 --- a/L10n/pl.strings +++ b/L10n/pl.strings @@ -124,6 +124,15 @@ /* Location of the new Journal file in E:D 2.2. [EDMarketConnector.py] */ "E:D journal file location" = "E:D Lokacja pliku dziennika"; +/* When EDDB functionality has been killswitched. [plugins/eddb.py] */ +"EDDB Journal processing disabled. See Log." = "Wyłączone przetwarzanie dziennika EDDB. Sprawdź logi."; + +/* When EDDN journal handling has been killswitched [plugins/eddn.py] */ +"EDDN journal handler disabled. See Log." = "Wyłączona obsługa dziennika EDDB. Sprawdź logi."; + +/* When EDSM functionality has been killswitched [plugins/edsm.py] */ +"EDSM Handler disabled. See Log." = "Wyłączona obsługa EDDB. Sprawdź logi."; + /* Empire rank. [stats.py] */ "Earl" = "Earl"; @@ -173,6 +182,9 @@ "Error: EDSM {MSG}" = "Błąd: EDSM {MSG}"; +/* Raised when cannot contact the Companion API server. [companion.py] */ +"Error: Frontier CAPI didn't respond" = "Błąd: Brak odpowiedzi od CAPI Frontier"; + /* OLD: Raised when cannot contact the Companion API server. [companion.py] */ "Error: Frontier server is down" = "Błąd: Serwer Frontiera nie odpowiada"; @@ -251,6 +263,9 @@ /* Section heading in settings. [inara.py] */ "Inara credentials" = "Inara - uprawnienia"; +/* When Inara support has been killswitched [plugins/inara.py] */ +"Inara disabled. See Log." = "Inara jest wyłączona. Sprawdź logi."; + /* Settings>Plugins>Information on migrating plugins [prefs.py] */ "Information on migrating plugins" = "Informacja o migracji pluginów"; diff --git a/L10n/pt-BR.strings b/L10n/pt-BR.strings index f7df3554..9bc86c83 100644 --- a/L10n/pt-BR.strings +++ b/L10n/pt-BR.strings @@ -1,6 +1,3 @@ -/* Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier CAPI didn't respond" = "Erro: o CAPI da Frontier não respondeu"; - /* Language name */ "!Language" = "Português (Brasil)"; @@ -185,6 +182,9 @@ "Error: EDSM {MSG}" = "Erro: EDSM {MSG}"; +/* Raised when cannot contact the Companion API server. [companion.py] */ +"Error: Frontier CAPI didn't respond" = "Erro: o CAPI da Frontier não respondeu"; + /* OLD: Raised when cannot contact the Companion API server. [companion.py] */ "Error: Frontier server is down" = "Erro: O servidor da Frontier está offline"; @@ -230,6 +230,12 @@ /* Section heading in settings. [prefs.py] */ "File location" = "Localização do Arquivo"; +/* Failures to access Frontier CAPI endpoints [companion.py] */ +"Frontier CAPI query failure" = "Falha na requisição ao CAPI da Frontier"; + +/* Server errors from Frontier CAPI server [companion.py] */ +"Frontier CAPI server error" = "Erro de servidor do CAPI da Frontier"; + /* CQC rank. [stats.py] */ "Gladiator" = "Gladiador"; diff --git a/L10n/pt-PT.strings b/L10n/pt-PT.strings index c32eeb84..619eb3bb 100644 --- a/L10n/pt-PT.strings +++ b/L10n/pt-PT.strings @@ -1,6 +1,3 @@ -/* Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier CAPI didn't respond" = "Erro: CAPI da Frontier não respondeu."; - /* Language name */ "!Language" = "Português (Portugal)"; @@ -185,6 +182,9 @@ "Error: EDSM {MSG}" = "Erro: EDSM {MSG}"; +/* Raised when cannot contact the Companion API server. [companion.py] */ +"Error: Frontier CAPI didn't respond" = "Erro: CAPI da Frontier não respondeu."; + /* OLD: Raised when cannot contact the Companion API server. [companion.py] */ "Error: Frontier server is down" = "Erro: O servidor da Frontier está offline"; @@ -230,6 +230,12 @@ /* Section heading in settings. [prefs.py] */ "File location" = "Localização do Ficheiro"; +/* Failures to access Frontier CAPI endpoints [companion.py] */ +"Frontier CAPI query failure" = "Falha ao consultar a CAPI da Frontier."; + +/* Server errors from Frontier CAPI server [companion.py] */ +"Frontier CAPI server error" = "Erro do servidor CAPI da Frontier."; + /* CQC rank. [stats.py] */ "Gladiator" = "Gladiador"; diff --git a/L10n/ru.strings b/L10n/ru.strings index 76806d2f..677d518a 100644 --- a/L10n/ru.strings +++ b/L10n/ru.strings @@ -1,6 +1,3 @@ -/* Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier CAPI didn't respond" = "Ошибка: CAPI Frontier не отвечает"; - /* Language name */ "!Language" = "Русский"; @@ -185,6 +182,9 @@ "Error: EDSM {MSG}" = "Ошибка: EDSM {MSG}"; +/* Raised when cannot contact the Companion API server. [companion.py] */ +"Error: Frontier CAPI didn't respond" = "Ошибка: CAPI Frontier не отвечает"; + /* OLD: Raised when cannot contact the Companion API server. [companion.py] */ "Error: Frontier server is down" = "Ошибка: сервера Frontier недоступны"; diff --git a/L10n/sr-Latn-BA.strings b/L10n/sr-Latn-BA.strings index c8bb2063..27072cba 100644 --- a/L10n/sr-Latn-BA.strings +++ b/L10n/sr-Latn-BA.strings @@ -1,6 +1,3 @@ -/* Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier CAPI didn't respond" = "Greška: Frontier CAPI nije odgovorio"; - /* Language name */ "!Language" = "Srpski (Latinica, Bosna i Hercegovina)"; @@ -185,6 +182,9 @@ "Error: EDSM {MSG}" = "Greška: EDSM {MSG}"; +/* Raised when cannot contact the Companion API server. [companion.py] */ +"Error: Frontier CAPI didn't respond" = "Greška: Frontier CAPI nije odgovorio"; + /* OLD: Raised when cannot contact the Companion API server. [companion.py] */ "Error: Frontier server is down" = "Greška: Frontier server je nedostupan"; diff --git a/L10n/sr-Latn.strings b/L10n/sr-Latn.strings index 702d3dbb..0c51b415 100644 --- a/L10n/sr-Latn.strings +++ b/L10n/sr-Latn.strings @@ -1,6 +1,3 @@ -/* Raised when cannot contact the Companion API server. [companion.py] */ -"Error: Frontier CAPI didn't respond" = "Greška: Frontier CAPI nije odgovorio"; - /* Language name */ "!Language" = "Srpski (Latinica)"; @@ -185,6 +182,9 @@ "Error: EDSM {MSG}" = "Greška: EDSM {MSG}"; +/* Raised when cannot contact the Companion API server. [companion.py] */ +"Error: Frontier CAPI didn't respond" = "Greška: Frontier CAPI nije odgovorio"; + /* OLD: Raised when cannot contact the Companion API server. [companion.py] */ "Error: Frontier server is down" = "Greška: Frontier server je nedostupan";