From e3d2fa40b7ef57b7fbcd4bd66b1a2e778c1829f8 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 5 Mar 2021 16:27:23 +0000 Subject: [PATCH 01/19] Add Steam and EGS to Frontier Auth audiences. --- companion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion.py b/companion.py index 1be9d0fa..b75d39ae 100644 --- a/companion.py +++ b/companion.py @@ -277,7 +277,7 @@ class Auth(object): logger.info(f'Trying auth from scratch for Commander "{self.cmdr}"') challenge = self.base64_url_encode(hashlib.sha256(self.verifier).digest()) webbrowser.open( - f'{SERVER_AUTH}{URL_AUTH}?response_type=code&audience=frontier&scope=capi&client_id={CLIENT_ID}&code_challenge={challenge}&code_challenge_method=S256&state={self.state}&redirect_uri={protocolhandler.redirect}' # noqa: E501 # I cant make this any shorter + f'{SERVER_AUTH}{URL_AUTH}?response_type=code&audience=frontier,steam,epic&scope=capi&client_id={CLIENT_ID}&code_challenge={challenge}&code_challenge_method=S256&state={self.state}&redirect_uri={protocolhandler.redirect}' # noqa: E501 # I cant make this any shorter ) return None From b11be09b56f97da9b0b8796024307650bb19a68e Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 5 Mar 2021 16:57:02 +0000 Subject: [PATCH 02/19] CAPI: Handle when we get no 'commander' in returned data. I was testing the new Steam or Epic CAPI auth. My EGS account hasn't yet been used, so has no commander attached. EDMC thinks the auth has succeeded in this case, but hitting 'Update' causes it to error because the returned data is empty. So, add some checks for lack of 'commander' key and a specific message "CAPI: No commander data returned" for status line. Without this there's a KeyError exception thrown, causing the status line to just get 'commander' in it, which isn't helpful. --- EDMarketConnector.py | 10 +++++++++- L10n/en.template | 3 +++ companion.py | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 73f15e67..6f4b8c7c 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -662,17 +662,25 @@ class AppWindow(object): config.set('querytime', querytime) # Validation - if not data.get('commander', {}).get('name'): + if 'commander' not in data: + # This can happen with EGS Auth if no commander created yet + self.status['text'] = _('CAPI: No commander data returned') + + elif not data.get('commander', {}).get('name'): self.status['text'] = _("Who are you?!") # Shouldn't happen + elif (not data.get('lastSystem', {}).get('name') or (data['commander'].get('docked') and not data.get('lastStarport', {}).get('name'))): # Only care if docked self.status['text'] = _("Where are you?!") # Shouldn't happen + elif not data.get('ship', {}).get('name') or not data.get('ship', {}).get('modules'): self.status['text'] = _("What are you flying?!") # Shouldn't happen + elif monitor.cmdr and data['commander']['name'] != monitor.cmdr: # Companion API return doesn't match Journal raise companion.CmdrError() + elif ((auto_update and not data['commander'].get('docked')) or (data['lastSystem']['name'] != monitor.system) or ((data['commander']['docked'] diff --git a/L10n/en.template b/L10n/en.template index 23b4abcd..ef8f7399 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -46,6 +46,9 @@ /* Folder selection button on Windows. [prefs.py] */ "Browse..." = "Browse..."; +/* No 'commander' data in CAPI [EDMarketConnector.py] */ +"CAPI: No commander data returned" = "CAPI: No commander data returned"; + /* Federation rank. [stats.py] */ "Cadet" = "Cadet"; diff --git a/companion.py b/companion.py index b75d39ae..ba8faf1a 100644 --- a/companion.py +++ b/companion.py @@ -532,6 +532,10 @@ class Session(object): def station(self) -> CAPIData: """Perform CAPI /profile endpoint query for station data.""" data = self.query(URL_QUERY) + if 'commander' not in data: + logger.error('No commander in returned data') + return data + if not data['commander'].get('docked'): return data From 5d39dee900eed0d3676bd96c1cdb2e1f8908dfef Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 6 Mar 2021 10:54:15 +0000 Subject: [PATCH 03/19] CAPI: Log if no commander in profile() data --- companion.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/companion.py b/companion.py index ba8faf1a..850f3a7a 100644 --- a/companion.py +++ b/companion.py @@ -527,7 +527,11 @@ class Session(object): def profile(self) -> CAPIData: """Perform general CAPI /profile endpoint query.""" - return self.query(URL_QUERY) + data = self.query(URL_QUERY) + if 'commander' not in data: + logger.error('No commander in returned data') + + return data def station(self) -> CAPIData: """Perform CAPI /profile endpoint query for station data.""" From 4658b267fb38817ae26938f0af071bc3ed03e876 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 9 Mar 2021 12:27:00 +0000 Subject: [PATCH 04/19] PLUGINS: Document that CargoJSON isn't initially populated. Blame Frontier for deciding to put this in-Journal on the first event at login, but in the separate file after that. --- PLUGINS.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index 14c46ada..337c80ed 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -460,8 +460,13 @@ typically about once a second when in orbital flight. New in version 4.1.6: -`CargoJSON` contains the raw data from the last read of `cargo.json` passed through json.load. -It contains more information about the cargo contents, such as the mission ID for mission specific cargo +`CargoJSON` contains the raw data from the last read of `Cargo.json` passed +through json.load. It contains more information about the cargo contents, such +as the mission ID for mission specific cargo + +**NB: Because this is only the data loaded from the `Cargo.json` file, and that +is not written at Commander login (instead the in-Journal `Cargo` event +contains all the data), this will not be populated at login.** #### Getting Commander Data From e76e76bbe475d7f20a47e018384b55e8df53dc8d Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 13 Mar 2021 12:44:23 +0000 Subject: [PATCH 05/19] Add MacOS pyobjc to requirements.txt --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index 7f6952ba..a7079592 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,6 @@ watchdog==0.10.3 # argh==0.26.2 watchdog dep # pyyaml==5.3.1 watchdog dep semantic-version==2.8.5 + +# Base requirement for MacOS +pyobjc; sys_platform == 'darwin' From 60c67723cae52e3564d99dda7df7b0f0be57cffe Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 12 Mar 2021 19:30:34 +0000 Subject: [PATCH 06/19] Change JournalLock.obtain_lock() return to an Enum. This way we can tell the difference between: 1. This process obtained the lock. 2. Another process has the lock. 3. We couldn't get the lock due to not being able to open the lock file read-write. Case 3 is currently also returned if the configured journal directory doesn't exist. This will be the case on any MacOS system that never had the game running. Likely given the OS hasn't been supported for the game in years now. --- EDMarketConnector.py | 10 ++++------ journal_lock.py | 24 +++++++++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index bcc64c80..764568ec 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -33,7 +33,7 @@ if __name__ == '__main__': # After the redirect in case config does logging setup from config import appversion, appversion_nobuild, config, copyright from EDMCLogging import edmclogger, logger, logging -from journal_lock import JournalLock +from journal_lock import JournalLock, JournalLockResult if __name__ == '__main__': # noqa: C901 # Command-line arguments @@ -72,16 +72,14 @@ if __name__ == '__main__': # noqa: C901 config.set_auth_force_localserver() def handle_edmc_callback_or_foregrounding(): # noqa: CCR001 - """ - Handle any edmc:// auth callback, else foreground existing window. - """ + """Handle any edmc:// auth callback, else foreground existing window.""" logger.trace('Begin...') if platform == 'win32': # If *this* instance hasn't locked, then another already has and we # now need to do the edmc:// checks for auth callback - if not locked: + if locked != JournalLockResult.LOCKED: import ctypes from ctypes.wintypes import BOOL, HWND, INT, LPARAM, LPCWSTR, LPWSTR @@ -190,7 +188,7 @@ if __name__ == '__main__': # noqa: C901 handle_edmc_callback_or_foregrounding() - if not locked: + if locked == JournalLockResult.ALREADY_LOCKED: # There's a copy already running. logger.info("An EDMarketConnector.exe process was already running, exiting.") diff --git a/journal_lock.py b/journal_lock.py index 33279c76..8dab6ddc 100644 --- a/journal_lock.py +++ b/journal_lock.py @@ -2,6 +2,7 @@ import pathlib import tkinter as tk +from enum import Enum from os import getpid as os_getpid from sys import platform from tkinter import ttk @@ -17,6 +18,15 @@ if TYPE_CHECKING: return x +class JournalLockResult(Enum): + """Enumeration of possible outcomes of trying to lock the Journal Directory.""" + + LOCKED = 1 + JOURNALDIR_NOTEXIST = 2 + JOURNALDIR_READONLY = 3 + ALREADY_LOCKED = 4 + + class JournalLock: """Handle locking of journal directory.""" @@ -28,11 +38,11 @@ class JournalLock: # We never test truthiness of this, so let it be defined when first assigned. Avoids type hint issues. # self.journal_dir_lockfile: Optional[IO] = None - def obtain_lock(self) -> bool: + def obtain_lock(self) -> JournalLockResult: """ Attempt to obtain a lock on the journal directory. - :return: bool - True if we successfully obtained the lock + :return: LockResult - See the class Enum definition """ self.journal_dir_lockfile_name = self.journal_dir_path / 'edmc-journal-lock.txt' logger.trace(f'journal_dir_lockfile_name = {self.journal_dir_lockfile_name!r}') @@ -44,7 +54,7 @@ class JournalLock: except Exception as e: # For remote FS this could be any of a wide range of exceptions logger.warning(f"Couldn't open \"{self.journal_dir_lockfile_name}\" for \"w+\"" f" Aborting duplicate process checks: {e!r}") - return False + return JournalLockResult.JOURNALDIR_READONLY if platform == 'win32': logger.trace('win32, using msvcrt') @@ -57,7 +67,7 @@ class JournalLock: except Exception as e: logger.info(f"Exception: Couldn't lock journal directory \"{self.journal_dir}\"" f", assuming another process running: {e!r}") - return False + return JournalLockResult.ALREADY_LOCKED else: logger.trace('NOT win32, using fcntl') @@ -67,7 +77,7 @@ class JournalLock: except ImportError: logger.warning("Not on win32 and we have no fcntl, can't use a file lock!" "Allowing multiple instances!") - return True # Lie about being locked + return JournalLockResult.LOCKED try: fcntl.flock(self.journal_dir_lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) @@ -75,13 +85,13 @@ class JournalLock: except Exception as e: logger.info(f"Exception: Couldn't lock journal directory \"{self.journal_dir}\", " f"assuming another process running: {e!r}") - return False + return JournalLockResult.ALREADY_LOCKED self.journal_dir_lockfile.write(f"Path: {self.journal_dir}\nPID: {os_getpid()}\n") self.journal_dir_lockfile.flush() logger.trace('Done') - return True + return JournalLockResult.LOCKED def release_lock(self) -> bool: """ From 1cd0392527d4975171540628e98d3f4e346eba63 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 13 Mar 2021 14:49:21 +0000 Subject: [PATCH 07/19] Record if JournalLock.obtain_lock() succeeded, use in release_lock() In the scenario of the default Journals location not even existing the user needs might update it to a network mount. When they do so we attempt, and fail, to unlock the old location, despite it not being locked. So now we have this boolean so we know if we should even attempt unlocking. --- journal_lock.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/journal_lock.py b/journal_lock.py index 8dab6ddc..e947ffe1 100644 --- a/journal_lock.py +++ b/journal_lock.py @@ -37,6 +37,7 @@ class JournalLock: self.journal_dir_lockfile_name: Optional[pathlib.Path] = None # We never test truthiness of this, so let it be defined when first assigned. Avoids type hint issues. # self.journal_dir_lockfile: Optional[IO] = None + self.locked = False def obtain_lock(self) -> JournalLockResult: """ @@ -91,6 +92,8 @@ class JournalLock: self.journal_dir_lockfile.flush() logger.trace('Done') + self.locked = True + return JournalLockResult.LOCKED def release_lock(self) -> bool: @@ -99,6 +102,9 @@ class JournalLock: :return: bool - Success of unlocking operation. """ + if not self.locked: + return True # We weren't locked, and still aren't + unlocked = False if platform == 'win32': logger.trace('win32, using msvcrt') From fa49159de52928944749cb8bafb50010e6654e79 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 15 Mar 2021 11:20:46 +0000 Subject: [PATCH 08/19] JournalLock: Catch when journal_dir is None * If it's None then set journal_dir_path to None as well. Setting '' or nothing results in '.' (CWD), which could cause other issues. * As we do this in three places, it's in a helper function. * New JournalLockResult.JOURNALDIR_IS_NONE to signal this. * Fix checking of return from obtain_lock() to specifically reference JournalLockResult.ALREADY_LOCKED. --- journal_lock.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/journal_lock.py b/journal_lock.py index e947ffe1..068626a4 100644 --- a/journal_lock.py +++ b/journal_lock.py @@ -25,6 +25,7 @@ class JournalLockResult(Enum): JOURNALDIR_NOTEXIST = 2 JOURNALDIR_READONLY = 3 ALREADY_LOCKED = 4 + JOURNALDIR_IS_NONE = 5 class JournalLock: @@ -33,18 +34,33 @@ class JournalLock: def __init__(self) -> None: """Initialise where the journal directory and lock file are.""" self.journal_dir: str = config.get('journaldir') or config.default_journal_dir - self.journal_dir_path = pathlib.Path(self.journal_dir) + self.journal_dir_path: Optional[pathlib.Path] = None + self.set_path_from_journaldir() self.journal_dir_lockfile_name: Optional[pathlib.Path] = None # We never test truthiness of this, so let it be defined when first assigned. Avoids type hint issues. # self.journal_dir_lockfile: Optional[IO] = None self.locked = False + def set_path_from_journaldir(self): + if self.journal_dir is None: + self.journal_dir_path = None + + else: + try: + self.journal_dir_path = pathlib.Path(self.journal_dir) + + except Exception: + logger.exception("Couldn't make pathlib.Path from journal_dir", exc_info=True) + def obtain_lock(self) -> JournalLockResult: """ Attempt to obtain a lock on the journal directory. :return: LockResult - See the class Enum definition """ + if self.journal_dir_path is None: + return JournalLockResult.JOURNALDIR_IS_NONE + self.journal_dir_lockfile_name = self.journal_dir_path / 'edmc-journal-lock.txt' logger.trace(f'journal_dir_lockfile_name = {self.journal_dir_lockfile_name!r}') try: @@ -100,7 +116,7 @@ class JournalLock: """ Release lock on journal directory. - :return: bool - Success of unlocking operation. + :return: bool - Whether we're now unlocked. """ if not self.locked: return True # We weren't locked, and still aren't @@ -222,8 +238,9 @@ class JournalLock: self.release_lock() self.journal_dir = current_journaldir - self.journal_dir_path = pathlib.Path(self.journal_dir) - if not self.obtain_lock(): + self.set_path_from_journaldir() + + if self.obtain_lock() == JournalLockResult.ALREADY_LOCKED: # Pop-up message asking for Retry or Ignore self.retry_popup = self.JournalAlreadyLocked(parent, self.retry_lock) @@ -241,7 +258,7 @@ class JournalLock: current_journaldir = config.get('journaldir') or config.default_journal_dir self.journal_dir = current_journaldir - self.journal_dir_path = pathlib.Path(self.journal_dir) - if not self.obtain_lock(): + self.set_path_from_journaldir() + if self.obtain_lock() == JournalLockResult.ALREADY_LOCKED: # Pop-up message asking for Retry or Ignore self.retry_popup = self.JournalAlreadyLocked(parent, self.retry_lock) From 4f524a0a35072a1aeff47426448eb13f9838e657 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 15 Mar 2021 12:07:39 +0000 Subject: [PATCH 09/19] Add extra logging for Frontier Auth, especially via edmc:// --- EDMarketConnector.py | 5 +++++ companion.py | 7 ++++++- protocol.py | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 764568ec..b33d9498 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -918,6 +918,7 @@ class AppWindow(object): # cAPI auth def auth(self, event=None): + logger.debug('Received "<>') try: companion.session.auth_callback() # Successfully authenticated with the Frontier website @@ -925,14 +926,18 @@ class AppWindow(object): if platform == 'darwin': self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data + else: self.file_menu.entryconfigure(0, state=tk.NORMAL) # Status self.file_menu.entryconfigure(1, state=tk.NORMAL) # Save Raw Data + except companion.ServerError as e: self.status['text'] = str(e) + except Exception as e: logger.debug('Frontier CAPI Auth:', exc_info=e) self.status['text'] = str(e) + self.cooldown() # Handle Status event diff --git a/companion.py b/companion.py index 850f3a7a..43f21721 100644 --- a/companion.py +++ b/companion.py @@ -263,6 +263,7 @@ class Auth(object): except (ValueError, requests.RequestException, ): logger.exception(f"Frontier CAPI Auth: Can't refresh token for \"{self.cmdr}\"") + self.dump(r) else: logger.error(f"Frontier CAPI Auth: No token for \"{self.cmdr}\"") @@ -360,7 +361,11 @@ class Auth(object): # noinspection PyMethodMayBeStatic def dump(self, r: requests.Response) -> None: """Dump details of HTTP failure from oAuth attempt.""" - logger.debug(f'Frontier CAPI Auth: {r.url} {r.status_code} {r.reason if r.reason else "None"} {r.text}') + if r: + logger.debug(f'Frontier CAPI Auth: {r.url} {r.status_code} {r.reason if r.reason else "None"} {r.text}') + + else: + logger.debug(f'Frontier CAPI Auth: failed with `r` False: {r!r}') # noinspection PyMethodMayBeStatic def base64_url_encode(self, text: bytes) -> str: diff --git a/protocol.py b/protocol.py index 9dd9aba5..04e233ef 100644 --- a/protocol.py +++ b/protocol.py @@ -37,6 +37,7 @@ class GenericProtocolHandler(object): self.lastpayload = url if not config.shutting_down: + logger.debug('event_generate("<>"') self.master.event_generate('<>', when="tail") @@ -190,12 +191,16 @@ elif sys.platform == 'win32' and getattr(sys, 'frozen', False) and not is_wine a None) msg = MSG() while GetMessage(byref(msg), None, 0, 0) != 0: + logger.trace(f'DDE message of type: {msg.message}') if msg.message == WM_DDE_EXECUTE: args = wstring_at(GlobalLock(msg.lParam)).strip() GlobalUnlock(msg.lParam) if args.lower().startswith('open("') and args.endswith('")'): + logger.trace(f'args are: {args}') url = urllib.parse.unquote(args[6:-2]).strip() + logger.trace(f'Parsed url: {url}') if url.startswith(self.redirect): + logger.debug(f'Message starts with {self.redirect}') self.event(url) SetForegroundWindow(GetParent(self.master.winfo_id())) # raise app window PostMessage(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0x80, msg.lParam)) From cc2395ca04477e79ca078c896fd714b756a4bd33 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 15 Mar 2021 12:13:28 +0000 Subject: [PATCH 10/19] Add similar extra logging for localhost handler * This also adjusts the logging in Session.auth_callback() because it can be called from either protocol. Saying 'edmc://' there is misleading. --- companion.py | 4 ++-- protocol.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/companion.py b/companion.py index 43f21721..36fb62cb 100644 --- a/companion.py +++ b/companion.py @@ -437,8 +437,8 @@ class Session(object): # Callback from protocol handler def auth_callback(self) -> None: - """Handle callback from edmc:// handler.""" - logger.debug('Handling callback from edmc:// handler') + """Handle callback from edmc:// or localhost:/auth handler.""" + logger.debug('Handling auth callback') if self.state != Session.STATE_AUTH: # Shouldn't be getting a callback logger.debug('Got an auth callback while not doing auth') diff --git a/protocol.py b/protocol.py index 04e233ef..dcaa8b99 100644 --- a/protocol.py +++ b/protocol.py @@ -36,6 +36,7 @@ class GenericProtocolHandler(object): def event(self, url): self.lastpayload = url + logger.trace(f'Payload: {self.lastpayload}') if not config.shutting_down: logger.debug('event_generate("<>"') self.master.event_generate('<>', when="tail") @@ -247,8 +248,10 @@ else: # Linux / Run from source class HTTPRequestHandler(BaseHTTPRequestHandler): def parse(self): + logger.trace(f'Got message on path: {self.path}') url = urllib.parse.unquote(self.path) if url.startswith('/auth'): + logger.debug('Request starts with /auth, sending to protocolhandler.event()') protocolhandler.event(url) self.send_response(200) return True From f1c50faafa1f503bc4882f205368ba41aef7d346 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 15 Mar 2021 15:58:18 +0000 Subject: [PATCH 11/19] logger.exception() doesn't need exc_info --- journal_lock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/journal_lock.py b/journal_lock.py index 068626a4..51f94f89 100644 --- a/journal_lock.py +++ b/journal_lock.py @@ -50,7 +50,7 @@ class JournalLock: self.journal_dir_path = pathlib.Path(self.journal_dir) except Exception: - logger.exception("Couldn't make pathlib.Path from journal_dir", exc_info=True) + logger.exception("Couldn't make pathlib.Path from journal_dir") def obtain_lock(self) -> JournalLockResult: """ From fbe3e7a6e5c2aa9604005734b4fda99dedcd641a Mon Sep 17 00:00:00 2001 From: A_D Date: Mon, 15 Mar 2021 18:53:20 +0200 Subject: [PATCH 12/19] Ensured that CAPIData objects are JSON Serialisable While it isnt directly an issue in main (right now), the fix applies here forward, so we may as well do it here and not deal with a headache later. --- EDMarketConnector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index bcc64c80..a991d2de 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -697,7 +697,7 @@ class AppWindow(object): system=data['lastSystem']['name'], station=data['commander'].get('docked') and '.' + data['lastStarport']['name'] or '', timestamp=strftime('%Y-%m-%dT%H.%M.%S', localtime())), 'wb') as h: - h.write(json.dumps(data, + h.write(json.dumps(dict(data), ensure_ascii=False, indent=2, sort_keys=True, @@ -1119,7 +1119,7 @@ class AppWindow(object): initialfile=f'{last_system}{last_starport}.{timestamp}') if f: with open(f, 'wb') as h: - h.write(json.dumps(data, + h.write(json.dumps(dict(data), ensure_ascii=False, indent=2, sort_keys=True, From 98be18292295b181bff157b2eb607102d151bb47 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 15 Mar 2021 17:03:39 +0000 Subject: [PATCH 13/19] plugins/eddn: Add paranoia about data in is_horizons() A damaged station has `"modules": []`, so trips over modules.values(). --- plugins/eddn.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 29de4910..6533f638 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -789,8 +789,18 @@ MAP_STR_ANY = Mapping[str, Any] def is_horizons(economies: MAP_STR_ANY, modules: MAP_STR_ANY, ships: MAP_STR_ANY) -> bool: - return ( - any(economy['name'] == 'Colony' for economy in economies.values()) or - any(module.get('sku') == HORIZ_SKU for module in modules.values()) or - any(ship.get('sku') == HORIZ_SKU for ship in (ships['shipyard_list'] or {}).values()) - ) + economies_colony = False + modules_horizons = False + ship_horizons = False + + if isinstance(dict, economies): + economies_colony = any(economy['name'] == 'Colony' for economy in economies.values()) + + if isinstance(dict, modules): + modules_horizons = any(module.get('sku') == HORIZ_SKU for module in modules.values()) + + if isinstance(dict, ships): + if ships.get('shipyard_list') is not None: + ship_horizons = any(ship.get('sku') == HORIZ_SKU for ship in ships['shipyard_list'].values()) + + return economies_colony or modules_horizons or ship_horizons From 173cffdcfc8f225c7172b9ba01a2851896e5c81d Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 15 Mar 2021 17:06:26 +0000 Subject: [PATCH 14/19] Add extra logging to is_horizons() if things not as expected. * Log the type(s) if not dict. * Log if ships['shipyard_list'] isn't present, despite ships being a dict. --- plugins/eddn.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index 6533f638..12f2b207 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -796,11 +796,23 @@ def is_horizons(economies: MAP_STR_ANY, modules: MAP_STR_ANY, ships: MAP_STR_ANY if isinstance(dict, economies): economies_colony = any(economy['name'] == 'Colony' for economy in economies.values()) + else: + logger.error(f'economies type is {type(economies)}') + if isinstance(dict, modules): modules_horizons = any(module.get('sku') == HORIZ_SKU for module in modules.values()) + else: + logger.error(f'modules type is {type(modules)}') + if isinstance(dict, ships): if ships.get('shipyard_list') is not None: ship_horizons = any(ship.get('sku') == HORIZ_SKU for ship in ships['shipyard_list'].values()) + else: + logger.debug('No ships["shipyard_list"] - Damaged station or FC ?') + + else: + logger.error(f'ships type is {type(ships)}') + return economies_colony or modules_horizons or ship_horizons From 977fec25b0998117c18615ea3a124f1ac2982977 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 15 Mar 2021 19:11:27 +0000 Subject: [PATCH 15/19] Release 4.2.1: appversion and changelog --- ChangeLog.md | 29 +++++++++++++++++++++++++++++ config.py | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index f23e7806..ac6daec3 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,35 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in reverse chronological order (latest first). --- +Release 4.2.1 +=== + + This is a bug-fix release. + +* PLUGINS.md: Clarify when `CargoJSON` is populated. + +* macOS: `pip install -r requirements.txt` will now include `pyobjc` so that + running this application works at all. + +* JournalLock: Handle when the Journal directory isn't set at all, rather than + erroring. Fixes [#910 - Not launching (Linux)](https://github.com/EDCD/EDMarketConnector/issues/910). + +* Extra logging added to track down cause of [#909 - Authentication not possible (PC)](https://github.com/EDCD/EDMarketConnector/issues/909) + . The debug log file might now indicate what's wrong, or we might need + you to run + + ``` + "c:\Program Files (x86)\EDMarketConnector/EDMarketConnector.exe" --trace + ``` + in order to increase the log level and gather some extra information. + Caution is advised if sharing a `--trace` log file as it will now contain + some of the actual auth data returned from Frontier. + +* Ensure that 'Save Raw Data' will work. Fixes [#908 - Raw export of CAPI data broken](https://github.com/EDCD/EDMarketConnector/issues/908). + +* Prevent EDDN plugin from erroring when we determine if the commander has + Horizons. Fixes [#907 - Modules is a list not a dict on damaged stations](https://github.com/EDCD/EDMarketConnector/issues/907) + Release 4.2.0 === diff --git a/config.py b/config.py index 0f2144bb..f611043f 100644 --- a/config.py +++ b/config.py @@ -13,7 +13,7 @@ appcmdname = 'EDMC' # appversion **MUST** follow Semantic Versioning rules: # # Major.Minor.Patch(-prerelease)(+buildmetadata) -appversion = '4.2.0' #-rc1+a872b5f' +appversion = '4.2.1' #-rc1+a872b5f' # For some things we want appversion without (possible) +build metadata appversion_nobuild = str(semantic_version.Version(appversion).truncate('prerelease')) copyright = u'© 2015-2019 Jonathan Harris, 2020 EDCD' From d13ebafcba9de8f3fbd640f61fe58f87c2a80fc4 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 15 Mar 2021 19:15:46 +0000 Subject: [PATCH 16/19] ChangeLog: Point macOS people to the updated docs. --- ChangeLog.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index ac6daec3..d5ee275b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -9,7 +9,9 @@ Release 4.2.1 * PLUGINS.md: Clarify when `CargoJSON` is populated. * macOS: `pip install -r requirements.txt` will now include `pyobjc` so that - running this application works at all. + running this application works at all. Check the updated [Running from + source](https://github.com/EDCD/EDMarketConnector/wiki/Running-from-source) + for some advice if attempting to run on macOS. * JournalLock: Handle when the Journal directory isn't set at all, rather than erroring. Fixes [#910 - Not launching (Linux)](https://github.com/EDCD/EDMarketConnector/issues/910). From 740d1bd38a73fe9dda86258a616b04ce3f142bbb Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 15 Mar 2021 19:21:17 +0000 Subject: [PATCH 17/19] Correct order of arguments to isinstance() --- plugins/eddn.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 12f2b207..a8a34b48 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -793,19 +793,19 @@ def is_horizons(economies: MAP_STR_ANY, modules: MAP_STR_ANY, ships: MAP_STR_ANY modules_horizons = False ship_horizons = False - if isinstance(dict, economies): + if isinstance(economies, dict): economies_colony = any(economy['name'] == 'Colony' for economy in economies.values()) else: logger.error(f'economies type is {type(economies)}') - if isinstance(dict, modules): + if isinstance(modules, dict): modules_horizons = any(module.get('sku') == HORIZ_SKU for module in modules.values()) else: logger.error(f'modules type is {type(modules)}') - if isinstance(dict, ships): + if isinstance(ships, dict): if ships.get('shipyard_list') is not None: ship_horizons = any(ship.get('sku') == HORIZ_SKU for ship in ships['shipyard_list'].values()) From 1eef5b86f32cca55fdae6f6b77a38264473982a3 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 15 Mar 2021 19:25:40 +0000 Subject: [PATCH 18/19] Update translations --- L10n/cs.strings | 18 ++++++++++++++++ L10n/de.strings | 18 ++++++++++++++++ L10n/fi.strings | 3 +++ L10n/it.strings | 24 +++++++++++++++++++++ L10n/ja.strings | 18 ++++++++++++++++ L10n/pl.strings | 29 +++++++++++++++++++++++++- L10n/pt-PT.strings | 18 ++++++++++++++++ L10n/ru.strings | 18 ++++++++++++++++ L10n/sr-Latn-BA.strings | 18 ++++++++++++++++ L10n/sr-Latn.strings | 18 ++++++++++++++++ L10n/zh-Hans.strings | 46 ++++++++++++++++++++++++++++++++++++++++- 11 files changed, 226 insertions(+), 2 deletions(-) diff --git a/L10n/cs.strings b/L10n/cs.strings index fad071e0..253b8d23 100644 --- a/L10n/cs.strings +++ b/L10n/cs.strings @@ -46,6 +46,9 @@ /* Folder selection button on Windows. [prefs.py] */ "Browse..." = "Procházet..."; +/* No 'commander' data in CAPI [EDMarketConnector.py] */ +"CAPI: No commander data returned" = "CAPI: Nebyla vrácena žádná data komandéra"; + /* Federation rank. [stats.py] */ "Cadet" = "Cadet"; @@ -226,9 +229,15 @@ /* Hotkey/Shortcut settings prompt on Windows. [prefs.py] */ "Hotkey" = "Klávesová zkratka"; +/* Changed journal update_lock failed [monitor.py] */ +"Ignore" = "Ignorovat"; + /* Section heading in settings. [inara.py] */ "Inara credentials" = "Přihlašovací údaje k Inaře"; +/* Changed journal update_lock failed [monitor.py] */ +"Journal directory already locked" = "Adresář deníku je již uzamčen"; + /* Hotkey/Shortcut settings prompt on OSX. [prefs.py] */ "Keyboard shortcut" = "Klávesová zkratka"; @@ -409,6 +418,9 @@ /* Help menu item. [EDMarketConnector.py] */ "Release Notes" = "Poznámky k verzi"; +/* Changed journal update_lock failed [monitor.py] */ +"Retry" = "Opakovat"; + /* Multicrew role label in main window. [EDMarketConnector.py] */ "Role" = "Role"; @@ -454,6 +466,9 @@ /* Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis. [prefs.py] */ "Shipyard" = "Loděnice"; +/* Status line text that appears when process exit sequence starts [EDMarketConnector.py] */ +"Shutting down..." = "Vypínání..."; + /* Empire rank. [stats.py] */ "Squire" = "Squire"; @@ -475,6 +490,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Systém"; +/* Changed journal update_lock failed [monitor.py] */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "Nové umístění adresáře deníku je již uzamčeno. {CR} Můžete zkusit tento problém vyřešit a Opakovat, nebo Ignorovat."; + /* Appearance setting. [prefs.py] */ "Theme" = "Schéma"; diff --git a/L10n/de.strings b/L10n/de.strings index 73239607..492de5de 100644 --- a/L10n/de.strings +++ b/L10n/de.strings @@ -46,6 +46,9 @@ /* Folder selection button on Windows. [prefs.py] */ "Browse..." = "Durchsuchen..."; +/* No 'commander' data in CAPI [EDMarketConnector.py] */ +"CAPI: No commander data returned" = "CAPI: Keine Kommandanten-Daten erhalten"; + /* Federation rank. [stats.py] */ "Cadet" = "Kadett"; @@ -226,9 +229,15 @@ /* Hotkey/Shortcut settings prompt on Windows. [prefs.py] */ "Hotkey" = "Hotkey"; +/* Changed journal update_lock failed [monitor.py] */ +"Ignore" = "Ignorieren"; + /* Section heading in settings. [inara.py] */ "Inara credentials" = "Inara-Anmeldedaten"; +/* Changed journal update_lock failed [monitor.py] */ +"Journal directory already locked" = "Journal-Ordner bereits gesperrt"; + /* Hotkey/Shortcut settings prompt on OSX. [prefs.py] */ "Keyboard shortcut" = "Makro"; @@ -409,6 +418,9 @@ /* Help menu item. [EDMarketConnector.py] */ "Release Notes" = "Anmerkungen"; +/* Changed journal update_lock failed [monitor.py] */ +"Retry" = "Wiederholen"; + /* Multicrew role label in main window. [EDMarketConnector.py] */ "Role" = "Rolle"; @@ -454,6 +466,9 @@ /* Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis. [prefs.py] */ "Shipyard" = "Werft"; +/* Status line text that appears when process exit sequence starts [EDMarketConnector.py] */ +"Shutting down..." = "Programm schließt..."; + /* Empire rank. [stats.py] */ "Squire" = "Knappe"; @@ -475,6 +490,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "System"; +/* Changed journal update_lock failed [monitor.py] */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "Der neue Journal-Ordnerpfad ist bereits gesperrt.{CR}Du kannst entweder versuchen, das zu beheben und es nochmal zu probieren, oder diese Meldung ignorieren."; + /* Appearance setting. [prefs.py] */ "Theme" = "Theme"; diff --git a/L10n/fi.strings b/L10n/fi.strings index e3e5eacb..98782961 100644 --- a/L10n/fi.strings +++ b/L10n/fi.strings @@ -454,6 +454,9 @@ /* Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis. [prefs.py] */ "Shipyard" = "Telakka"; +/* Status line text that appears when process exit sequence starts [EDMarketConnector.py] */ +"Shutting down..." = "Sulkeutuu..."; + /* Empire rank. [stats.py] */ "Squire" = "Squire"; diff --git a/L10n/it.strings b/L10n/it.strings index 0706161f..c6f9258c 100644 --- a/L10n/it.strings +++ b/L10n/it.strings @@ -1,3 +1,9 @@ +/* Label for 'UI Scaling' option [prefs.py] */ +"UI Scale Percentage" = "Percentuale zoom della UI"; + +/* Text describing that value '100' means 'default', and changes require a restart [prefs.py] */ +"100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 significa Default{CR}Riavvio Richiesto per{CR}applicare i cambiamenti!"; + /* Language name */ "!Language" = "Italiano"; @@ -40,6 +46,9 @@ /* Folder selection button on Windows. [prefs.py] */ "Browse..." = "Sfoglia..."; +/* No 'commander' data in CAPI [EDMarketConnector.py] */ +"CAPI: No commander data returned" = "CAPI: Nessun dato sul commandante"; + /* Federation rank. [stats.py] */ "Cadet" = "Cadet"; @@ -220,9 +229,15 @@ /* Hotkey/Shortcut settings prompt on Windows. [prefs.py] */ "Hotkey" = "Hotkey"; +/* Changed journal update_lock failed [monitor.py] */ +"Ignore" = "Ignora"; + /* Section heading in settings. [inara.py] */ "Inara credentials" = "Credenziali per Inara"; +/* Changed journal update_lock failed [monitor.py] */ +"Journal directory already locked" = "cartella Journal già bloccata"; + /* Hotkey/Shortcut settings prompt on OSX. [prefs.py] */ "Keyboard shortcut" = "Scorciatoia di tastiera"; @@ -403,6 +418,9 @@ /* Help menu item. [EDMarketConnector.py] */ "Release Notes" = "Note di rilascio"; +/* Changed journal update_lock failed [monitor.py] */ +"Retry" = "Riprova"; + /* Multicrew role label in main window. [EDMarketConnector.py] */ "Role" = "Ruolo"; @@ -448,6 +466,9 @@ /* Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis. [prefs.py] */ "Shipyard" = "Spazioporto"; +/* Status line text that appears when process exit sequence starts [EDMarketConnector.py] */ +"Shutting down..." = "Spegnimento..."; + /* Empire rank. [stats.py] */ "Squire" = "Squire"; @@ -469,6 +490,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Sistema"; +/* Changed journal update_lock failed [monitor.py] */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "La nuova cartella dove risiede il Journal è già bloccata.{CR}Puoi provare a risolvere Riprovando, oppure scegliere di ignorare la cosa."; + /* Appearance setting. [prefs.py] */ "Theme" = "Tema"; diff --git a/L10n/ja.strings b/L10n/ja.strings index 64bca474..3e933735 100644 --- a/L10n/ja.strings +++ b/L10n/ja.strings @@ -46,6 +46,9 @@ /* Folder selection button on Windows. [prefs.py] */ "Browse..." = "参照..."; +/* No 'commander' data in CAPI [EDMarketConnector.py] */ +"CAPI: No commander data returned" = "CAPI: コマンダーのデータが返ってきませんでした"; + /* Federation rank. [stats.py] */ "Cadet" = "Cadet"; @@ -226,9 +229,15 @@ /* Hotkey/Shortcut settings prompt on Windows. [prefs.py] */ "Hotkey" = "ホットキー"; +/* Changed journal update_lock failed [monitor.py] */ +"Ignore" = "無視"; + /* Section heading in settings. [inara.py] */ "Inara credentials" = "Inara認証情報"; +/* Changed journal update_lock failed [monitor.py] */ +"Journal directory already locked" = "ジャーナルディレクトリは既にロックされています"; + /* Hotkey/Shortcut settings prompt on OSX. [prefs.py] */ "Keyboard shortcut" = "ショートカット"; @@ -409,6 +418,9 @@ /* Help menu item. [EDMarketConnector.py] */ "Release Notes" = "リリースノート"; +/* Changed journal update_lock failed [monitor.py] */ +"Retry" = "再試行"; + /* Multicrew role label in main window. [EDMarketConnector.py] */ "Role" = "役割"; @@ -454,6 +466,9 @@ /* Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis. [prefs.py] */ "Shipyard" = "造船所"; +/* Status line text that appears when process exit sequence starts [EDMarketConnector.py] */ +"Shutting down..." = "終了します..."; + /* Empire rank. [stats.py] */ "Squire" = "Squire"; @@ -475,6 +490,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "星系"; +/* Changed journal update_lock failed [monitor.py] */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "新しいジャーナルディレクトリは既にロックされています。{CR}この問題を解決して再試行するか、無視するかを選択してください。"; + /* Appearance setting. [prefs.py] */ "Theme" = "テーマ"; diff --git a/L10n/pl.strings b/L10n/pl.strings index ee8d58a9..7f41bbdd 100644 --- a/L10n/pl.strings +++ b/L10n/pl.strings @@ -1,3 +1,9 @@ +/* Label for 'UI Scaling' option [prefs.py] */ +"UI Scale Percentage" = "Skalowanie UI"; + +/* Text describing that value '100' means 'default', and changes require a restart [prefs.py] */ +"100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 oznacza wartość domyślną{CR}Zmiany zostaną wprowadzone po{CR}ponownym uruchomieniu programu!"; + /* Language name */ "!Language" = "Polski"; @@ -40,6 +46,9 @@ /* Folder selection button on Windows. [prefs.py] */ "Browse..." = "Przeglądaj..."; +/* No 'commander' data in CAPI [EDMarketConnector.py] */ +"CAPI: No commander data returned" = "CAPI: Nie zwrócono danych dowódcy"; + /* Federation rank. [stats.py] */ "Cadet" = "Cadet"; @@ -101,7 +110,7 @@ "Delay sending until docked" = "Czekaj z wysłaniem na zadokowanie"; /* Option to disabled Automatic Check For Updates whilst in-game [prefs.py] */ -"Disable Automatic Application Updates Check when in-game" = "Wyłącz Sprawdzanie Aktualnej Wersji Aplikacji, gdy jesteś w grze"; +"Disable Automatic Application Updates Check when in-game" = "Wyłącz automatyczne wyszukiwanie aktualizacji, gdy jesteś w grze"; /* List of plugins in settings. [prefs.py] */ "Disabled Plugins" = "Pluginy wyłączone"; @@ -220,9 +229,15 @@ /* Hotkey/Shortcut settings prompt on Windows. [prefs.py] */ "Hotkey" = "Skr. Klaw."; +/* Changed journal update_lock failed [monitor.py] */ +"Ignore" = "Zignoruj"; + /* Section heading in settings. [inara.py] */ "Inara credentials" = "Inara - uprawnienia"; +/* Changed journal update_lock failed [monitor.py] */ +"Journal directory already locked" = "Katalog dziennika zablokowany"; + /* Hotkey/Shortcut settings prompt on OSX. [prefs.py] */ "Keyboard shortcut" = "Skrót klawiaturowy"; @@ -403,6 +418,9 @@ /* Help menu item. [EDMarketConnector.py] */ "Release Notes" = "Informacje o wersji"; +/* Changed journal update_lock failed [monitor.py] */ +"Retry" = "Ponów"; + /* Multicrew role label in main window. [EDMarketConnector.py] */ "Role" = "Rola"; @@ -448,6 +466,9 @@ /* Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis. [prefs.py] */ "Shipyard" = "Stocznia"; +/* Status line text that appears when process exit sequence starts [EDMarketConnector.py] */ +"Shutting down..." = "Wyłączanie..."; + /* Empire rank. [stats.py] */ "Squire" = "Squire"; @@ -469,6 +490,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "System"; +/* Changed journal update_lock failed [monitor.py] */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "Nowa pozycja katalogu dziennika jest zablokowana. {CR}Możesz spróbować to rozwiązać na własną rękę lub zignorować."; + /* Appearance setting. [prefs.py] */ "Theme" = "Schemat kolorystyczny"; @@ -525,3 +549,6 @@ /* Shortcut settings prompt on OSX. [prefs.py] */ "{APP} needs permission to use shortcuts" = "{APP} wymaga uprawnień by{CR}móc korzystać ze skrótów klawiszowych."; + +/* Label for user configured level of logging [prefs.py] */ +"Log Level" = "Poziom logowania"; diff --git a/L10n/pt-PT.strings b/L10n/pt-PT.strings index 79542cf1..9b5ec61e 100644 --- a/L10n/pt-PT.strings +++ b/L10n/pt-PT.strings @@ -46,6 +46,9 @@ /* Folder selection button on Windows. [prefs.py] */ "Browse..." = "Procurar..."; +/* No 'commander' data in CAPI [EDMarketConnector.py] */ +"CAPI: No commander data returned" = "CAPI: Dados do Comandante não recebidos"; + /* Federation rank. [stats.py] */ "Cadet" = "Cadete"; @@ -226,9 +229,15 @@ /* Hotkey/Shortcut settings prompt on Windows. [prefs.py] */ "Hotkey" = "Tecla Rápida"; +/* Changed journal update_lock failed [monitor.py] */ +"Ignore" = "Ignorar"; + /* Section heading in settings. [inara.py] */ "Inara credentials" = "Credenciais Inara"; +/* Changed journal update_lock failed [monitor.py] */ +"Journal directory already locked" = "Directório do Diário já se encontra bloqueado"; + /* Hotkey/Shortcut settings prompt on OSX. [prefs.py] */ "Keyboard shortcut" = "Atalho de Teclado"; @@ -409,6 +418,9 @@ /* Help menu item. [EDMarketConnector.py] */ "Release Notes" = "Notas da Versão"; +/* Changed journal update_lock failed [monitor.py] */ +"Retry" = "Tentar Novamente"; + /* Multicrew role label in main window. [EDMarketConnector.py] */ "Role" = "Função"; @@ -454,6 +466,9 @@ /* Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis. [prefs.py] */ "Shipyard" = "Estaleiro"; +/* Status line text that appears when process exit sequence starts [EDMarketConnector.py] */ +"Shutting down..." = "A Desligar..."; + /* Empire rank. [stats.py] */ "Squire" = "Escudeiro"; @@ -475,6 +490,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Sistema"; +/* Changed journal update_lock failed [monitor.py] */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "O novo directório do Diário já se encontra bloqueado.{CR}Pode tentar resolver este problema e depois Tentar Novamente, ou Ignorar isto."; + /* Appearance setting. [prefs.py] */ "Theme" = "Tema"; diff --git a/L10n/ru.strings b/L10n/ru.strings index 64a8c6dc..934ab0b8 100644 --- a/L10n/ru.strings +++ b/L10n/ru.strings @@ -46,6 +46,9 @@ /* Folder selection button on Windows. [prefs.py] */ "Browse..." = "Просмотреть..."; +/* No 'commander' data in CAPI [EDMarketConnector.py] */ +"CAPI: No commander data returned" = "CAPI: Нет данных пилота"; + /* Federation rank. [stats.py] */ "Cadet" = "Кадет"; @@ -226,9 +229,15 @@ /* Hotkey/Shortcut settings prompt on Windows. [prefs.py] */ "Hotkey" = "Горячая клавиша"; +/* Changed journal update_lock failed [monitor.py] */ +"Ignore" = "Игнорировать"; + /* Section heading in settings. [inara.py] */ "Inara credentials" = "Учётные данные Inara"; +/* Changed journal update_lock failed [monitor.py] */ +"Journal directory already locked" = "Каталог журнала уже заблокирован"; + /* Hotkey/Shortcut settings prompt on OSX. [prefs.py] */ "Keyboard shortcut" = "Сочетание клавиш"; @@ -409,6 +418,9 @@ /* Help menu item. [EDMarketConnector.py] */ "Release Notes" = "Заметки о текущем релизе"; +/* Changed journal update_lock failed [monitor.py] */ +"Retry" = "Повтор"; + /* Multicrew role label in main window. [EDMarketConnector.py] */ "Role" = "Роль"; @@ -454,6 +466,9 @@ /* Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis. [prefs.py] */ "Shipyard" = "Верфи"; +/* Status line text that appears when process exit sequence starts [EDMarketConnector.py] */ +"Shutting down..." = "Выключение..."; + /* Empire rank. [stats.py] */ "Squire" = "Оруженосец"; @@ -475,6 +490,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Система"; +/* Changed journal update_lock failed [monitor.py] */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "Каталог журнала заблокирован..{CR}Вы можете устранить ошибку и нажать \"Повтор\", или игнорировать эту ошибку."; + /* Appearance setting. [prefs.py] */ "Theme" = "Тема оформления"; diff --git a/L10n/sr-Latn-BA.strings b/L10n/sr-Latn-BA.strings index 70c6e66b..d9dba531 100644 --- a/L10n/sr-Latn-BA.strings +++ b/L10n/sr-Latn-BA.strings @@ -46,6 +46,9 @@ /* Folder selection button on Windows. [prefs.py] */ "Browse..." = "Potraži..."; +/* No 'commander' data in CAPI [EDMarketConnector.py] */ +"CAPI: No commander data returned" = "CAPI: Nema podataka o komandantu"; + /* Federation rank. [stats.py] */ "Cadet" = "Cadet"; @@ -226,9 +229,15 @@ /* Hotkey/Shortcut settings prompt on Windows. [prefs.py] */ "Hotkey" = "Prečica"; +/* Changed journal update_lock failed [monitor.py] */ +"Ignore" = "Ignoriši"; + /* Section heading in settings. [inara.py] */ "Inara credentials" = "Kredencijali za Inara"; +/* Changed journal update_lock failed [monitor.py] */ +"Journal directory already locked" = "Journal direktorijum je već zaključan"; + /* Hotkey/Shortcut settings prompt on OSX. [prefs.py] */ "Keyboard shortcut" = "Prečica"; @@ -409,6 +418,9 @@ /* Help menu item. [EDMarketConnector.py] */ "Release Notes" = "Informacije o verziji"; +/* Changed journal update_lock failed [monitor.py] */ +"Retry" = "Probaj ponovo"; + /* Multicrew role label in main window. [EDMarketConnector.py] */ "Role" = "Uloga"; @@ -454,6 +466,9 @@ /* Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis. [prefs.py] */ "Shipyard" = "Brodogradilište"; +/* Status line text that appears when process exit sequence starts [EDMarketConnector.py] */ +"Shutting down..." = "Zatvaranje programa..."; + /* Empire rank. [stats.py] */ "Squire" = "Squire"; @@ -475,6 +490,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Sistem"; +/* Changed journal update_lock failed [monitor.py] */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "Novi journal direktorijum je već zaključan.{CR}Možete pokušati da riješite problem i Probate ponovo ili da ga Ignorišete."; + /* Appearance setting. [prefs.py] */ "Theme" = "Tema"; diff --git a/L10n/sr-Latn.strings b/L10n/sr-Latn.strings index 39f5bce3..9154e070 100644 --- a/L10n/sr-Latn.strings +++ b/L10n/sr-Latn.strings @@ -46,6 +46,9 @@ /* Folder selection button on Windows. [prefs.py] */ "Browse..." = "Potraži..."; +/* No 'commander' data in CAPI [EDMarketConnector.py] */ +"CAPI: No commander data returned" = "CAPI: Nema podataka o komandiru"; + /* Federation rank. [stats.py] */ "Cadet" = "Cadet"; @@ -226,9 +229,15 @@ /* Hotkey/Shortcut settings prompt on Windows. [prefs.py] */ "Hotkey" = "Skraćenica"; +/* Changed journal update_lock failed [monitor.py] */ +"Ignore" = "Ignoriši"; + /* Section heading in settings. [inara.py] */ "Inara credentials" = "Inara kredencijali"; +/* Changed journal update_lock failed [monitor.py] */ +"Journal directory already locked" = "Žurnal direktorijum je već zaključan"; + /* Hotkey/Shortcut settings prompt on OSX. [prefs.py] */ "Keyboard shortcut" = "Skraćenica"; @@ -409,6 +418,9 @@ /* Help menu item. [EDMarketConnector.py] */ "Release Notes" = "Informacije o ovoj verziji"; +/* Changed journal update_lock failed [monitor.py] */ +"Retry" = "Probaj ponovo"; + /* Multicrew role label in main window. [EDMarketConnector.py] */ "Role" = "Uloga"; @@ -454,6 +466,9 @@ /* Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis. [prefs.py] */ "Shipyard" = "Shipyard"; +/* Status line text that appears when process exit sequence starts [EDMarketConnector.py] */ +"Shutting down..." = "Gasim..."; + /* Empire rank. [stats.py] */ "Squire" = "Squire"; @@ -475,6 +490,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "Sistem"; +/* Changed journal update_lock failed [monitor.py] */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "Novi Žurnal direktorijum je već zaključan.{CR}Možete probati da rešite problem i probate ponovo ili da ga ignorišete."; + /* Appearance setting. [prefs.py] */ "Theme" = "Tema"; diff --git a/L10n/zh-Hans.strings b/L10n/zh-Hans.strings index 68827d9d..16d3102d 100644 --- a/L10n/zh-Hans.strings +++ b/L10n/zh-Hans.strings @@ -1,3 +1,9 @@ +/* Label for 'UI Scaling' option [prefs.py] */ +"UI Scale Percentage" = "UI 缩放比例"; + +/* Text describing that value '100' means 'default', and changes require a restart [prefs.py] */ +"100 means Default{CR}Restart Required for{CR}changes to take effect!" = "100 为默认设置{CR}需要重启应用{CR}以使修改生效"; + /* Language name */ "!Language" = "简体中文"; @@ -40,6 +46,9 @@ /* Folder selection button on Windows. [prefs.py] */ "Browse..." = "浏览…"; +/* No 'commander' data in CAPI [EDMarketConnector.py] */ +"CAPI: No commander data returned" = "CAPI:没有指挥官数据"; + /* Federation rank. [stats.py] */ "Cadet" = "Cadet"; @@ -50,7 +59,7 @@ "Change..." = "更改…"; /* Menu item. [EDMarketConnector.py] */ -"Check for Updates..." = "检查可用更新…"; +"Check for Updates..." = "检查版本更新…"; /* Federation rank. [stats.py] */ "Chief Petty Officer" = "Chief Petty Officer"; @@ -100,6 +109,9 @@ /* Output setting under 'Send system and scan data to the Elite Dangerous Data Network' new in E:D 2.2. [eddn.py] */ "Delay sending until docked" = "延时更新,靠站时再发送"; +/* Option to disabled Automatic Check For Updates whilst in-game [prefs.py] */ +"Disable Automatic Application Updates Check when in-game" = "当我在玩游戏时,禁止此应用自动检查版本更新。"; + /* List of plugins in settings. [prefs.py] */ "Disabled Plugins" = "已禁用插件"; @@ -217,9 +229,15 @@ /* Hotkey/Shortcut settings prompt on Windows. [prefs.py] */ "Hotkey" = "快捷键"; +/* Changed journal update_lock failed [monitor.py] */ +"Ignore" = "忽略"; + /* Section heading in settings. [inara.py] */ "Inara credentials" = "Inara 授权凭证"; +/* Changed journal update_lock failed [monitor.py] */ +"Journal directory already locked" = "日志目录已被锁定"; + /* Hotkey/Shortcut settings prompt on OSX. [prefs.py] */ "Keyboard shortcut" = "键盘快捷键"; @@ -334,6 +352,18 @@ /* Section heading in settings. [prefs.py] */ "Plugins folder" = "插件文件夹"; +/* Popup title: Warning about plugins without Python 3.x support [EDMarketConnector.py] */ +"EDMC: Plugins Without Python 3.x Support" = "EDMC:不支持 Python 3.x 的插件"; + +/* Popup body: Warning about plugins without Python 3.x support [EDMarketConnector.py] */ +"One or more of your enabled plugins do not yet have support for Python 3.x. Please see the list on the '{PLUGINS}' tab of '{FILE}' > '{SETTINGS}'. You should check if there is an updated version available, else alert the developer that they need to update the code for Python 3.x.\r\n\r\nYou can disable a plugin by renaming its folder to have '{DISABLED}' on the end of the name." = "一项或多项你所启用的插件尚不支持 Python 3.x,详情请见 {FILE} > {SETTINGS} > {PLUGINS}。请检查上述插件是否有可用的更新,若没有,则应与插件的开发者就 Python 3.x 的支持问题取得联系。\n\n你也可以禁用某一项插件,只需在其文件夹名称的末尾添加 {DISABLED} 即可。"; + +/* Settings>Plugins>Plugins without Python 3.x support [prefs.py] */ +"Plugins Without Python 3.x Support" = "不支持 Python 3.x 的插件"; + +/* Settings>Plugins>Information on migrating plugins [prefs.py] */ +"Information on migrating plugins" = "关于插件迁移"; + /* Federation rank. [stats.py] */ "Post Captain" = "Post Captain"; @@ -388,6 +418,9 @@ /* Help menu item. [EDMarketConnector.py] */ "Release Notes" = "更新说明"; +/* Changed journal update_lock failed [monitor.py] */ +"Retry" = "重试"; + /* Multicrew role label in main window. [EDMarketConnector.py] */ "Role" = "角色"; @@ -433,6 +466,9 @@ /* Setting to decide which ship outfitting website to link to - either E:D Shipyard or Coriolis. [prefs.py] */ "Shipyard" = "配船"; +/* Status line text that appears when process exit sequence starts [EDMarketConnector.py] */ +"Shutting down..." = "应用关闭中…"; + /* Empire rank. [stats.py] */ "Squire" = "Squire"; @@ -454,6 +490,9 @@ /* Main window. [EDMarketConnector.py] */ "System" = "星系"; +/* Changed journal update_lock failed [monitor.py] */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "新的日志目录位置已被锁定。{CR}你可以尝试解锁并“重试”以解决此问题,或者选择“忽略”。"; + /* Appearance setting. [prefs.py] */ "Theme" = "主题"; @@ -475,6 +514,9 @@ /* Update button in main window. [EDMarketConnector.py] */ "Update" = "更新"; +/* Option to use alternate URL method on shipyard links [prefs.py] */ +"Use alternate URL method" = "若链接过长无法打开,请勾选此项"; + /* Status dialog subtitle - CR value of ship. [stats.py] */ "Value" = "价值"; @@ -508,3 +550,5 @@ /* Shortcut settings prompt on OSX. [prefs.py] */ "{APP} needs permission to use shortcuts" = "{APP} 需要权限以使用快捷键"; +/* Label for user configured level of logging [prefs.py] */ +"Log Level" = "诊断模式"; From 301e7e583fb6efa99e21935442ea0fccf751a228 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 15 Mar 2021 19:27:26 +0000 Subject: [PATCH 19/19] ChangeLog: Remembered to update translations --- ChangeLog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index d5ee275b..25fc8188 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,9 @@ Release 4.2.1 This is a bug-fix release. +* Updated translations. Thanks once again to all those contributing as per + [Translations](https://github.com/EDCD/EDMarketConnector/wiki/Translations). + * PLUGINS.md: Clarify when `CargoJSON` is populated. * macOS: `pip install -r requirements.txt` will now include `pyobjc` so that