From c17716a7f921e58d154b7412a826dc52cbbf12df Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 22 Nov 2021 11:09:18 +0000 Subject: [PATCH 001/186] develop: Reset appversion to 5.3.0-beta0 --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 22663eab..053eb0d7 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.2.3' +_static_appversion = '5.3.0-beta0' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD' From 37533ed2638fb2b0249cc22620395f1d71d3ac04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Nov 2021 17:03:47 +0000 Subject: [PATCH 002/186] build(deps-dev): bump types-requests from 2.26.0 to 2.26.1 Bumps [types-requests](https://github.com/python/typeshed) from 2.26.0 to 2.26.1. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d3c22835..0f8dae5f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.3 mypy==0.910 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.26.0 +types-requests==2.26.1 # Code formatting tools autopep8==1.6.0 From 94a959b0cd3fb79f7188623378b004a37e6d8cf8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Nov 2021 17:03:28 +0000 Subject: [PATCH 003/186] build(deps): bump actions/setup-python from 2.3.0 to 2.3.1 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.3.0 to 2.3.1. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2.3.0...v2.3.1) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/pr-checks.yml | 2 +- .github/workflows/push-checks.yml | 2 +- .github/workflows/windows-build.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index f6a6d44b..a1472e27 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -53,7 +53,7 @@ jobs: # Get Python set up #################################################################### - name: Set up Python 3.9 - uses: actions/setup-python@v2.3.0 + uses: actions/setup-python@v2.3.1 with: python-version: 3.9 - name: Install dependencies diff --git a/.github/workflows/push-checks.yml b/.github/workflows/push-checks.yml index 94bbcbd3..5f78645f 100644 --- a/.github/workflows/push-checks.yml +++ b/.github/workflows/push-checks.yml @@ -21,7 +21,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python 3.9 - uses: actions/setup-python@v2.3.0 + uses: actions/setup-python@v2.3.1 with: python-version: 3.9 - name: Install dependencies diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 5f394a24..9a30947a 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - - uses: actions/setup-python@v2.3.0 + - uses: actions/setup-python@v2.3.1 with: python-version: "3.9.9" architecture: "x86" From eda2ace51b620a21f925ea33390b66d27d4ef1a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Nov 2021 17:03:42 +0000 Subject: [PATCH 004/186] build(deps-dev): bump coverage[toml] from 6.1.2 to 6.2 Bumps [coverage[toml]](https://github.com/nedbat/coveragepy) from 6.1.2 to 6.2. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/6.1.2...6.2) --- updated-dependencies: - dependency-name: coverage[toml] dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0f8dae5f..3b443e17 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -33,7 +33,7 @@ py2exe==0.10.4.1; sys_platform == 'win32' # Testing pytest==6.2.5 pytest-cov==3.0.0 # Pytest code coverage support -coverage[toml]==6.1.2 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs +coverage[toml]==6.2 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs # For manipulating folder permissions and the like. pywin32==302; sys_platform == 'win32' From 42fea4ec349f7216e0ca60349b779f68fa5aacca Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 29 Nov 2021 17:47:25 +0000 Subject: [PATCH 005/186] EDDN: Set custom User-Agent --- plugins/eddn.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 171807b2..c2bb11c9 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -21,7 +21,7 @@ import killswitch import myNotebook as nb # noqa: N813 import plug from companion import CAPIData, category_map -from config import applongname, appversion_nobuild, config, debug_senders +from config import applongname, appname, appversion, appversion_nobuild, config, debug_senders from EDMCLogging import get_main_logger from monitor import monitor from myNotebook import Frame @@ -101,6 +101,9 @@ HORIZONS_SKU = 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' # Thus do **NOT** use either of these in addition to the PLANETARY_LANDINGS # one. +# Custom user agent, to be clear in EDDN logs +USER_AGENT = f'EDCD-{appname}-{appversion()}' + # TODO: a good few of these methods are static or could be classmethods. they should be created as such. @@ -125,6 +128,7 @@ class EDDN: def __init__(self, parent: tk.Tk): self.parent: tk.Tk = parent self.session = requests.Session() + self.session.headers['User-Agent'] = USER_AGENT self.replayfile: Optional[TextIO] = None # For delayed messages self.replaylog: List[str] = [] From 2fd72fde8e9fed19250ca99e5d8b1eb96475b083 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 29 Nov 2021 17:51:19 +0000 Subject: [PATCH 006/186] EDSM: Set custom User-Agent for requests session --- plugins/edsm.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/edsm.py b/plugins/edsm.py index 33a39804..781994ab 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -23,7 +23,7 @@ import killswitch import myNotebook as nb # noqa: N813 import plug from companion import CAPIData -from config import applongname, appversion, config, debug_senders +from config import applongname, appname, appversion, config, debug_senders from edmc_data import DEBUG_WEBSERVER_HOST, DEBUG_WEBSERVER_PORT from EDMCLogging import get_main_logger from ttkHyperlinkLabel import HyperlinkLabel @@ -37,6 +37,8 @@ logger = get_main_logger() EDSM_POLL = 0.1 _TIMEOUT = 20 DISCARDED_EVENTS_SLEEP = 10 +# Custom user agent +USER_AGENT = f'EDCD-{appname}-{appversion()}' # trace-if events CMDR_EVENTS = 'plugin.edsm.cmdr-events' @@ -49,6 +51,7 @@ class This: self.shutting_down = False # Plugin is shutting down. self.session: requests.Session = requests.Session() + self.session.headers['User-Agent'] = USER_AGENT self.queue: Queue = Queue() # Items to be sent to EDSM by worker thread self.discarded_events: Set[str] = [] # List discarded events from EDSM self.lastlookup: requests.Response # Result of last system lookup From 4ab6d6292622fc8422cd4512d05950df7092b36f Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 29 Nov 2021 18:04:56 +0000 Subject: [PATCH 007/186] user_agent: Set this once in config.py and use it everywhere * Also flake8/mypy pass on timeout_session.py --- companion.py | 7 +++---- config.py | 3 +++ plugins/eddn.py | 7 ++----- plugins/edsm.py | 6 ++---- timeout_session.py | 17 ++++++++++------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/companion.py b/companion.py index a518fac1..9a175a1d 100644 --- a/companion.py +++ b/companion.py @@ -30,7 +30,7 @@ import requests import config as conf_module import protocol -from config import appname, appversion, config +from config import config, user_agent from edmc_data import companion_category_map as category_map from EDMCLogging import get_main_logger from monitor import monitor @@ -54,7 +54,6 @@ auth_timeout = 30 # timeout for initial auth # Used by both class Auth and Session FRONTIER_AUTH_SERVER = 'https://auth.frontierstore.net' -USER_AGENT = f'EDCD-{appname}-{appversion()}' SERVER_LIVE = 'https://companion.orerve.net' SERVER_BETA = 'https://pts-companion.orerve.net' @@ -305,7 +304,7 @@ class Auth(object): def __init__(self, cmdr: str) -> None: self.cmdr: str = cmdr self.requests_session = requests.Session() - self.requests_session.headers['User-Agent'] = USER_AGENT + self.requests_session.headers['User-Agent'] = user_agent self.verifier: Union[bytes, None] = None self.state: Union[str, None] = None @@ -644,7 +643,7 @@ class Session(object): logger.debug('Starting session') self.requests_session = requests.Session() self.requests_session.headers['Authorization'] = f'Bearer {access_token}' - self.requests_session.headers['User-Agent'] = USER_AGENT + self.requests_session.headers['User-Agent'] = user_agent self.state = Session.STATE_OK def login(self, cmdr: str = None, is_beta: Optional[bool] = None) -> bool: diff --git a/config.py b/config.py index 053eb0d7..882bd4c5 100644 --- a/config.py +++ b/config.py @@ -164,6 +164,9 @@ def appversion() -> semantic_version.Version: return _cached_version +user_agent = f'EDCD-{appname}-{appversion()}' + + def appversion_nobuild() -> semantic_version.Version: """ Determine app version without *any* build meta data. diff --git a/plugins/eddn.py b/plugins/eddn.py index c2bb11c9..37165ea1 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -21,7 +21,7 @@ import killswitch import myNotebook as nb # noqa: N813 import plug from companion import CAPIData, category_map -from config import applongname, appname, appversion, appversion_nobuild, config, debug_senders +from config import applongname, appversion_nobuild, config, debug_senders, user_agent from EDMCLogging import get_main_logger from monitor import monitor from myNotebook import Frame @@ -101,9 +101,6 @@ HORIZONS_SKU = 'ELITE_HORIZONS_V_PLANETARY_LANDINGS' # Thus do **NOT** use either of these in addition to the PLANETARY_LANDINGS # one. -# Custom user agent, to be clear in EDDN logs -USER_AGENT = f'EDCD-{appname}-{appversion()}' - # TODO: a good few of these methods are static or could be classmethods. they should be created as such. @@ -128,7 +125,7 @@ class EDDN: def __init__(self, parent: tk.Tk): self.parent: tk.Tk = parent self.session = requests.Session() - self.session.headers['User-Agent'] = USER_AGENT + self.session.headers['User-Agent'] = user_agent self.replayfile: Optional[TextIO] = None # For delayed messages self.replaylog: List[str] = [] diff --git a/plugins/edsm.py b/plugins/edsm.py index 781994ab..31f5a317 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -23,7 +23,7 @@ import killswitch import myNotebook as nb # noqa: N813 import plug from companion import CAPIData -from config import applongname, appname, appversion, config, debug_senders +from config import applongname, appversion, config, debug_senders, user_agent from edmc_data import DEBUG_WEBSERVER_HOST, DEBUG_WEBSERVER_PORT from EDMCLogging import get_main_logger from ttkHyperlinkLabel import HyperlinkLabel @@ -37,8 +37,6 @@ logger = get_main_logger() EDSM_POLL = 0.1 _TIMEOUT = 20 DISCARDED_EVENTS_SLEEP = 10 -# Custom user agent -USER_AGENT = f'EDCD-{appname}-{appversion()}' # trace-if events CMDR_EVENTS = 'plugin.edsm.cmdr-events' @@ -51,7 +49,7 @@ class This: self.shutting_down = False # Plugin is shutting down. self.session: requests.Session = requests.Session() - self.session.headers['User-Agent'] = USER_AGENT + self.session.headers['User-Agent'] = user_agent self.queue: Queue = Queue() # Items to be sent to EDSM by worker thread self.discarded_events: Set[str] = [] # List discarded events from EDSM self.lastlookup: requests.Response # Result of last system lookup diff --git a/timeout_session.py b/timeout_session.py index 3da5e3ab..6faf54bb 100644 --- a/timeout_session.py +++ b/timeout_session.py @@ -1,22 +1,24 @@ - +"""A requests.session with a TimeoutAdapter.""" import requests from requests.adapters import HTTPAdapter +from config import user_agent + REQUEST_TIMEOUT = 10 # reasonable timeout that all HTTP requests should use class TimeoutAdapter(HTTPAdapter): - """ - TimeoutAdapter is an HTTP Adapter that enforces an overridable default timeout on HTTP requests. - """ - def __init__(self, timeout, *args, **kwargs): + """An HTTP Adapter that enforces an overridable default timeout on HTTP requests.""" + + def __init__(self, timeout: int, *args, **kwargs): self.default_timeout = timeout if kwargs.get("timeout") is not None: del kwargs["timeout"] super().__init__(*args, **kwargs) - def send(self, *args, **kwargs): + def send(self, *args, **kwargs) -> requests.Response: + """Send, but with a timeout always set.""" if kwargs["timeout"] is None: kwargs["timeout"] = self.default_timeout @@ -25,7 +27,7 @@ class TimeoutAdapter(HTTPAdapter): def new_session(timeout: int = REQUEST_TIMEOUT, session: requests.Session = None) -> requests.Session: """ - new_session creates a new requests.Session and overrides the default HTTPAdapter with a TimeoutAdapter. + Create a new requests.Session and override the default HTTPAdapter with a TimeoutAdapter. :param timeout: the timeout to set the TimeoutAdapter to, defaults to REQUEST_TIMEOUT :param session: the Session object to attach the Adapter to, defaults to a new session @@ -33,6 +35,7 @@ def new_session(timeout: int = REQUEST_TIMEOUT, session: requests.Session = None """ if session is None: session = requests.Session() + session.headers['User-Agent'] = user_agent adapter = TimeoutAdapter(timeout) session.mount("http://", adapter) From 3a28945ad5696f5e0f50ffeee6ece017cb77a050 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 3 Dec 2021 11:17:18 +0000 Subject: [PATCH 008/186] Python 3.10: Change version number in most of our local files The requirements-dev.txt change for py2exe will be in a separate commit. --- .github/workflows/windows-build.yml | 2 +- .pre-commit-config.yaml | 2 +- .python-version | 2 +- EDMarketConnector.wxs | 2 +- setup.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 9a30947a..74f8ebe8 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v2.4.0 - uses: actions/setup-python@v2.3.1 with: - python-version: "3.9.9" + python-version: "3.10.0" architecture: "x86" - name: Install python tools diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8bfb90c..a6effa3b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,7 +77,7 @@ repos: always_run: true default_language_version: - python: python3.9 + python: python3.10 default_stages: [ commit, push ] diff --git a/.python-version b/.python-version index b04bfd83..30291cba 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.9.9 +3.10.0 diff --git a/EDMarketConnector.wxs b/EDMarketConnector.wxs index c24a594f..afa69e1a 100644 --- a/EDMarketConnector.wxs +++ b/EDMarketConnector.wxs @@ -198,7 +198,7 @@ - + diff --git a/setup.py b/setup.py index 183a7383..52be2c78 100755 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ from config import ( ) from constants import GITVERSION_FILE -if sys.version_info[0:2] != (3, 9): +if sys.version_info[0:2] != (3, 10): raise AssertionError(f'Unexpected python version {sys.version}') ########################################################################### From d546e84031e39f68cc7e60821d5b158c42ef93a5 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 3 Dec 2021 11:40:31 +0000 Subject: [PATCH 009/186] Python 3.10: Bump py2exe version --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3b443e17..afc11caf 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -28,7 +28,7 @@ grip==4.5.2 # Packaging # We only need py2exe on windows. -py2exe==0.10.4.1; sys_platform == 'win32' +py2exe==0.11.0.1; sys_platform == 'win32' # Testing pytest==6.2.5 From 74c2c295bcabfac02bf97bf718cf486b4edc6994 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 3 Dec 2021 11:47:14 +0000 Subject: [PATCH 010/186] Python 3.10: Remove un-necessary pyd files _aynscio.pyd _multiprocessing.pyd _overlapped.pyd They do still exist in Python 3.10, but we don't use them and it seems py2exe is no longer causing their inclusion in the build. --- EDMarketConnector.wxs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/EDMarketConnector.wxs b/EDMarketConnector.wxs index afa69e1a..d56dff92 100644 --- a/EDMarketConnector.wxs +++ b/EDMarketConnector.wxs @@ -113,9 +113,6 @@ - - - @@ -134,12 +131,6 @@ - - - - - - @@ -152,9 +143,6 @@ - - - @@ -562,20 +550,16 @@ - - - - @@ -604,7 +588,7 @@ - + From bb65718cef0703c8f1c3723a4314d7f71ae59063 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 3 Dec 2021 12:21:36 +0000 Subject: [PATCH 011/186] Python 3.10: Final adjustments to .wxs file We were missing some new files which seemed to be the cause of issues with the definitely included and installed _tkinter.pyd. I think it was due to missing _win32sysloader.pyd but didn't test in detail. --- EDMarketConnector.wxs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/EDMarketConnector.wxs b/EDMarketConnector.wxs index d56dff92..621aafa2 100644 --- a/EDMarketConnector.wxs +++ b/EDMarketConnector.wxs @@ -146,6 +146,12 @@ + + + + + + @@ -188,6 +194,12 @@ + + + + + + @@ -215,6 +227,15 @@ + + + + + + + + + @@ -561,6 +582,8 @@ + + @@ -589,6 +612,8 @@ + + @@ -604,6 +629,9 @@ + + + From 8be6a53f43468198ff36e26cfdeb9625098ca782 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 4 Dec 2021 09:26:23 +0000 Subject: [PATCH 012/186] Python 3.10: Change tcl/tk DLLs to '*' GUID For reasons not logged by: msiexec /l*vx 5.3.0-beta0-install.txt /i EDMarketConnector_win_5.3.0-beta0.msi if you install the GitHub-built 5.3.0-beta0 installer in place of 5.2.3 the two files tcl86t.dll & tk86t.dll do not get installed. Other than WinSparkle.dll (which won't have changed in a long time) they're the only two DLL files with a hard-coded GUID. So let's try changing them to '*'. --- EDMarketConnector.wxs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.wxs b/EDMarketConnector.wxs index 621aafa2..d5d145bc 100644 --- a/EDMarketConnector.wxs +++ b/EDMarketConnector.wxs @@ -218,10 +218,10 @@ - + - + From 5b20451209f5df21acec14772761f6eeb8fdfe89 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sun, 5 Dec 2021 11:34:12 +0000 Subject: [PATCH 013/186] Revert "Python 3.10: Change tcl/tk DLLs to '*' GUID" This reverts commit 8be6a53f43468198ff36e26cfdeb9625098ca782. It didn't help and will potentially cause other issues. --- EDMarketConnector.wxs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EDMarketConnector.wxs b/EDMarketConnector.wxs index d5d145bc..621aafa2 100644 --- a/EDMarketConnector.wxs +++ b/EDMarketConnector.wxs @@ -218,10 +218,10 @@ - + - + From 342edf57d4a4bf4d2886c6c3a58447aeb40e1196 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 7 Dec 2021 11:03:00 +0000 Subject: [PATCH 014/186] Python: Bump 3.10.1 --- .github/workflows/windows-build.yml | 2 +- .python-version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 74f8ebe8..80b61487 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v2.4.0 - uses: actions/setup-python@v2.3.1 with: - python-version: "3.10.0" + python-version: "3.10.1" architecture: "x86" - name: Install python tools diff --git a/.python-version b/.python-version index 30291cba..f870be23 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.10.0 +3.10.1 From f3b5f2b4ede9d6c570540df93b938b78194f442f Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 7 Dec 2021 11:28:11 +0000 Subject: [PATCH 015/186] Pre-Release 5.3.0-beta1: appversion & changelog --- ChangeLog.md | 35 ++++++++++++++++++++++++++++++++++- config.py | 2 +- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 9bdc3ad7..5c2f7e73 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -9,7 +9,7 @@ produce the Windows executables and installer. --- -* We now test against, and package with, Python 3.9.9. +* We now test against, and package with, Python 3.10.1. **As a consequence of this we no longer support Windows 7. This is due to @@ -25,6 +25,39 @@ produce the Windows executables and installer. in the source (it's not distributed with the Windows installer) for the currently used version in a given branch. +--- +Pre-Release 5.3.0-beta1 +=== + +This is a test release to ensure packaging with Python 3.10.1 is working +correctly. There is also a small change to metadata in any remote web +request we make. + +* We now set a custom User-Agent header in all web requests, i.e. to EDDN, + EDSM and the like. This is of the form: + + `EDCD-EDMarketConnector-` + +Developers +--- + +We now test against, and package with Python 3.10.1. + +We've made no explicit changes to the Python stdlib, or other modules, we +currently offer. However, the newer [py2exe](https://github.com/py2exe/py2exe/) +we now use has decided we no longer need: + + - _aynscio.pyd + - _multiprocessing.pyd + - _overlapped.pyd + +so those are no longer included in the Windows installers. We are looking into +[including all of Python stdlib](https://github.com/EDCD/EDMarketConnector/issues/1327) +so this would be resolved by that. + +If your plugin utilises any module/package that requires per-architecture +libraries then you should check it has Python 3.10 wheels available. + --- Release 5.2.3 diff --git a/config.py b/config.py index 882bd4c5..7df3e218 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.3.0-beta0' +_static_appversion = '5.3.0-beta1' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD' From 43394b17ffcf908b3747e1985075755dca002872 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 7 Dec 2021 15:27:33 +0000 Subject: [PATCH 016/186] Initial work on auto-generating WiX Component list * We'll be generating EDMarketConnector.wxs, so remove it from git and add to .gitignore * Initial work on using `lxml` to parse in wix/components.wxs, generated by heat.exe, to pick out what we need, modify it, and eventually stitch into a generated EDMarketConnector.wxs --- .gitignore | 1 + EDMarketConnector.wxs | 724 ------------------------------------------ setup.py | 63 ++++ 3 files changed, 64 insertions(+), 724 deletions(-) delete mode 100644 EDMarketConnector.wxs diff --git a/.gitignore b/.gitignore index 7474987d..49459783 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ venv htmlcov/ .ignored .coverage +EDMarketConnector.wxs diff --git a/EDMarketConnector.wxs b/EDMarketConnector.wxs deleted file mode 100644 index 621aafa2..00000000 --- a/EDMarketConnector.wxs +++ /dev/null @@ -1,724 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - WIX_UPGRADE_DETECTED AND ARPINSTALLLOCATION - - - - - WIX_UPGRADE_DETECTED AND ARPINSTALLLOCATION - - - - - - - - - NOT Installed - - - - - - - - - - NOT Installed AND LAUNCH ~= "yes" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/setup.py b/setup.py index 52be2c78..a87773f6 100755 --- a/setup.py +++ b/setup.py @@ -9,6 +9,7 @@ Script to build to .exe and .msi package. import codecs import os +import pathlib import platform import re import shutil @@ -18,6 +19,8 @@ from os.path import exists, isdir, join from tempfile import gettempdir from typing import Any, Generator, Set +from lxml import etree + from config import ( appcmdname, applongname, appname, appversion, appversion_nobuild, copyright, git_shorthash_from_head, update_feed, update_interval @@ -283,6 +286,66 @@ if sys.platform == 'darwin': os.system(f'cd {dist_dir}; ditto -ck --keepParent --sequesterRsrc {appname}.app ../{package_filename}; cd ..') elif sys.platform == 'win32': + header_file = pathlib.Path('wix/header.wxs') + components_file = pathlib.Path('wix/components.wxs') + components_transformed_file = pathlib.Path(r'wix/components_transformed.wxs') + + header_tree = etree.parse(header_file) + # Use heat.exe to generate the Component for all files inside dist.win32 + os.system(rf'"{WIXPATH}\heat.exe" dir {dist_dir}\ -ag -sfrag -srid -suid -out {components_file}') + + component_tree = etree.parse(components_file) + # 1. Change the element: + # + # + # + # to: + # + # + win32 = component_tree.find('.//{*}Directory[@Id="dist.win32"][@Name="dist.win32"]') + if not win32: + raise ValueError(f'{components_file}: Expected Directory with Id="dist.win32"') + + win32.set('Id', 'INSTALLDIR') + win32.set('Name', '$(var.PRODUCTNAME)') + # 2. Change: + # + # + # + # + # + # to: + # + # + # + # + # + main_executable = win32.find('.//{*}Component[@Id="EDMarketConnector.exe"]') + if not main_executable: + raise ValueError(f'{components_file}: Expected Component with Id="EDMarketConnector.exe"') + + main_executable.set('Id', 'MainExecutable') + main_executable.set('Guid', '{D33BB66E-9664-4AB6-A044-3004B50A09B0}') + shortcut = etree.SubElement( + main_executable, + 'Shortcut', + nsmap=main_executable.nsmap, + attrib={ + 'Id': 'MainExeShortcut', + 'Directory': 'ProgramMenuFolder', + 'Name': '$(var.PRODUCTLONGNAME)', + 'Description': 'Downloads station data from Elite: Dangerous', + 'WorkingDirectory': 'INSTALLDIR', + 'Icon': 'EDMarketConnector.exe', + 'IconIndex': '0', + 'Advertise': 'yes' + } + ) + + # Append the Feature/ComponentRef listing to match + # Concatenate our header, this middle, and our footer. os.system(rf'"{WIXPATH}\candle.exe" -out {dist_dir}\ {appname}.wxs') if not exists(f'{dist_dir}/{appname}.wixobj'): From 6ed64c9d6b5e0202b27101e92c7fc04478d9bf0b Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 7 Dec 2021 15:51:38 +0000 Subject: [PATCH 017/186] WiX: .gitignore components.wxs & add template.wxs Also renamed header.wxs to template.wxs in setup.py, as we're going to insert into the middle of its content, not use it as a simple concatenated header. --- .gitignore | 1 + setup.py | 8 +-- wix/template.wxs | 126 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 wix/template.wxs diff --git a/.gitignore b/.gitignore index 49459783..9bea3d5a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ htmlcov/ .ignored .coverage EDMarketConnector.wxs +wix/components.wxs diff --git a/setup.py b/setup.py index a87773f6..afb4233c 100755 --- a/setup.py +++ b/setup.py @@ -286,11 +286,10 @@ if sys.platform == 'darwin': os.system(f'cd {dist_dir}; ditto -ck --keepParent --sequesterRsrc {appname}.app ../{package_filename}; cd ..') elif sys.platform == 'win32': - header_file = pathlib.Path('wix/header.wxs') + template_file = pathlib.Path('wix/template.wxs') components_file = pathlib.Path('wix/components.wxs') components_transformed_file = pathlib.Path(r'wix/components_transformed.wxs') - header_tree = etree.parse(header_file) # Use heat.exe to generate the Component for all files inside dist.win32 os.system(rf'"{WIXPATH}\heat.exe" dir {dist_dir}\ -ag -sfrag -srid -suid -out {components_file}') @@ -345,7 +344,10 @@ elif sys.platform == 'win32': ) # Append the Feature/ComponentRef listing to match - # Concatenate our header, this middle, and our footer. + + template_tree = etree.parse(template_file) + # Insert what we now have into the template and write it out + os.system(rf'"{WIXPATH}\candle.exe" -out {dist_dir}\ {appname}.wxs') if not exists(f'{dist_dir}/{appname}.wixobj'): diff --git a/wix/template.wxs b/wix/template.wxs new file mode 100644 index 00000000..0d365919 --- /dev/null +++ b/wix/template.wxs @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WIX_UPGRADE_DETECTED AND ARPINSTALLLOCATION + + + + + WIX_UPGRADE_DETECTED AND ARPINSTALLLOCATION + + + + + + + + + NOT Installed + + + + + + + + + + NOT Installed AND LAUNCH ~= "yes" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ff32c299cf4053e79fb3e524144fe60fc159a04f Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 7 Dec 2021 16:16:27 +0000 Subject: [PATCH 018/186] WiX: Insertion of heat.exe produced Components now working --- setup.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index afb4233c..e829b74d 100755 --- a/setup.py +++ b/setup.py @@ -288,12 +288,12 @@ if sys.platform == 'darwin': elif sys.platform == 'win32': template_file = pathlib.Path('wix/template.wxs') components_file = pathlib.Path('wix/components.wxs') - components_transformed_file = pathlib.Path(r'wix/components_transformed.wxs') + final_wxs_file = pathlib.Path('wix/final.wxs') # Use heat.exe to generate the Component for all files inside dist.win32 os.system(rf'"{WIXPATH}\heat.exe" dir {dist_dir}\ -ag -sfrag -srid -suid -out {components_file}') - component_tree = etree.parse(components_file) + component_tree = etree.parse(str(components_file)) # 1. Change the element: # # @@ -301,12 +301,12 @@ elif sys.platform == 'win32': # to: # # - win32 = component_tree.find('.//{*}Directory[@Id="dist.win32"][@Name="dist.win32"]') - if not win32: + directory_win32 = component_tree.find('.//{*}Directory[@Id="dist.win32"][@Name="dist.win32"]') + if directory_win32 is None: raise ValueError(f'{components_file}: Expected Directory with Id="dist.win32"') - win32.set('Id', 'INSTALLDIR') - win32.set('Name', '$(var.PRODUCTNAME)') + directory_win32.set('Id', 'INSTALLDIR') + directory_win32.set('Name', '$(var.PRODUCTNAME)') # 2. Change: # # @@ -321,8 +321,8 @@ elif sys.platform == 'win32': # Description="Downloads station data from Elite: Dangerous" WorkingDirectory="INSTALLDIR" # Icon="EDMarketConnector.exe" IconIndex="0" Advertise="yes" /> # - main_executable = win32.find('.//{*}Component[@Id="EDMarketConnector.exe"]') - if not main_executable: + main_executable = directory_win32.find('.//{*}Component[@Id="EDMarketConnector.exe"]') + if main_executable is None: raise ValueError(f'{components_file}: Expected Component with Id="EDMarketConnector.exe"') main_executable.set('Id', 'MainExecutable') @@ -342,11 +342,22 @@ elif sys.platform == 'win32': 'Advertise': 'yes' } ) + # Now insert the appropriate parts as a child of the ProgramFilesFolder part + # of the template. + template_tree = etree.parse(str(template_file)) + program_files_folder = template_tree.find('.//{*}Directory[@Id="ProgramFilesFolder"]') + if program_files_folder is None: + raise ValueError(f'{template_file}: Expected Directory with Id="ProgramFilesFolder"') + program_files_folder.insert(0, directory_win32) # Append the Feature/ComponentRef listing to match - template_tree = etree.parse(template_file) # Insert what we now have into the template and write it out + template_tree.write( + str(final_wxs_file), encoding='utf-8', + pretty_print=True, + xml_declaration=True + ) os.system(rf'"{WIXPATH}\candle.exe" -out {dist_dir}\ {appname}.wxs') From 931740f6401bc6948816e7117b8cfeb8112e47a3 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 7 Dec 2021 16:17:38 +0000 Subject: [PATCH 019/186] WiX: Re-add the hard-coded EDMarketConnector.wxs inside wix/ folder So that it's around for reference. --- .gitignore | 1 - wix/EDMarketConnector.wxs | 724 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 724 insertions(+), 1 deletion(-) create mode 100644 wix/EDMarketConnector.wxs diff --git a/.gitignore b/.gitignore index 9bea3d5a..989d6405 100644 --- a/.gitignore +++ b/.gitignore @@ -23,5 +23,4 @@ venv htmlcov/ .ignored .coverage -EDMarketConnector.wxs wix/components.wxs diff --git a/wix/EDMarketConnector.wxs b/wix/EDMarketConnector.wxs new file mode 100644 index 00000000..d19dfa67 --- /dev/null +++ b/wix/EDMarketConnector.wxs @@ -0,0 +1,724 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WIX_UPGRADE_DETECTED AND ARPINSTALLLOCATION + + + + + WIX_UPGRADE_DETECTED AND ARPINSTALLLOCATION + + + + + + + + + NOT Installed + + + + + + + + + + NOT Installed AND LAUNCH ~= "yes" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0af2af344deba8d2e8af2248e65e80749bbbae3c Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 7 Dec 2021 16:18:26 +0000 Subject: [PATCH 020/186] WiX: Output to final .wxs to expected filename for next stages --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e829b74d..bfab647d 100755 --- a/setup.py +++ b/setup.py @@ -288,7 +288,7 @@ if sys.platform == 'darwin': elif sys.platform == 'win32': template_file = pathlib.Path('wix/template.wxs') components_file = pathlib.Path('wix/components.wxs') - final_wxs_file = pathlib.Path('wix/final.wxs') + final_wxs_file = pathlib.Path('EDMarketConnector.wxs') # Use heat.exe to generate the Component for all files inside dist.win32 os.system(rf'"{WIXPATH}\heat.exe" dir {dist_dir}\ -ag -sfrag -srid -suid -out {components_file}') From a196bfe1722dfd9f9ee368ffdabd03a148b30ba7 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 7 Dec 2021 16:48:11 +0000 Subject: [PATCH 021/186] WiX: Autogeneration of EDMarketConnector.wxs fully working * Changed config version to 5.3.0-beta2 to be sure during testing. * Fixed the case on ChangeLog.md in setup.py py2exe config. * Replaced all TABs with <4 spaces> in wix/template.wxs. * Now generating the correct Feature tree in the output file. NB: The 'RegistryEntries' isn't part of the file Components but still needs to be referenced inside . --- config.py | 2 +- setup.py | 26 ++++++- wix/template.wxs | 194 +++++++++++++++++++++++------------------------ 3 files changed, 123 insertions(+), 99 deletions(-) diff --git a/config.py b/config.py index 7df3e218..ff0b7fed 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.3.0-beta1' +_static_appversion = '5.3.0-beta2' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD' diff --git a/setup.py b/setup.py index bfab647d..ba88ce7c 100755 --- a/setup.py +++ b/setup.py @@ -212,7 +212,7 @@ elif sys.platform == 'win32': 'WinSparkle.dll', 'WinSparkle.pdb', # For debugging - don't include in package 'EUROCAPS.TTF', - 'Changelog.md', + 'ChangeLog.md', 'commodity.csv', 'rare_commodity.csv', 'snd_good.wav', @@ -351,6 +351,30 @@ elif sys.platform == 'win32': program_files_folder.insert(0, directory_win32) # Append the Feature/ComponentRef listing to match + feature = template_tree.find('.//{*}Feature[@Id="Complete"][@Level="1"]') + if feature is None: + raise ValueError(f'{template_file}: Expected Feature element with Id="Complete" Level="1"') + + # This isn't part of the components + feature.append( + etree.Element( + 'ComponentRef', + attrib={ + 'Id': 'RegistryEntries' + }, + nsmap=directory_win32.nsmap + ) + ) + for c in directory_win32.findall('.//{*}Component'): + feature.append( + etree.Element( + 'ComponentRef', + attrib={ + 'Id': c.get('Id') + }, + nsmap=directory_win32.nsmap + ) + ) # Insert what we now have into the template and write it out template_tree.write( diff --git a/wix/template.wxs b/wix/template.wxs index 0d365919..7e4409a4 100644 --- a/wix/template.wxs +++ b/wix/template.wxs @@ -6,119 +6,119 @@ - + - - - + + + - - - + + + - + - + - - - - + + + + - - - - - - - - - WIX_UPGRADE_DETECTED AND ARPINSTALLLOCATION - - - - - WIX_UPGRADE_DETECTED AND ARPINSTALLLOCATION - - + + + + + + + + + WIX_UPGRADE_DETECTED AND ARPINSTALLLOCATION + + + + + WIX_UPGRADE_DETECTED AND ARPINSTALLLOCATION + + - - - - - - NOT Installed - - + + + + + + NOT Installed + + - - - - - - - NOT Installed AND LAUNCH ~= "yes" - - + + + + + + + NOT Installed AND LAUNCH ~= "yes" + + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - + - - + + - + From 97f025e1b96f1092a38f1c944b2007ad49fd2895 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 7 Dec 2021 17:08:58 +0000 Subject: [PATCH 022/186] requirements-dev: Add lxml, used for Windows packaging --- requirements-dev.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index afc11caf..221b5997 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -27,6 +27,8 @@ autopep8==1.6.0 grip==4.5.2 # Packaging +# Used to put together a WiX configuration from template/auto-gen +lxml==4.6.4 # We only need py2exe on windows. py2exe==0.11.0.1; sys_platform == 'win32' From ee66b9deb1b3396d1522cf6e1ca7beb822be2016 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 7 Dec 2021 17:16:21 +0000 Subject: [PATCH 023/186] WiX: Move EDMarketConnector.wixobj to root directory * Don't specify its generation inside dist.win32/ * Specify `-b dist.win32\` to light.exe invocation Tested with installing resultant .msi over 5.2.3. --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index ba88ce7c..8bb8fb30 100755 --- a/setup.py +++ b/setup.py @@ -383,13 +383,13 @@ elif sys.platform == 'win32': xml_declaration=True ) - os.system(rf'"{WIXPATH}\candle.exe" -out {dist_dir}\ {appname}.wxs') + os.system(rf'"{WIXPATH}\candle.exe" {appname}.wxs') - if not exists(f'{dist_dir}/{appname}.wixobj'): - raise AssertionError(f'No {dist_dir}/{appname}.wixobj: candle.exe failed?') + if not exists(f'{appname}.wixobj'): + raise AssertionError(f'No {appname}.wixobj: candle.exe failed?') package_filename = f'{appname}_win_{appversion_nobuild()}.msi' - os.system(rf'"{WIXPATH}\light.exe" -sacl -spdb -sw1076 {dist_dir}\{appname}.wixobj -out {package_filename}') + os.system(rf'"{WIXPATH}\light.exe" -b {dist_dir}\ -sacl -spdb -sw1076 {appname}.wixobj -out {package_filename}') if not exists(package_filename): raise AssertionError(f'light.exe failed, no {package_filename}') From 6737ab932292d4b60ebbf72e7dfb92a453782c70 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 7 Dec 2021 17:20:44 +0000 Subject: [PATCH 024/186] WiX: Rename to wix/static-EDMarketConnector.wxs and .gitignore generated --- .gitignore | 1 + wix/{EDMarketConnector.wxs => static-EDMarketConnector.wxs} | 0 2 files changed, 1 insertion(+) rename wix/{EDMarketConnector.wxs => static-EDMarketConnector.wxs} (100%) diff --git a/.gitignore b/.gitignore index 989d6405..9bea3d5a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ venv htmlcov/ .ignored .coverage +EDMarketConnector.wxs wix/components.wxs diff --git a/wix/EDMarketConnector.wxs b/wix/static-EDMarketConnector.wxs similarity index 100% rename from wix/EDMarketConnector.wxs rename to wix/static-EDMarketConnector.wxs From 43d98bff1c847e56811408003ce327f3e07f88f7 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 7 Dec 2021 17:36:51 +0000 Subject: [PATCH 025/186] WiX: Add/update comments in template.wxs about auto-generation We *are* now using auto GUIDs. It worked for a 5.2.3 -> 5.3.0-beta2 upgrade install. I suspect that specifying 'MajorUpgrade' means the GUIDs mostly don't even matter. They're there for if you try to patch so as to patch the correct files? --- wix/template.wxs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wix/template.wxs b/wix/template.wxs index 7e4409a4..a15e0fc1 100644 --- a/wix/template.wxs +++ b/wix/template.wxs @@ -104,9 +104,7 @@ - - - + @@ -115,6 +113,7 @@ + From 381c156fb9c2fa793fb91584d34c5767ab1eae6b Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 11 Dec 2021 10:53:31 +0000 Subject: [PATCH 026/186] l10n: Change _Locale.preferred_languages() to use a common return var This is the first step before putting any necessary "difference between OneSky and system names for languages" translations in. --- l10n.py | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/l10n.py b/l10n.py index 7ffed54b..b1ff2672 100755 --- a/l10n.py +++ b/l10n.py @@ -309,7 +309,7 @@ class _Locale: return None - def preferred_languages(self) -> Iterable[str]: + def preferred_languages(self) -> Iterable[str]: # noqa: CCR001 """ Return a list of preferred language codes. @@ -320,34 +320,41 @@ class _Locale: :return: The preferred language list """ + languages: Iterable[str] if platform == 'darwin': - return NSLocale.preferredLanguages() + languages = NSLocale.preferredLanguages() elif platform != 'win32': # POSIX lang = locale.getlocale()[0] - return lang and [lang.replace('_', '-')] or [] + languages = lang and [lang.replace('_', '-')] or [] - def wszarray_to_list(array): - offset = 0 - while offset < len(array): - sz = ctypes.wstring_at(ctypes.addressof(array) + offset*2) - if sz: - yield sz - offset += len(sz)+1 + else: + def wszarray_to_list(array): + offset = 0 + while offset < len(array): + sz = ctypes.wstring_at(ctypes.addressof(array) + offset*2) + if sz: + yield sz + offset += len(sz)+1 - else: - break + else: + break - num = ctypes.c_ulong() - size = ctypes.c_ulong(0) - if GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, ctypes.byref(num), None, ctypes.byref(size)) and size.value: - buf = ctypes.create_unicode_buffer(size.value) + num = ctypes.c_ulong() + size = ctypes.c_ulong(0) + languages = [] + if GetUserPreferredUILanguages( + MUI_LANGUAGE_NAME, ctypes.byref(num), None, ctypes.byref(size) + ) and size.value: + buf = ctypes.create_unicode_buffer(size.value) - if GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, ctypes.byref(num), ctypes.byref(buf), ctypes.byref(size)): - return wszarray_to_list(buf) + if GetUserPreferredUILanguages( + MUI_LANGUAGE_NAME, ctypes.byref(num), ctypes.byref(buf), ctypes.byref(size) + ): + languages = wszarray_to_list(buf) - return [] + return languages # singletons From 72fc78048fb7e4fefc76d0bf26eb6eb1bf95aefb Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 11 Dec 2021 10:59:43 +0000 Subject: [PATCH 027/186] l10n: Change zh-CN to zh-Hans in preferred_languages(). --- l10n.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/l10n.py b/l10n.py index b1ff2672..2771f46a 100755 --- a/l10n.py +++ b/l10n.py @@ -354,6 +354,10 @@ class _Locale: ): languages = wszarray_to_list(buf) + # OneSky calls "Chinese Simplified" "zh-Hans" in the name of the file, + # but that will be zh-CN in terms of locale. So map zh-CN -> zh-Hans + languages = ['zh-Hans' if lang == 'zh-CN' else lang for lang in languages] + return languages From fc3a7b32a3d6e1d9327b5d9747973df383c1b480 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 11 Dec 2021 14:15:30 +0000 Subject: [PATCH 028/186] l10n: Make the zh-CN -> zh-Hans comment a HACK --- l10n.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/l10n.py b/l10n.py index 2771f46a..b77c07de 100755 --- a/l10n.py +++ b/l10n.py @@ -354,8 +354,9 @@ class _Locale: ): languages = wszarray_to_list(buf) - # OneSky calls "Chinese Simplified" "zh-Hans" in the name of the file, - # but that will be zh-CN in terms of locale. So map zh-CN -> zh-Hans + # HACK: | 2021-12-11: OneSky calls "Chinese Simplified" "zh-Hans" + # in the name of the file, but that will be zh-CN in terms of + # locale. So map zh-CN -> zh-Hans languages = ['zh-Hans' if lang == 'zh-CN' else lang for lang in languages] return languages From ea0fa7c77ddfce1b25a9e397af2eacf1cf4bcb0c Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 16 Dec 2021 13:06:09 +0000 Subject: [PATCH 029/186] develop: Bump appversion to 5.3.0-beta3 --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index ff0b7fed..14a79651 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.3.0-beta2' +_static_appversion = '5.3.0-beta3' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD' From 8bae11c84b798a6b1d5680dff75a37206d10bd9c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Dec 2021 17:03:58 +0000 Subject: [PATCH 030/186] build(deps-dev): bump lxml from 4.6.4 to 4.7.1 Bumps [lxml](https://github.com/lxml/lxml) from 4.6.4 to 4.7.1. - [Release notes](https://github.com/lxml/lxml/releases) - [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt) - [Commits](https://github.com/lxml/lxml/compare/lxml-4.6.4...lxml-4.7.1) --- updated-dependencies: - dependency-name: lxml dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 221b5997..a0931b9c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -28,7 +28,7 @@ grip==4.5.2 # Packaging # Used to put together a WiX configuration from template/auto-gen -lxml==4.6.4 +lxml==4.7.1 # We only need py2exe on windows. py2exe==0.11.0.1; sys_platform == 'win32' From becdd8ce83e0c96b4d4cfa46567fed483e37a586 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Dec 2021 17:04:07 +0000 Subject: [PATCH 031/186] build(deps-dev): bump mypy from 0.910 to 0.920 Bumps [mypy](https://github.com/python/mypy) from 0.910 to 0.920. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.910...v0.920) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 221b5997..7da3ad36 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,7 +15,7 @@ flake8-noqa==1.2.1 flake8-polyfill==1.0.2 flake8-use-fstring==1.3 -mypy==0.910 +mypy==0.920 pep8-naming==0.12.1 safety==1.10.3 types-requests==2.26.1 From 096b41fa844a82d2011610029f0e0e4cffad587e Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 16 Dec 2021 17:34:46 +0000 Subject: [PATCH 032/186] fdevids: Add FDevIDs repo as a submodule --- .gitmodules | 3 +++ FDevIDs | 1 + 2 files changed, 4 insertions(+) create mode 160000 FDevIDs diff --git a/.gitmodules b/.gitmodules index 134d60e0..b844f49e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "coriolis-data"] path = coriolis-data url = git@github.com:EDCD/coriolis-data.git +[submodule "FDevIDs"] + path = FDevIDs + url = https://github.com/EDCD/FDevIDs.git diff --git a/FDevIDs b/FDevIDs new file mode 160000 index 00000000..f4677fff --- /dev/null +++ b/FDevIDs @@ -0,0 +1 @@ +Subproject commit f4677fffe618439e4111b97154c4592fe4f360e9 From bcabd97f4cb623d5f5b375482ae59821168e552d Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 17 Dec 2021 11:08:14 +0000 Subject: [PATCH 033/186] FDevIDs: Switch code to using FDevIDs/ files --- collate.py | 3 ++- companion.py | 3 +-- setup.py | 10 ++++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/collate.py b/collate.py index a47b4a45..5ba0731a 100755 --- a/collate.py +++ b/collate.py @@ -4,6 +4,7 @@ import csv import json import os +import pathlib import sys from os.path import isfile from traceback import print_exc @@ -38,7 +39,7 @@ def addcommodities(data) -> None: # noqa: CCR001 if not data['lastStarport'].get('commodities'): return - commodityfile = 'commodity.csv' + commodityfile = pathlib.Path('FDevIDs/commodity.csv') commodities = {} # slurp existing diff --git a/companion.py b/companion.py index 9a175a1d..91b306e3 100644 --- a/companion.py +++ b/companion.py @@ -22,7 +22,6 @@ import urllib.parse import webbrowser from builtins import object, range, str from email.utils import parsedate -from os.path import join from queue import Queue from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, OrderedDict, TypeVar, Union @@ -1065,7 +1064,7 @@ def fixup(data: CAPIData) -> CAPIData: # noqa: C901, CCR001 # Can't be usefully if not commodity_map: # Lazily populate for f in ('commodity.csv', 'rare_commodity.csv'): - with open(join(config.respath_path, f), 'r') as csvfile: + with open(config.respath_path / 'FDevIDs' / f, 'r') as csvfile: reader = csv.DictReader(csvfile) for row in reader: diff --git a/setup.py b/setup.py index 8bb8fb30..4a9ed68d 100755 --- a/setup.py +++ b/setup.py @@ -144,8 +144,10 @@ if sys.platform == 'darwin': ('plugins', x) for x in PLUGINS ], 'resources': [ - 'commodity.csv', - 'rare_commodity.csv', + '.gitversion', # Contains git short hash + 'ChangeLog.md', + join('FDevIDs', 'commodity.csv'), + join('FDevIDs', 'rare_commodity.csv'), 'snd_good.wav', 'snd_bad.wav', 'modules.p', @@ -213,8 +215,8 @@ elif sys.platform == 'win32': 'WinSparkle.pdb', # For debugging - don't include in package 'EUROCAPS.TTF', 'ChangeLog.md', - 'commodity.csv', - 'rare_commodity.csv', + join('FDevIDs', 'commodity.csv'), + join('FDevIDs', 'rare_commodity.csv'), 'snd_good.wav', 'snd_bad.wav', 'modules.p', From 33844126329c3c95c26a87fa495e75953ba79c07 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 17 Dec 2021 11:12:55 +0000 Subject: [PATCH 034/186] collapre.py: flake8/mypy fixups --- collate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/collate.py b/collate.py index 5ba0731a..0350485f 100755 --- a/collate.py +++ b/collate.py @@ -14,14 +14,14 @@ import outfitting from edmc_data import companion_category_map, ship_name_map -def __make_backup(file_name: str, suffix: str = '.bak') -> None: +def __make_backup(file_name: pathlib.Path, suffix: str = '.bak') -> None: """ Rename the given file to $file.bak, removing any existing $file.bak. Assumes $file exists on disk. :param file_name: The name of the file to make a backup of :param suffix: The suffix to use for backup files (default '.bak') """ - backup_name = file_name + suffix + backup_name = file_name.parent / (file_name.name + suffix) if isfile(backup_name): os.unlink(backup_name) @@ -150,7 +150,7 @@ def addships(data) -> None: # noqa: CCR001 if not data['lastStarport'].get('ships'): return - shipfile = 'shipyard.csv' + shipfile = pathlib.Path('shipyard.csv') ships = {} fields = ('id', 'symbol', 'name') From 1d3ff006cd5bc5e2b04734676527b2621e852de7 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 17 Dec 2021 11:35:00 +0000 Subject: [PATCH 035/186] collate.py: Correction so it works with dump/ files And adjust the dile docstring to point out it will only work with the dump/ file format, not the new 'File > Save Raw Data' one. --- collate.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/collate.py b/collate.py index 0350485f..d789a6c5 100755 --- a/collate.py +++ b/collate.py @@ -1,5 +1,11 @@ #!/usr/bin/env python3 -"""Collate lists of seen commodities, modules and ships from dumps of the Companion API output.""" +""" +Collate lists of seen commodities, modules and ships from dumps of the Companion API output. + +Note that currently this will only work with the output files created if you +run the main program from a working directory that has a `dump/` directory, +which causes a file to be written per CAPI query. +""" import csv import json @@ -210,6 +216,7 @@ if __name__ == "__main__": with open(file_name) as f: print(file_name) data = json.load(f) + data = data['data'] if not data['commander'].get('docked'): print('Not docked!') From 77e89a9df15ea76d02e728781ea64f4058352627 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 17 Dec 2021 12:00:49 +0000 Subject: [PATCH 036/186] setup.py: Properly include FDevIDs files I have my doubts about the darwin section, but we never package for it any more, so I'm not going to worry about it now. --- setup.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 4a9ed68d..f73dc370 100755 --- a/setup.py +++ b/setup.py @@ -146,12 +146,14 @@ if sys.platform == 'darwin': 'resources': [ '.gitversion', # Contains git short hash 'ChangeLog.md', - join('FDevIDs', 'commodity.csv'), - join('FDevIDs', 'rare_commodity.csv'), 'snd_good.wav', 'snd_bad.wav', 'modules.p', 'ships.p', + ('FDevIDs', [ + join('FDevIDs', 'commodity.csv'), + join('FDevIDs', 'rare_commodity.csv'), + ]), ], 'site_packages': False, 'plist': { @@ -215,8 +217,6 @@ elif sys.platform == 'win32': 'WinSparkle.pdb', # For debugging - don't include in package 'EUROCAPS.TTF', 'ChangeLog.md', - join('FDevIDs', 'commodity.csv'), - join('FDevIDs', 'rare_commodity.csv'), 'snd_good.wav', 'snd_bad.wav', 'modules.p', @@ -228,6 +228,10 @@ elif sys.platform == 'win32': 'EDMarketConnector - reset-ui.bat', ]), ('L10n', [join('L10n', x) for x in os.listdir('L10n') if x.endswith('.strings')]), + ('FDevIDs', [ + join('FDevIDs', 'commodity.csv'), + join('FDevIDs', 'rare_commodity.csv'), + ]), ('plugins', PLUGINS), ] From c45b99a1804270540c2111a4a510a12ed56d4f28 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 17 Dec 2021 12:17:07 +0000 Subject: [PATCH 037/186] Remove local copies of (rare_)commodity.csv in favour of FDevIDs versions NB: Keeping the local outfitting.csv for now due to how it's used (both input and output in collate.py, input in coriolis.py). --- commodity.csv | 220 --------------------------------------------- rare_commodity.csv | 139 ---------------------------- 2 files changed, 359 deletions(-) delete mode 100644 commodity.csv delete mode 100644 rare_commodity.csv diff --git a/commodity.csv b/commodity.csv deleted file mode 100644 index eb7084a3..00000000 --- a/commodity.csv +++ /dev/null @@ -1,220 +0,0 @@ -id,symbol,category,name -128049152,Platinum,Metals,Platinum -128049153,Palladium,Metals,Palladium -128049154,Gold,Metals,Gold -128049155,Silver,Metals,Silver -128049156,Bertrandite,Minerals,Bertrandite -128049157,Indite,Minerals,Indite -128049158,Gallite,Minerals,Gallite -128049159,Coltan,Minerals,Coltan -128049160,Uraninite,Minerals,Uraninite -128049161,Lepidolite,Minerals,Lepidolite -128049162,Cobalt,Metals,Cobalt -128049163,Rutile,Minerals,Rutile -128049165,Bauxite,Minerals,Bauxite -128049166,Water,Chemicals,Water -128049168,Beryllium,Metals,Beryllium -128049169,Indium,Metals,Indium -128049170,Gallium,Metals,Gallium -128049171,Tantalum,Metals,Tantalum -128049172,Uranium,Metals,Uranium -128049173,Lithium,Metals,Lithium -128049174,Titanium,Metals,Titanium -128049175,Copper,Metals,Copper -128049176,Aluminium,Metals,Aluminium -128049177,Algae,Foods,Algae -128049178,FruitAndVegetables,Foods,Fruit and Vegetables -128049180,Grain,Foods,Grain -128049182,Animalmeat,Foods,Animal Meat -128049183,Fish,Foods,Fish -128049184,FoodCartridges,Foods,Food Cartridges -128049185,SyntheticMeat,Foods,Synthetic Meat -128049188,Tea,Foods,Tea -128049189,Coffee,Foods,Coffee -128049190,Leather,Textiles,Leather -128049191,NaturalFabrics,Textiles,Natural Fabrics -128049193,SyntheticFabrics,Textiles,Synthetic Fabrics -128049197,Polymers,Industrial Materials,Polymers -128049199,Semiconductors,Industrial Materials,Semiconductors -128049200,Superconductors,Industrial Materials,Superconductors -128049202,HydrogenFuel,Chemicals,Hydrogen Fuel -128049203,MineralOil,Chemicals,Mineral Oil -128049204,Explosives,Chemicals,Explosives -128049205,Pesticides,Chemicals,Pesticides -128049208,AgriculturalMedicines,Medicines,Agri-Medicines -128049209,PerformanceEnhancers,Medicines,Performance Enhancers -128049210,BasicMedicines,Medicines,Basic Medicines -128049212,BasicNarcotics,Legal Drugs,Narcotics -128049213,Tobacco,Legal Drugs,Tobacco -128049214,Beer,Legal Drugs,Beer -128049215,Wine,Legal Drugs,Wine -128049216,Liquor,Legal Drugs,Liquor -128049217,PowerGenerators,Machinery,Power Generators -128049218,WaterPurifiers,Machinery,Water Purifiers -128049220,HeliostaticFurnaces,Machinery,Microbial Furnaces -128049221,MineralExtractors,Machinery,Mineral Extractors -128049222,CropHarvesters,Machinery,Crop Harvesters -128049223,MarineSupplies,Machinery,Marine Equipment -128049225,ComputerComponents,Technology,Computer Components -128049226,HazardousEnvironmentSuits,Technology,H.E. Suits -128049227,Robotics,Technology,Robotics -128049228,AutoFabricators,Technology,Auto-Fabricators -128049229,AnimalMonitors,Technology,Animal Monitors -128049230,AquaponicSystems,Technology,Aquaponic Systems -128049231,AdvancedCatalysers,Technology,Advanced Catalysers -128049232,TerrainEnrichmentSystems,Technology,Land Enrichment Systems -128049233,PersonalWeapons,Weapons,Personal Weapons -128049234,BattleWeapons,Weapons,Battle Weapons -128049235,ReactiveArmour,Weapons,Reactive Armour -128049236,NonLethalWeapons,Weapons,Non-Lethal Weapons -128049238,DomesticAppliances,Consumer Items,Domestic Appliances -128049240,ConsumerTechnology,Consumer Items,Consumer Technology -128049241,Clothing,Consumer Items,Clothing -128049243,Slaves,Slavery,Slaves -128049244,Biowaste,Waste,Biowaste -128049245,ToxicWaste,Waste,Toxic Waste -128049246,ChemicalWaste,Waste,Chemical Waste -128049248,Scrap,Waste,Scrap -128049669,ProgenitorCells,Medicines,Progenitor Cells -128049670,CombatStabilisers,Medicines,Combat Stabilisers -128049671,ResonatingSeparators,Technology,Resonating Separators -128049672,BioReducingLichen,Technology,Bioreducing Lichen -128064028,AtmosphericExtractors,Machinery,Atmospheric Processors -128066403,Drones,NonMarketable,Limpets -128666752,USSCargoBlackBox,Salvage,Black Box -128666754,USSCargoTradeData,Salvage,Trade Data -128666755,USSCargoMilitaryPlans,Salvage,Military Plans -128666756,USSCargoAncientArtefact,Salvage,Ancient Artefact -128666757,USSCargoRareArtwork,Salvage,Rare Artwork -128666758,USSCargoExperimentalChemicals,Salvage,Experimental Chemicals -128666759,USSCargoRebelTransmissions,Salvage,Rebel Transmissions -128666760,USSCargoPrototypeTech,Salvage,Prototype Tech -128666761,USSCargoTechnicalBlueprints,Salvage,Technical Blueprints -128667728,ImperialSlaves,Slavery,Imperial Slaves -128668547,UnknownArtifact,Salvage,Thargoid Sensor -128668548,AiRelics,Salvage,AI Relics -128668549,Hafnium178,Metals,Hafnium 178 -128668550,Painite,Minerals,Painite -128668551,Antiquities,Salvage,Antiquities -128668552,MilitaryIntelligence,Salvage,Military Intelligence -128671118,Osmium,Metals,Osmium -128671443,SAP8CoreContainer,Salvage,SAP 8 Core Container -128671444,TrinketsOfFortune,Consumer Items,Trinkets of Hidden Fortune -128672123,WreckageComponents,Salvage,Wreckage Components -128672124,EncriptedDataStorage,Salvage,Encrypted Data Storage -128672125,OccupiedCryoPod,Salvage,Occupied Escape Pod -128672126,PersonalEffects,Salvage,Personal Effects -128672127,ComercialSamples,Salvage,Commercial Samples -128672128,TacticalData,Salvage,Tactical Data -128672129,AssaultPlans,Salvage,Assault Plans -128672130,EncryptedCorrespondence,Salvage,Encrypted Correspondence -128672131,DiplomaticBag,Salvage,Diplomatic Bag -128672132,ScientificResearch,Salvage,Scientific Research -128672133,ScientificSamples,Salvage,Scientific Samples -128672134,PoliticalPrisoner,Salvage,Political Prisoners -128672135,Hostage,Salvage,Hostages -128672136,LargeExplorationDataCash,Salvage,Large Survey Data Cache -128672137,SmallExplorationDataCash,Salvage,Small Survey Data Cache -128672159,AntiqueJewellery,Salvage,Antique Jewellery -128672160,PreciousGems,Salvage,Precious Gems -128672161,EarthRelics,Salvage,Earth Relics -128672162,GeneBank,Salvage,Gene Bank -128672163,TimeCapsule,Salvage,Time Capsule -128672294,Cryolite,Minerals,Cryolite -128672295,Goslarite,Minerals,Goslarite -128672296,Moissanite,Minerals,Moissanite -128672297,Pyrophyllite,Minerals,Pyrophyllite -128672298,Lanthanum,Metals,Lanthanum -128672299,Thallium,Metals,Thallium -128672300,Bismuth,Metals,Bismuth -128672301,Thorium,Metals,Thorium -128672302,CeramicComposites,Industrial Materials,Ceramic Composites -128672303,SyntheticReagents,Chemicals,Synthetic Reagents -128672304,NerveAgents,Chemicals,Nerve Agents -128672305,SurfaceStabilisers,Chemicals,Surface Stabilisers -128672306,BootlegLiquor,Legal Drugs,Bootleg Liquor -128672307,GeologicalEquipment,Machinery,Geological Equipment -128672308,ThermalCoolingUnits,Machinery,Thermal Cooling Units -128672309,BuildingFabricators,Machinery,Building Fabricators -128672310,MuTomImager,Technology,Muon Imager -128672311,StructuralRegulators,Technology,Structural Regulators -128672312,Landmines,Weapons,Landmines -128672313,SkimerComponents,Machinery,Skimmer Components -128672314,EvacuationShelter,Consumer Items,Evacuation Shelter -128672315,GeologicalSamples,Salvage,Geological Samples -128672701,MetaAlloys,Industrial Materials,Meta-Alloys -128672775,Taaffeite,Minerals,Taaffeite -128672776,Jadeite,Minerals,Jadeite -128672810,UnstableDataCore,Salvage,Unstable Data Core -128672811,DamagedEscapePod,Salvage,Damaged Escape Pod -128673845,Praseodymium,Metals,Praseodymium -128673846,Bromellite,Minerals,Bromellite -128673847,Samarium,Metals,Samarium -128673848,LowTemperatureDiamond,Minerals,Low Temperature Diamonds -128673850,HydrogenPeroxide,Chemicals,Hydrogen Peroxide -128673851,LiquidOxygen,Chemicals,Liquid oxygen -128673852,MethanolMonohydrateCrystals,Minerals,Methanol Monohydrate Crystals -128673853,LithiumHydroxide,Minerals,Lithium Hydroxide -128673854,MethaneClathrate,Minerals,Methane Clathrate -128673855,InsulatingMembrane,Industrial Materials,Insulating Membrane -128673856,CMMComposite,Industrial Materials,CMM Composite -128673857,CoolingHoses,Industrial Materials,Micro-weave Cooling Hoses -128673858,NeofabricInsulation,Industrial Materials,Neofabric Insulation -128673859,ArticulationMotors,Machinery,Articulation Motors -128673860,HNShockMount,Machinery,HN Shock Mount -128673861,EmergencyPowerCells,Machinery,Emergency Power Cells -128673862,PowerConverter,Machinery,Power Converter -128673863,PowerGridAssembly,Machinery,Energy Grid Assembly -128673864,PowerTransferConduits,Machinery,Power Transfer Bus -128673865,RadiationBaffle,Machinery,Radiation Baffle -128673866,ExhaustManifold,Machinery,Exhaust Manifold -128673867,ReinforcedMountingPlate,Machinery,Reinforced Mounting Plate -128673868,HeatsinkInterlink,Machinery,Heatsink Interlink -128673869,MagneticEmitterCoil,Machinery,Magnetic Emitter Coil -128673870,ModularTerminals,Machinery,Modular Terminals -128673871,Nanobreakers,Technology,Nanobreakers -128673872,TelemetrySuite,Technology,Telemetry Suite -128673873,MicroControllers,Technology,Micro Controllers -128673874,IonDistributor,Machinery,Ion Distributor -128673875,DiagnosticSensor,Technology,Hardware Diagnostic Sensor -128673876,UnknownArtifact2,Salvage,Thargoid Probe -128682044,ConductiveFabrics,Textiles,Conductive Fabrics -128682045,MilitaryGradeFabrics,Textiles,Military Grade Fabrics -128682046,AdvancedMedicines,Medicines,Advanced Medicines -128682047,MedicalDiagnosticEquipment,Technology,Medical Diagnostic Equipment -128682048,SurvivalEquipment,Consumer Items,Survival Equipment -128682049,DataCore,Salvage,Data Core -128682051,MysteriousIdol,Salvage,Mysterious Idol -128682052,ProhibitedResearchMaterials,Salvage,Prohibited Research Materials -128682053,AntimatterContainmentUnit,Salvage,Antimatter Containment Unit -128682054,SpacePioneerRelics,Salvage,Space Pioneer Relics -128682055,FossilRemnants,Salvage,Fossil Remnants -128732183,AncientRelic,Salvage,Ancient Relic -128732184,AncientOrb,Salvage,Ancient Orb -128732185,AncientCasket,Salvage,Ancient Casket -128732186,AncientTablet,Salvage,Ancient Tablet -128732187,AncientUrn,Salvage,Ancient Urn -128732188,AncientTotem,Salvage,Ancient Totem -128737287,UnknownResin,Salvage,Thargoid Resin -128737288,UnknownBiologicalMatter,Salvage,Thargoid Biological Matter -128737289,UnknownTechnologySamples,Salvage,Thargoid Technology Samples -128740752,UnknownArtifact3,Salvage,Thargoid Link -128793127,ThargoidHeart,Salvage,Thargoid Heart -128793128,ThargoidTissueSampleType1,Salvage,Thargoid Cyclops Tissue Sample -128793129,ThargoidTissueSampleType2,Salvage,Thargoid Basilisk Tissue Sample -128793130,ThargoidTissueSampleType3,Salvage,Thargoid Medusa Tissue Sample -128824468,ThargoidScoutTissueSample,Salvage,Thargoid Scout Tissue Sample -128888499,AncientKey,Salvage,Ancient Key -128902652,ThargoidTissueSampleType4,Salvage,Thargoid Hydra Tissue Sample -128913661,Nanomedicines,Medicines,Nanomedicines -128922524,Duradrives,Consumer Items,Duradrives -128924325,Rhodplumsite,Minerals,Rhodplumsite -128924326,Serendibite,Minerals,Serendibite -128924327,Monazite,Minerals,Monazite -128924328,Musgravite,Minerals,Musgravite -128924329,Benitoite,Minerals,Benitoite -128924330,Grandidierite,Minerals,Grandidierite -128924331,Alexandrite,Minerals,Alexandrite -128924332,Opal,Minerals,Void Opal -128924333,RockforthFertiliser,Chemicals,Rockforth Fertiliser -128924334,AgronomicTreatment,Chemicals,Agronomic Treatment diff --git a/rare_commodity.csv b/rare_commodity.csv deleted file mode 100644 index 67b95dbd..00000000 --- a/rare_commodity.csv +++ /dev/null @@ -1,139 +0,0 @@ -id,symbol,market_id,category,name -128666746,EraninPearlWhisky,128001536,Legal Drugs,Eranin Pearl Whisky -128666747,LavianBrandy,128106744,Legal Drugs,Lavian Brandy -128667019,HIP10175BushMeat,3223234816,Foods,HIP 10175 Bush Meat -128667020,AlbinoQuechuaMammoth,3222822912,Foods,Albino Quechua Mammoth Meat -128667021,UtgaroarMillenialEggs,128037120,Foods,Utgaroar Millennial Eggs -128667022,WitchhaulKobeBeef,3223358720,Foods,Witchhaul Kobe Beef -128667023,KarsukiLocusts,3225028096,Foods,Karsuki Locusts -128667024,GiantIrukamaSnails,3225345792,Foods,Giant Irukama Snails -128667025,BaltahSineVacuumKrill,128088056,Foods,Baltah'sine Vacuum Krill -128667026,CetiRabbits,3222560000,Foods,Ceti Rabbits -128667027,KachiriginLeaches,3221595648,Medicines,Kachirigin Filter Leeches -128667028,LyraeWeed,3226417152,Legal Drugs,Lyrae Weed -128667029,OnionHead,128129272,Legal Drugs,Onionhead -128667030,TarachTorSpice,128041984,Legal Drugs,Tarach Spice -128667031,Wolf1301Fesh,128084984,Legal Drugs,Wolf Fesh -128667032,BorasetaniPathogenetics,3229638400,Weapons,Borasetani Pathogenetics -128667033,HIP118311Swarm,3223177472,Weapons,HIP 118311 Swarm -128667034,KonggaAle,3226978048,Legal Drugs,Kongga Ale -128667035,WuthieloKuFroth,3222155776,Legal Drugs,Wuthielo Ku Froth -128667036,AlacarakmoSkinArt,3231373824,Consumer Items,Alacarakmo Skin Art -128667037,EleuThermals,3230624768,Consumer Items,Eleu Thermals -128667038,EshuUmbrellas,3222295552,Consumer Items,Eshu Umbrellas -128667039,KaretiiCouture,3227333120,Consumer Items,Karetii Couture -128667040,NjangariSaddles,3222416896,Consumer Items,Njangari Saddles -128667041,AnyNaCoffee,3229880064,Foods,Any Na Coffee -128667042,CD75CatCoffee,3228566016,Foods,CD-75 Kitten Brand Coffee -128667043,GomanYauponCoffee,3224449792,Foods,Goman Yaupon Coffee -128667044,VolkhabBeeDrones,3227831808,Machinery,Volkhab Bee Drones -128667045,KinagoInstruments,3227394304,Consumer Items,Kinago Violins -128667046,NgunaModernAntiques,3221538304,Consumer Items,Nguna Modern Antiques -128667047,RajukruStoves,3227512320,Consumer Items,Rajukru Multi-Stoves -128667048,TiolceWaste2PasteUnits,3224141312,Consumer Items,Tiolce Waste2Paste Units -128667049,ChiEridaniMarinePaste,128128760,Foods,Chi Eridani Marine Paste -128667050,EsusekuCaviar,3226919680,Foods,Esuseku Caviar -128667051,LiveHecateSeaWorms,128042496,Foods,Live Hecate Sea Worms -128667052,HelvetitjPearls,3231094528,Metals,Helvetitj Pearls -128667053,HIP41181Squid,3227995392,Foods,HIP Proto-Squid -128667054,CoquimSpongiformVictuals,3223832576,Foods,Coquim Spongiform Victuals -128667055,AerialEdenApple,128083448,Foods,Eden Apples of Aerial -128667056,NeritusBerries,3228206080,Foods,Neritus Berries -128667057,OchoengChillies,3226719232,Foods,Ochoeng Chillies -128667058,DeuringasTruffles,3229713408,Foods,Deuringas Truffles -128667059,HR7221Wheat,3226170880,Foods,HR 7221 Wheat -128667060,JarouaRice,3224698112,Foods,Jaroua Rice -128667061,BelalansRayLeather,3223537152,Textiles,Belalans Ray Leather -128667062,DamnaCarapaces,3227751936,Textiles,Damna Carapaces -128667063,RapaBaoSnakeSkins,3222875648,Textiles,Rapa Bao Snake Skins -128667064,VanayequiRhinoFur,3227289856,Textiles,Vanayequi Ceratomorpha Fur -128667065,BastSnakeGin,128086776,Legal Drugs,Bast Snake Gin -128667066,ThrutisCream,3226522368,Legal Drugs,Thrutis Cream -128667067,WulpaHyperboreSystems,3221388032,Machinery,Wulpa Hyperbore Systems -128667068,AganippeRush,128012800,Medicines,Aganippe Rush -128667069,TerraMaterBloodBores,128051466,Medicines,Terra Mater Blood Bores -128667070,HolvaDuellingBlades,3222713088,Weapons,Holva Duelling Blades -128667071,KamorinHistoricWeapons,3221669632,Weapons,Kamorin Historic Weapons -128667072,GilyaSignatureWeapons,3226857216,Weapons,Gilya Signature Weapons -128667073,DeltaPhoenicisPalms,128045312,Chemicals,Delta Phoenicis Palms -128667074,ToxandjiVirocide,3230258688,Chemicals,Toxandji Virocide -128667075,XiheCompanions,3224133120,Technology,Xihe Biomorphic Companions -128667076,SanumaMEAT,3230331136,Foods,Sanuma Decorative Meat -128667077,EthgrezeTeaBuds,3229524992,Foods,Ethgreze Tea Buds -128667078,CeremonialHeikeTea,3227417856,Foods,Ceremonial Heike Tea -128667079,TanmarkTranquilTea,128057866,Foods,Tanmark Tranquil Tea -128667080,AZCancriFormula42,3228400128,Technology,Az Cancri Formula 42 -128667081,KamitraCigars,3225450752,Legal Drugs,Kamitra Cigars -128667082,RusaniOldSmokey,3229255680,Legal Drugs,Rusani Old Smokey -128667083,YasoKondiLeaf,3223088640,Legal Drugs,Yaso Kondi Leaf -128667084,ChateauDeAegaeon,3228416768,Legal Drugs,Chateau De Aegaeon -128667085,WatersOfShintara,128666762,Medicines,The Waters of Shintara -128667668,OphiuchiExinoArtefacts,3228939264,Consumer Items,Ophiuch Exino Artefacts -128667669,BakedGreebles,3229378560,Foods,Baked Greebles -128667670,CetiAepyornisEgg,3222560256,Foods,Aepyornis Egg -128667671,SaxonWine,3227986432,Legal Drugs,Saxon Wine -128667672,CentauriMegaGin,3228728832,Legal Drugs,Centauri Mega Gin -128667673,AnduligaFireWorks,3230243584,Consumer Items,Anduliga Fire Works -128667674,BankiAmphibiousLeather,3228346112,Textiles,Banki Amphibious Leather -128667675,CherbonesBloodCrystals,3229594624,Metals,Cherbones Blood Crystals -128667676,MotronaExperienceJelly,3229750528,Legal Drugs,Motrona Experience Jelly -128667677,GeawenDanceDust,3230954752,Legal Drugs,Geawen Dance Dust -128667678,GerasianGueuzeBeer,3228047360,Legal Drugs,Gerasian Gueuze Beer -128667679,HaidneBlackBrew,3226557696,Foods,Haiden Black Brew -128667680,HavasupaiDreamCatcher,3221438976,Consumer Items,Havasupai Dream Catcher -128667681,BurnhamBileDistillate,3230224384,Legal Drugs,Burnham Bile Distillate -128667682,HIPOrganophosphates,3227036160,Chemicals,HIP Organophosphates -128667683,JaradharrePuzzlebox,3230754816,Consumer Items,Jaradharre Puzzle Box -128667684,KorroKungPellets,3228726272,Chemicals,Koro Kung Pellets -128667685,LFTVoidExtractCoffee,3229028864,Foods,Void Extract Coffee -128667686,HonestyPills,3229561344,Medicines,Honesty Pills -128667687,NonEuclidianExotanks,3224135424,Machinery,Non Euclidian Exotanks -128667688,LTTHyperSweet,3224166400,Foods,LTT Hyper Sweet -128667689,MechucosHighTea,3228398848,Foods,Mechucos High Tea -128667690,MedbStarlube,3228762368,Chemicals,Medb Starlube -128667691,MokojingBeastFeast,3229612800,Foods,Mokojing Beast Feast -128667692,MukusubiiChitinOs,3221719296,Foods,Mukusubii Chitin-os -128667693,MulachiGiantFungus,3228892672,Foods,Mulachi Giant Fungus -128667694,NgadandariFireOpals,3226127872,Metals,Ngadandari Fire Opals -128667695,TiegfriesSynthSilk,3227726848,Textiles,Tiegfries Synth Silk -128667696,UzumokuLowGWings,3226474496,Consumer Items,Uzumoku Low-G Wings -128667697,VHerculisBodyRub,3228959232,Medicines,V Herculis Body Rub -128667698,WheemeteWheatCakes,3225032704,Foods,Wheemete Wheat Cakes -128667699,VegaSlimWeed,128149240,Medicines,Vega Slimweed -128667700,AltairianSkin,128151032,Consumer Items,Altairian Skin -128667701,PavonisEarGrubs,128117240,Legal Drugs,Pavonis Ear Grubs -128667702,JotunMookah,128078840,Textiles,Jotun Mookah -128667703,GiantVerrix,128121336,Machinery,Giant Verrix -128667704,IndiBourbon,128118520,Legal Drugs,Indi Bourbon -128667705,AroucaConventualSweets,128098040,Foods,Arouca Conventual Sweets -128667706,TauriChimes,128134648,Consumer Items,Tauri Chimes -128667707,ZeesszeAntGlue,128125432,Consumer Items,Zeessze Ant Grub Glue -128667708,PantaaPrayerSticks,3228824064,Medicines,Pantaa Prayer Sticks -128667709,FujinTea,128134392,Foods,Fujin Tea -128667710,ChameleonCloth,3223418880,Textiles,Chameleon Cloth -128667711,OrrerianViciousBrew,128166392,Foods,Orrerian Vicious Brew -128667712,UszaianTreeGrub,128164856,Foods,Uszaian Tree Grub -128667713,MomusBogSpaniel,128075256,Consumer Items,Momus Bog Spaniel -128667714,DisoMaCorn,128161016,Foods,Diso Ma Corn -128667715,LeestianEvilJuice,128639992,Legal Drugs,Leestian Evil Juice -128667716,BlueMilk,128639992,Foods,Azure Milk -128667717,AlienEggs,128164088,Consumer Items,Leathery Eggs -128667718,AlyaBodilySoap,3221638400,Medicines,Alya Body Soap -128667719,VidavantianLace,3231082240,Consumer Items,Vidavantian Lace -128667760,TransgenicOnionHead,128057866,Legal Drugs,Lucan Onionhead -128668017,JaquesQuinentianStill,128667761,Consumer Items,Jaques Quinentian Still -128668018,SoontillRelics,3225348096,Consumer Items,Soontill Relics -128671119,Advert1,3227172352,Consumer Items,Ultra-Compact Processor Prototypes -128672121,TheHuttonMug,3228728832,Consumer Items,The Hutton Mug -128672122,SothisCrystallineGold,128668557,Metals,Sothis Crystalline Gold -128672316,MasterChefs,3228193280,Slavery,Master Chefs -128672431,PersonalGifts,3223105792,Salvage,Personal Gifts -128672432,CrystallineSpheres,128059402,Salvage,Crystalline Spheres -128672812,OnionHeadA,3226977024,Legal Drugs,Onionhead Alpha Strain -128673069,OnionHeadB,3223027200,Legal Drugs,Onionhead Beta Strain -128682050,GalacticTravelGuide,128673074,Salvage,Galactic Travel Guide -128727921,AnimalEffigies,3228463360,Legal Drugs,Crom Silver Fesh -128732551,ShansCharisOrchid,128107768,Consumer Items,Shan's Charis Orchid -128748428,BuckyballBeerMats,128745551,Consumer Items,Buckyball Beer Mats -128793113,HarmaSilverSeaRum,3221575424,Legal Drugs,Harma Silver Sea Rum -128793114,PlatinumAloy,3223779840,Metals,Platinum Alloy From da4ae24440c7815492a6f59d7416725dea0cee37 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 17 Dec 2021 12:23:20 +0000 Subject: [PATCH 038/186] Document use of *local* outfitting.csv instead of FDevIDs version That's in collate.py and coriolis.py docstrings. --- collate.py | 5 +++++ coriolis.py | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/collate.py b/collate.py index d789a6c5..3ab5224b 100755 --- a/collate.py +++ b/collate.py @@ -5,6 +5,11 @@ Collate lists of seen commodities, modules and ships from dumps of the Companion Note that currently this will only work with the output files created if you run the main program from a working directory that has a `dump/` directory, which causes a file to be written per CAPI query. + +This script also utilise the file outfitting.csv. As it both reads it in *and* +writes out a new copy a local copy, in the root of the project structure, is +used for this purpose. If you want to utilise the FDevIDs/ version of the +file, copy it over the local one. """ import csv diff --git a/coriolis.py b/coriolis.py index 5ce76a29..0afb16e5 100755 --- a/coriolis.py +++ b/coriolis.py @@ -1,5 +1,12 @@ #!/usr/bin/env python3 -"""Build ship and module databases from https://github.com/EDCD/coriolis-data/ .""" +""" +Build ship and module databases from https://github.com/EDCD/coriolis-data/ . + +This script also utilise the file outfitting.csv. Due to how collate.py +both reads and writes to this file a local copy is used, in the root of the +project structure, is used for this purpose. If you want to utilise the +FDevIDs/ version of the file, copy it over the local one. +""" import csv From 36c0bc10fddafef4ce4d63564fcd6495cc329134 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Dec 2021 17:03:37 +0000 Subject: [PATCH 039/186] build(deps-dev): bump types-requests from 2.26.1 to 2.26.2 Bumps [types-requests](https://github.com/python/typeshed) from 2.26.1 to 2.26.2. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a071b9ce..dd5b03be 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.3 mypy==0.920 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.26.1 +types-requests==2.26.2 # Code formatting tools autopep8==1.6.0 From b74bf63076c68c1dd23b00d7b45746a3ff2ab0f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Dec 2021 17:04:26 +0000 Subject: [PATCH 040/186] build(deps-dev): bump pywin32 from 302 to 303 Bumps [pywin32](https://github.com/mhammond/pywin32) from 302 to 303. - [Release notes](https://github.com/mhammond/pywin32/releases) - [Changelog](https://github.com/mhammond/pywin32/blob/main/CHANGES.txt) - [Commits](https://github.com/mhammond/pywin32/commits) --- updated-dependencies: - dependency-name: pywin32 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index dd5b03be..c5d90f3c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -37,7 +37,7 @@ pytest==6.2.5 pytest-cov==3.0.0 # Pytest code coverage support coverage[toml]==6.2 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs # For manipulating folder permissions and the like. -pywin32==302; sys_platform == 'win32' +pywin32==303; sys_platform == 'win32' # All of the normal requirements From 0f0a4211ee43c40e7dcee7c05bfdb8d042904db4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Dec 2021 17:03:30 +0000 Subject: [PATCH 041/186] build(deps-dev): bump mypy from 0.920 to 0.921 Bumps [mypy](https://github.com/python/mypy) from 0.920 to 0.921. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.920...v0.921) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index dd5b03be..50b5a65c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,7 +15,7 @@ flake8-noqa==1.2.1 flake8-polyfill==1.0.2 flake8-use-fstring==1.3 -mypy==0.920 +mypy==0.921 pep8-naming==0.12.1 safety==1.10.3 types-requests==2.26.2 From 04bba0dda252c03aca7be9595b909a3f99e60be0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Dec 2021 17:03:19 +0000 Subject: [PATCH 042/186] build(deps-dev): bump mypy from 0.921 to 0.930 Bumps [mypy](https://github.com/python/mypy) from 0.921 to 0.930. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.921...v0.930) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a3dcf9a8..d4a58ae0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,7 +15,7 @@ flake8-noqa==1.2.1 flake8-polyfill==1.0.2 flake8-use-fstring==1.3 -mypy==0.921 +mypy==0.930 pep8-naming==0.12.1 safety==1.10.3 types-requests==2.26.2 From 997adf332ac3ac76decea5f8d851047d36904ff3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Dec 2021 17:03:31 +0000 Subject: [PATCH 043/186] build(deps-dev): bump types-requests from 2.26.2 to 2.26.3 Bumps [types-requests](https://github.com/python/typeshed) from 2.26.2 to 2.26.3. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d4a58ae0..69b3bb23 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.3 mypy==0.930 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.26.2 +types-requests==2.26.3 # Code formatting tools autopep8==1.6.0 From 5589c16dea3dceda92bff534b9a1a4396cae42ff Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 31 Dec 2021 20:24:07 +0000 Subject: [PATCH 044/186] config: Bump copyright end year --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 14a79651..18879201 100644 --- a/config.py +++ b/config.py @@ -35,7 +35,7 @@ appcmdname = 'EDMC' # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() _static_appversion = '5.3.0-beta3' _cached_version: Optional[semantic_version.Version] = None -copyright = '© 2015-2019 Jonathan Harris, 2020-2021 EDCD' +copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD' update_feed = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' update_interval = 8*60*60 From ee5f80cde1d73ddd374fd3e3b15e420a63aa0c74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 17:05:45 +0000 Subject: [PATCH 045/186] build(deps): bump requests from 2.26.0 to 2.27.0 Bumps [requests](https://github.com/psf/requests) from 2.26.0 to 2.27.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.26.0...v2.27.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bcf9b57d..cd73000f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ certifi==2021.10.8 -requests==2.26.0 +requests==2.27.0 watchdog==2.1.6 # Commented out because this doesn't package well with py2exe infi.systray==0.1.12; sys_platform == 'win32' From 6c67435b8a3901bfa88eb87d4337fa92d783194e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jan 2022 17:03:51 +0000 Subject: [PATCH 046/186] build(deps): bump requests from 2.27.0 to 2.27.1 Bumps [requests](https://github.com/psf/requests) from 2.27.0 to 2.27.1. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.27.0...v2.27.1) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cd73000f..479425c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ certifi==2021.10.8 -requests==2.27.0 +requests==2.27.1 watchdog==2.1.6 # Commented out because this doesn't package well with py2exe infi.systray==0.1.12; sys_platform == 'win32' From a5affe34366c53584cfb304e8ce2588c77576da5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Jan 2022 17:03:52 +0000 Subject: [PATCH 047/186] build(deps-dev): bump mypy from 0.930 to 0.931 Bumps [mypy](https://github.com/python/mypy) from 0.930 to 0.931. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.930...v0.931) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 69b3bb23..3a9009fd 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -15,7 +15,7 @@ flake8-noqa==1.2.1 flake8-polyfill==1.0.2 flake8-use-fstring==1.3 -mypy==0.930 +mypy==0.931 pep8-naming==0.12.1 safety==1.10.3 types-requests==2.26.3 From 9a590f700eb273e423f2bdd07d4093de2881db88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Jan 2022 17:03:58 +0000 Subject: [PATCH 048/186] build(deps-dev): bump types-requests from 2.26.3 to 2.27.1 Bumps [types-requests](https://github.com/python/typeshed) from 2.26.3 to 2.27.1. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 69b3bb23..5dbff540 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.3 mypy==0.930 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.26.3 +types-requests==2.27.1 # Code formatting tools autopep8==1.6.0 From 6798c083848ffd0028c44da4c443ed1f5a5dcbc6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 17:03:58 +0000 Subject: [PATCH 049/186] build(deps-dev): bump types-requests from 2.27.1 to 2.27.5 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.1 to 2.27.5. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index b855240a..d5bb36b8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.3 mypy==0.931 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.27.1 +types-requests==2.27.5 # Code formatting tools autopep8==1.6.0 From f422d1efe78f61cfed6b1132e281781fb0b625dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jan 2022 17:03:24 +0000 Subject: [PATCH 050/186] build(deps-dev): bump flake8-comprehensions from 3.7.0 to 3.8.0 Bumps [flake8-comprehensions](https://github.com/adamchainz/flake8-comprehensions) from 3.7.0 to 3.8.0. - [Release notes](https://github.com/adamchainz/flake8-comprehensions/releases) - [Changelog](https://github.com/adamchainz/flake8-comprehensions/blob/main/HISTORY.rst) - [Commits](https://github.com/adamchainz/flake8-comprehensions/compare/3.7.0...3.8.0) --- updated-dependencies: - dependency-name: flake8-comprehensions dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d5bb36b8..a1ce6af6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ wheel flake8==4.0.1 flake8-annotations-coverage==0.0.5 flake8-cognitive-complexity==0.1.0 -flake8-comprehensions==3.7.0 +flake8-comprehensions==3.8.0 flake8-docstrings==1.6.0 isort==5.10.1 flake8-isort==4.1.1 From fcf2ccfdedf23f76c1547c2f4e1dca85060e7679 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jan 2022 17:04:01 +0000 Subject: [PATCH 051/186] build(deps-dev): bump types-requests from 2.27.5 to 2.27.6 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.5 to 2.27.6. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a1ce6af6..a4c1c296 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.3 mypy==0.931 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.27.5 +types-requests==2.27.6 # Code formatting tools autopep8==1.6.0 From 65c806ea97daba0ced2d9c5a362e95a0da3ab2ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jan 2022 17:06:26 +0000 Subject: [PATCH 052/186] build(deps-dev): bump types-requests from 2.27.6 to 2.27.7 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.6 to 2.27.7. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a4c1c296..d746f046 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.3 mypy==0.931 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.27.6 +types-requests==2.27.7 # Code formatting tools autopep8==1.6.0 From 2e50f357ee5fe03c16e74a52a91b3564b714ba96 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 Jan 2022 13:28:47 +0000 Subject: [PATCH 053/186] config.py: Bump to 5.3.0-beta4 to ensure I'm testing 3.10.2 build --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 18879201..b32b2613 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.3.0-beta3' +_static_appversion = '5.3.0-beta4' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD' From dfa8051f4242a8e44d2868b271d49585b2ad3a01 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 Jan 2022 13:32:01 +0000 Subject: [PATCH 054/186] python: Bump to 3.10.2 Confirmed working with a build+run. CAPI update works, Inara plugin works, Settings open without issues.... --- .github/workflows/windows-build.yml | 2 +- .python-version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 80b61487..5d12da92 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v2.4.0 - uses: actions/setup-python@v2.3.1 with: - python-version: "3.10.1" + python-version: "3.10.2" architecture: "x86" - name: Install python tools diff --git a/.python-version b/.python-version index f870be23..7b59a5ca 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.10.1 +3.10.2 From 85f7bddef0b2f37de3812d81e3e2f2a051786923 Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 20 Jan 2022 15:52:44 +0200 Subject: [PATCH 055/186] use gmtime as time formatter --- EDMCLogging.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/EDMCLogging.py b/EDMCLogging.py index c00b7ab9..c98ebe04 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -46,6 +46,7 @@ from fnmatch import fnmatch # So that any warning about accessing a protected member is only in one place. from sys import _getframe as getframe from threading import get_native_id as thread_native_id +from time import gmtime from traceback import print_exc from typing import TYPE_CHECKING, Tuple, cast @@ -91,6 +92,9 @@ logging.Logger.trace = lambda self, message, *args, **kwargs: self._log( # type **kwargs ) +# make logging use UTC for times +logging.Formatter.converter = gmtime + def _trace_if(self: logging.Logger, condition: str, message: str, *args, **kwargs) -> None: if any(fnmatch(condition, p) for p in config_mod.trace_on): From cd81f65ed73958c0d32999de7b12e8d0e8c4b2f9 Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 20 Jan 2022 15:20:01 +0200 Subject: [PATCH 056/186] Reset geometry and UI scale with --reset-ui --- EDMarketConnector.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index c0f69f08..fb136fca 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1867,7 +1867,11 @@ sys.path: {sys.path}''' config.set('ui_transparency', 100) # 100 is completely opaque config.delete('font') config.delete('font_size') - logger.info('reset theme, font, font size, and transparency to default.') + + config.set('ui_scale', 100) # 100% is the default here + config.delete('geometry') # unset is recreated by other code + + logger.info('reset theme, transparency, font, font size, ui scale, and ui geometry to default.') # We prefer a UTF-8 encoding gets set, but older Windows versions have # issues with this. From Windows 10 1903 onwards we can rely on the From 7bb060f32cadafa084209bf589d6903b633aded8 Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 20 Jan 2022 16:02:23 +0200 Subject: [PATCH 057/186] dont explode if a key doesnt exist when resetting --- EDMarketConnector.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index fb136fca..01c9d846 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -74,7 +74,7 @@ if __name__ == '__main__': # noqa: C901 ########################################################################### parser.add_argument( '--reset-ui', - help='reset UI theme and transparency to defaults', + help='Reset UI theme, transparency, font, font size, ui scale, and ui geometry to default', action='store_true' ) ########################################################################### @@ -1865,11 +1865,11 @@ sys.path: {sys.path}''' if args.reset_ui: config.set('theme', 0) # 'Default' theme uses ID 0 config.set('ui_transparency', 100) # 100 is completely opaque - config.delete('font') - config.delete('font_size') + config.delete('font', suppress=True) + config.delete('font_size', suppress=True) config.set('ui_scale', 100) # 100% is the default here - config.delete('geometry') # unset is recreated by other code + config.delete('geometry', suppress=True) # unset is recreated by other code logger.info('reset theme, transparency, font, font size, ui scale, and ui geometry to default.') From 66a212f7c46d6ca603618477f556ffef8208411a Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 20 Jan 2022 16:26:04 +0200 Subject: [PATCH 058/186] move UTC to hardcoded, add comment --- EDMCLogging.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/EDMCLogging.py b/EDMCLogging.py index c98ebe04..3f08f436 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -166,7 +166,10 @@ class Logger: self.logger_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(process)d:%(thread)d:%(osthreadid)d %(module)s.%(qualname)s:%(lineno)d: %(message)s') # noqa: E501 self.logger_formatter.default_time_format = '%Y-%m-%d %H:%M:%S' - self.logger_formatter.default_msec_format = '%s.%03d' + # WORKAROUND n/a | 2022-01-20: This is concatted to the above time format, so including %Z there is broken + # As a sidenote, this will not always match with journal time etc. As that's based + # on the elite server side time + self.logger_formatter.default_msec_format = '%s.%03d UTC' self.logger_channel.setFormatter(self.logger_formatter) self.logger.addHandler(self.logger_channel) From 5f007b3f77bd62204960bdf9c778547924e60f7d Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 20 Jan 2022 16:32:36 +0200 Subject: [PATCH 059/186] more clarifying logs --- EDMCLogging.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EDMCLogging.py b/EDMCLogging.py index 3f08f436..85e7d843 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -92,7 +92,9 @@ logging.Logger.trace = lambda self, message, *args, **kwargs: self._log( # type **kwargs ) -# make logging use UTC for times +# make logging use UTC for times, to help make our logs congruent with journals etc. +# Note that as this is the local system vs the remote system (ED servers, for journals), times may not be perfectly +# in sync. (something something NTP) logging.Formatter.converter = gmtime From 3b4f6a4e859773d2534307a876719a1d4f1c77d5 Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 20 Jan 2022 16:41:18 +0200 Subject: [PATCH 060/186] switch comments to MAGIC tags --- EDMCLogging.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/EDMCLogging.py b/EDMCLogging.py index 85e7d843..82807c90 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -92,9 +92,10 @@ logging.Logger.trace = lambda self, message, *args, **kwargs: self._log( # type **kwargs ) -# make logging use UTC for times, to help make our logs congruent with journals etc. -# Note that as this is the local system vs the remote system (ED servers, for journals), times may not be perfectly -# in sync. (something something NTP) +# MAGIC n/a: 2020-01-20: make logging use UTC for times, to help make our logs congruent with journals etc. +# MAGIC-CONT: Note that as this is the local system vs the remote system (ED servers, for journals), +# MAGIC-CONT: times may not be perfectly in sync. (something something NTP). +# MAGIC-CONT: See MAGIC tagged comment in Logger.__init__() logging.Formatter.converter = gmtime @@ -168,9 +169,9 @@ class Logger: self.logger_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(process)d:%(thread)d:%(osthreadid)d %(module)s.%(qualname)s:%(lineno)d: %(message)s') # noqa: E501 self.logger_formatter.default_time_format = '%Y-%m-%d %H:%M:%S' - # WORKAROUND n/a | 2022-01-20: This is concatted to the above time format, so including %Z there is broken - # As a sidenote, this will not always match with journal time etc. As that's based - # on the elite server side time + # MAGIC n/a | 2022-01-20: As of Python 3.10.2 you can *not* use either `%s.%03.d` in default_time_format + # MAGIC-CONT: *or* use `%Z` in default_time_msec (as its concatted to default_msec). + # MAGIC-CONT: UTC is hardcoded here because we know it always will be (see above MAGIC comment) self.logger_formatter.default_msec_format = '%s.%03d UTC' self.logger_channel.setFormatter(self.logger_formatter) From 71261fc3d9eb84320de4cc9fee83f42856fd40d6 Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 20 Jan 2022 16:48:29 +0200 Subject: [PATCH 061/186] Reorder and clarify some comments --- EDMCLogging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EDMCLogging.py b/EDMCLogging.py index 82807c90..82f56ca3 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -170,8 +170,8 @@ class Logger: self.logger_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(process)d:%(thread)d:%(osthreadid)d %(module)s.%(qualname)s:%(lineno)d: %(message)s') # noqa: E501 self.logger_formatter.default_time_format = '%Y-%m-%d %H:%M:%S' # MAGIC n/a | 2022-01-20: As of Python 3.10.2 you can *not* use either `%s.%03.d` in default_time_format - # MAGIC-CONT: *or* use `%Z` in default_time_msec (as its concatted to default_msec). - # MAGIC-CONT: UTC is hardcoded here because we know it always will be (see above MAGIC comment) + # MAGIC-CONT: (throws exceptions), *or* use `%Z` in default_time_msec (formatting issues). + # UTC is hardcoded here because we know it always will be (see above MAGIC comment) self.logger_formatter.default_msec_format = '%s.%03d UTC' self.logger_channel.setFormatter(self.logger_formatter) From 251f129774412a1704fb4a3c3348d2d04fdc7de2 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 Jan 2022 14:58:06 +0000 Subject: [PATCH 062/186] EDMCLogging: Reword/format the 'UTC please' MAGIC comments --- EDMCLogging.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/EDMCLogging.py b/EDMCLogging.py index 82f56ca3..5248f544 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -92,9 +92,9 @@ logging.Logger.trace = lambda self, message, *args, **kwargs: self._log( # type **kwargs ) -# MAGIC n/a: 2020-01-20: make logging use UTC for times, to help make our logs congruent with journals etc. -# MAGIC-CONT: Note that as this is the local system vs the remote system (ED servers, for journals), -# MAGIC-CONT: times may not be perfectly in sync. (something something NTP). +# MAGIC n/a: 2022-01-20: We want logging timestamps to be in UTC, not least because the game journals log in UTC. +# MAGIC-CONT: Note that the game client uses the ED server's idea of UTC, which can easily be different from machine +# MAGIC-CONT: local idea of it. So don't expect our log timestamps to perfectly match Journal ones. # MAGIC-CONT: See MAGIC tagged comment in Logger.__init__() logging.Formatter.converter = gmtime @@ -170,8 +170,9 @@ class Logger: self.logger_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(process)d:%(thread)d:%(osthreadid)d %(module)s.%(qualname)s:%(lineno)d: %(message)s') # noqa: E501 self.logger_formatter.default_time_format = '%Y-%m-%d %H:%M:%S' # MAGIC n/a | 2022-01-20: As of Python 3.10.2 you can *not* use either `%s.%03.d` in default_time_format - # MAGIC-CONT: (throws exceptions), *or* use `%Z` in default_time_msec (formatting issues). - # UTC is hardcoded here because we know it always will be (see above MAGIC comment) + # MAGIC-CONT: (throws exceptions), *or* use `%Z` in default_time_msec (more exceptions). + # MAGIC-CONT: ' UTC' is hard-coded here - we know we're using the local machine's idea of UTC/GMT because we + # MAGIC-CONT: cause logging.Formatter() to use `gmtime()` - see MAGIC comment in this file's top-level code. self.logger_formatter.default_msec_format = '%s.%03d UTC' self.logger_channel.setFormatter(self.logger_formatter) From 333b84ada698d5913add4443e7a233a7a26abc58 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 Jan 2022 14:59:46 +0000 Subject: [PATCH 063/186] EDMCLogging: Fix minor MAGIC comment format issue --- EDMCLogging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMCLogging.py b/EDMCLogging.py index 5248f544..1bf83bc7 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -92,7 +92,7 @@ logging.Logger.trace = lambda self, message, *args, **kwargs: self._log( # type **kwargs ) -# MAGIC n/a: 2022-01-20: We want logging timestamps to be in UTC, not least because the game journals log in UTC. +# MAGIC n/a | 2022-01-20: We want logging timestamps to be in UTC, not least because the game journals log in UTC. # MAGIC-CONT: Note that the game client uses the ED server's idea of UTC, which can easily be different from machine # MAGIC-CONT: local idea of it. So don't expect our log timestamps to perfectly match Journal ones. # MAGIC-CONT: See MAGIC tagged comment in Logger.__init__() From 7ac248be7a748e7f5bbd170c6dbbf66a957a0b83 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 20 Jan 2022 17:45:23 +0000 Subject: [PATCH 064/186] inara: Utilise Statistics Current_Wealth for commanderAssets Inara itself seems to be doing this if it has access to CAPI-sourced Journals. If we don't send commanderAssets as part of setCommanderCredits then it does ... something else ... and ends up with your total assets value bouncing between what EDMC is causing and what it sees from the CAPI-sourced journals. So, don't send this from LoadGame any more, instead wait for Statistics, and if it has the relevant data, also send commanderAssets. --- plugins/inara.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index a3e2a22c..86e3ec25 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -737,17 +737,24 @@ def journal_entry( # noqa: C901, CCR001 logger.debug('Adding events', exc_info=e) return str(e) - # Send credits and stats to Inara on startup only - otherwise may be out of date + # We want to utilise some Statistics data, so don't setCommanderCredits here if event_name == 'LoadGame': - new_add_event( - 'setCommanderCredits', - entry['timestamp'], - {'commanderCredits': state['Credits'], 'commanderLoan': state['Loan']} - ) - this.lastcredits = state['Credits'] elif event_name == 'Statistics': + inara_data = { + 'commanderCredits': state['Credits'], + 'commanderLoan': state['Loan'], + } + if entry.get('Bank_Account') is not None: + if entry['Bank_Account'].get('Current_Wealth') is not None: + inara_data['commanderAssets'] = entry['Bank_Account']['Current_Wealth'] + + new_add_event( + 'setCommanderCredits', + entry['timestamp'], + inara_data + ) new_add_event('setCommanderGameStatistics', entry['timestamp'], state['Statistics']) # may be out of date # Selling / swapping ships From eb0ff31805ccfddce41cf09033b17dac51eb6369 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 25 Jan 2022 14:26:42 +0000 Subject: [PATCH 065/186] Python: Bump minor version in GH workflows & update Releasing.md I used Releasing.md as reference for what would need the version updating, but it didn't say anything about all the workflows, it now does. --- .github/workflows/pr-checks.yml | 4 ++-- .github/workflows/push-checks.yml | 4 ++-- docs/Releasing.md | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index a1472e27..c89cbae5 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -52,10 +52,10 @@ jobs: #################################################################### # Get Python set up #################################################################### - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v2.3.1 with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/push-checks.yml b/.github/workflows/push-checks.yml index 5f78645f..ba9c64be 100644 --- a/.github/workflows/push-checks.yml +++ b/.github/workflows/push-checks.yml @@ -20,10 +20,10 @@ jobs: - uses: actions/checkout@v2.4.0 with: fetch-depth: 0 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v2.3.1 with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/docs/Releasing.md b/docs/Releasing.md index 37d14ae6..50f5d0d6 100644 --- a/docs/Releasing.md +++ b/docs/Releasing.md @@ -446,3 +446,5 @@ When changing the Python version (Major.Minor.Patch) used: pythonXX.dll file. 3. `.pre-commit-config.yaml` will need the `default_language_version` section updated to the appropriate version. + 4. All `.github/workflows/` files will need to be citing the correct + version in any `uses: actions/setup-python` section. From 109f964fc3840e595423a2b1b2e534d1d900e2d7 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 25 Jan 2022 14:30:58 +0000 Subject: [PATCH 066/186] Python: Version must be a quoted string now Else `3.10` gets interpreted as `3.1` and it all breaks. --- .github/workflows/pr-checks.yml | 2 +- .github/workflows/push-checks.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index c89cbae5..7b0a61c2 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -55,7 +55,7 @@ jobs: - name: Set up Python 3.10 uses: actions/setup-python@v2.3.1 with: - python-version: 3.10 + python-version: "3.10" - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/push-checks.yml b/.github/workflows/push-checks.yml index ba9c64be..e8cf5f5c 100644 --- a/.github/workflows/push-checks.yml +++ b/.github/workflows/push-checks.yml @@ -23,7 +23,7 @@ jobs: - name: Set up Python 3.10 uses: actions/setup-python@v2.3.1 with: - python-version: 3.10 + python-version: "3.10" - name: Install dependencies run: | python -m pip install --upgrade pip From c4f734734e2f80cbf9e0ce057ce5d949decde0dc Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 25 Jan 2022 14:59:53 +0000 Subject: [PATCH 067/186] inara: snake_case lastcredits variable --- plugins/inara.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index 86e3ec25..53be13d2 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -76,7 +76,7 @@ class This: self.suppress_docked = False # Skip initial Docked event if started docked self.cargo: Optional[List[OrderedDictT[str, Any]]] = None self.materials: Optional[List[OrderedDictT[str, Any]]] = None - self.lastcredits: int = 0 # Send credit update soon after Startup / new game + self.last_credits: int = 0 # Send credit update soon after Startup / new game self.storedmodules: Optional[List[OrderedDictT[str, Any]]] = None self.loadout: Optional[OrderedDictT[str, Any]] = None self.fleet: Optional[List[OrderedDictT[str, Any]]] = None @@ -372,7 +372,7 @@ def journal_entry( # noqa: C901, CCR001 this.suppress_docked = False this.cargo = None this.materials = None - this.lastcredits = 0 + this.last_credits = 0 this.storedmodules = None this.loadout = None this.fleet = None @@ -383,8 +383,8 @@ def journal_entry( # noqa: C901, CCR001 this.station_marketid = None elif event_name in ('Resurrect', 'ShipyardBuy', 'ShipyardSell', 'SellShipOnRebuy'): - # Events that mean a significant change in credits so we should send credits after next "Update" - this.lastcredits = 0 + # Events that mean a significant change in credits, so we should send credits after next "Update" + this.last_credits = 0 elif event_name in ('ShipyardNew', 'ShipyardSwap') or (event_name == 'Location' and entry['Docked']): this.suppress_docked = True @@ -739,7 +739,7 @@ def journal_entry( # noqa: C901, CCR001 # We want to utilise some Statistics data, so don't setCommanderCredits here if event_name == 'LoadGame': - this.lastcredits = state['Credits'] + this.last_credits = state['Credits'] elif event_name == 'Statistics': inara_data = { @@ -1356,7 +1356,7 @@ def cmdr_data(data: CAPIData, is_beta): # noqa: CCR001 this.station_link.update_idletasks() if config.get_int('inara_out') and not is_beta and not this.multicrew and credentials(this.cmdr): - if not (CREDIT_RATIO > this.lastcredits / data['commander']['credits'] > 1/CREDIT_RATIO): + if not (CREDIT_RATIO > this.last_credits / data['commander']['credits'] > 1 / CREDIT_RATIO): this.filter_events( Credentials(this.cmdr, this.FID, str(credentials(this.cmdr))), lambda e: e.name != 'setCommanderCredits' @@ -1372,7 +1372,7 @@ def cmdr_data(data: CAPIData, is_beta): # noqa: CCR001 } ) - this.lastcredits = int(data['commander']['credits']) + this.last_credits = int(data['commander']['credits']) def make_loadout(state: Dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR001 From ded86c2ff56abe6cb3c1976d14aa5957293fdc30 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 25 Jan 2022 15:00:33 +0000 Subject: [PATCH 068/186] inara: Rename to current_credentials to avoid 'creds' ambiguity Every time I see `current_creds` my first though is "creds means credits". --- plugins/inara.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index 53be13d2..69270e97 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -419,7 +419,7 @@ def journal_entry( # noqa: C901, CCR001 this.station_marketid = None if config.get_int('inara_out') and not is_beta and not this.multicrew and credentials(cmdr): - current_creds = Credentials(this.cmdr, this.FID, str(credentials(this.cmdr))) + current_credentials = Credentials(this.cmdr, this.FID, str(credentials(this.cmdr))) try: # Dump starting state to Inara if (this.newuser or event_name == 'StartUp' or (this.newsession and event_name == 'Cargo')): @@ -844,7 +844,7 @@ def journal_entry( # noqa: C901, CCR001 if this.fleet != fleet: this.fleet = fleet - this.filter_events(current_creds, lambda e: e.name != 'setCommanderShip') + this.filter_events(current_credentials, lambda e: e.name != 'setCommanderShip') # this.events = [x for x in this.events if x['eventName'] != 'setCommanderShip'] # Remove any unsent for ship in this.fleet: @@ -857,7 +857,7 @@ def journal_entry( # noqa: C901, CCR001 this.loadout = loadout this.filter_events( - current_creds, + current_credentials, lambda e: ( e.name != 'setCommanderShipLoadout' or cast(dict, e.data)['shipGameID'] != cast(dict, this.loadout)['shipGameID']) @@ -898,7 +898,7 @@ def journal_entry( # noqa: C901, CCR001 # Only send on change this.storedmodules = modules # Remove any unsent - this.filter_events(current_creds, lambda e: e.name != 'setCommanderStorageModules') + this.filter_events(current_credentials, lambda e: e.name != 'setCommanderStorageModules') # this.events = list(filter(lambda e: e['eventName'] != 'setCommanderStorageModules', this.events)) new_add_event('setCommanderStorageModules', entry['timestamp'], this.storedmodules) @@ -1227,7 +1227,7 @@ def journal_entry( # noqa: C901, CCR001 if event_name == 'CommunityGoal': # Remove any unsent this.filter_events( - current_creds, lambda e: e.name not in ('setCommunityGoal', 'setCommanderCommunityGoalProgress') + current_credentials, lambda e: e.name not in ('setCommunityGoal', 'setCommanderCommunityGoalProgress') ) # this.events = list(filter( From 28cbd6e0ea46db70e2f8388f5a0d19b70fc32b83 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 25 Jan 2022 15:27:35 +0000 Subject: [PATCH 069/186] Inara: queue 'Rank'/'setCommanderRankPilot' as soon as seen This means we *will* send a message when logging in on foot, rather than waiting for a `Cargo` event that never arrives. --- plugins/inara.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index 69270e97..868671ec 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -426,16 +426,6 @@ def journal_entry( # noqa: C901, CCR001 this.newuser = False this.newsession = False - # Send rank info to Inara on startup - new_add_event( - 'setCommanderRankPilot', - entry['timestamp'], - [ - {'rankName': k.lower(), 'rankValue': v[0], 'rankProgress': v[1] / 100.0} - for k, v in state['Rank'].items() if v is not None - ] - ) - # Don't send the API call with no values. if state['Reputation']: new_add_event( @@ -503,6 +493,18 @@ def journal_entry( # noqa: C901, CCR001 this.loadout = make_loadout(state) new_add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout) + # Login-time Ranks + elif event_name == 'Rank': + # Send rank info to Inara on startup + new_add_event( + 'setCommanderRankPilot', + entry['timestamp'], + [ + {'rankName': k.lower(), 'rankValue': v[0], 'rankProgress': v[1] / 100.0} + for k, v in state['Rank'].items() if v is not None + ] + ) + # Promotions elif event_name == 'Promotion': for k, v in state['Rank'].items(): From 626fb0cb87e606e83d352e3c67f1b00f80f3d427 Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 25 Jan 2022 17:39:44 +0200 Subject: [PATCH 070/186] add odyssey ranks to status page --- stats.py | 51 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/stats.py b/stats.py index 334e8358..f48142aa 100644 --- a/stats.py +++ b/stats.py @@ -56,15 +56,17 @@ def status(data: Dict[str, Any]) -> List[List[str]]: RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime # in output order - (_('Combat'), 'combat'), # LANG: Ranking - (_('Trade'), 'trade'), # LANG: Ranking - (_('Explorer'), 'explore'), # LANG: Ranking - (_('CQC'), 'cqc'), # LANG: Ranking - (_('Federation'), 'federation'), # LANG: Ranking - (_('Empire'), 'empire'), # LANG: Ranking - (_('Powerplay'), 'power'), # LANG: Ranking - # ??? , 'crime'), # LANG: Ranking - # ??? , 'service'), # LANG: Ranking + (_('Combat'), 'combat'), # LANG: Ranking + (_('Trade'), 'trade'), # LANG: Ranking + (_('Explorer'), 'explore'), # LANG: Ranking + (_('Mercenary'), 'mercenary'), # LANG: Ranking + (_('Exobiologist'), 'exobiologist'), # LANG: Ranking + (_('CQC'), 'cqc'), # LANG: Ranking + (_('Federation'), 'federation'), # LANG: Ranking + (_('Empire'), 'empire'), # LANG: Ranking + (_('Powerplay'), 'power'), # LANG: Ranking + # ??? , 'crime'), # LANG: Ranking + # ??? , 'service'), # LANG: Ranking ] RANK_NAMES = { # noqa: N806 # Its a constant, just needs to be updated at runtime @@ -102,6 +104,28 @@ def status(data: Dict[str, Any]) -> List[List[str]]: _('Pioneer'), # LANG: Explorer rank _('Elite') # LANG: Top rank ], + 'mercenary': [ + _('Defenceless'), # LANG: Mercenary rank + _('Mostly Defenceless'), # LANG: Mercenary rank + _('Rookie'), # LANG: Mercenary rank + _('Soldier'), # LANG: Mercenary rank + _('Gunslinger'), # LANG: Mercenary rank + _('Warrior'), # LANG: Mercenary rank + _('Gunslinger'), # LANG: Mercenary rank + _('Deadeye'), # LANG: Mercenary rank + _('Elite'), # LANG: Top rank + ], + 'exobiologist': [ + _('Directionless'), # LANG: Exobiologist rank + _('Mostly Directionless'), # LANG: Exobiologist rank + _('Compiler'), # LANG: Exobiologist rank + _('Collector'), # LANG: Exobiologist rank + _('Cataloguer'), # LANG: Exobiologist rank + _('Taxonomist'), # LANG: Exobiologist rank + _('Ecologist'), # LANG: Exobiologist rank + _('Geneticist'), # LANG: Exobiologist rank + _('Elite'), # LANG: Top rank + ], 'cqc': [ _('Helpless'), # LANG: CQC rank _('Mostly Helpless'), # LANG: CQC rank @@ -167,6 +191,7 @@ def status(data: Dict[str, Any]) -> List[List[str]]: for title, thing in RANKS: rank = ranks.get(thing) names = RANK_NAMES[thing] + # TODO: handle Elite I-V here. if isinstance(rank, int): res.append([title, names[rank] if rank < len(names) else f'Rank {rank}']) @@ -357,7 +382,13 @@ class StatsResults(tk.Toplevel): # assumes things two and three are money self.addpagerow(page, [thing[0], self.credits(int(thing[1]))], with_copy=True) - for thing in stats[3:]: + # TODO: Headers, and a bit saner of some splitting up of things here + self.addpagespacer(page) + for thing in stats[3:9]: + self.addpagerow(page, thing, with_copy=True) + + self.addpagespacer(page) + for thing in stats[9:]: self.addpagerow(page, thing, with_copy=True) ttk.Frame(page).grid(pady=5) # bottom spacer From 1f9f7d45f9eb1ad524fd0e0ac8be1a9341500bc6 Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 25 Jan 2022 17:48:56 +0200 Subject: [PATCH 071/186] update lang --- L10n/en.template | 492 ++++++++++++++++++++++++++--------------------- 1 file changed, 273 insertions(+), 219 deletions(-) diff --git a/L10n/en.template b/L10n/en.template index d520a556..e1849115 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -1,38 +1,125 @@ /* Language name */ "!Language" = "English"; -/* companion.py: Frontier CAPI didn't respond; In files: companion.py:220; */ -"Error: Frontier CAPI didn't respond" = "Error: Frontier CAPI didn't respond"; +/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:212; eddn.py:584; eddn.py:925; load.py:215; load.py:593; load.py:940; eddn.py:252; eddn.py:699; eddn.py:1439; */ +"Sending data to EDDN..." = "Sending data to EDDN..."; -/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:239; */ -"Error: Frontier server is lagging" = "Error: Frontier server is lagging"; +/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:257; eddn.py:864; eddn.py:898; eddn.py:937; load.py:260; load.py:878; load.py:913; load.py:952; eddn.py:316; eddn.py:1375; eddn.py:1410; eddn.py:1451; */ +"Error: Can't connect to EDDN" = "Error: Can't connect to EDDN"; -/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:255; */ -"Docked but unknown station: EDO Settlement?" = "Docked but unknown station: EDO Settlement?"; +/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:663; load.py:672; eddn.py:1122; */ +"Send station data to the Elite Dangerous Data Network" = "Send station data to the Elite Dangerous Data Network"; -/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:265; */ -"Error: Invalid Credentials" = "Error: Invalid Credentials"; +/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:673; load.py:682; eddn.py:1133; */ +"Send system and scan data to the Elite Dangerous Data Network" = "Send system and scan data to the Elite Dangerous Data Network"; -/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:290; */ -"Error: Wrong Cmdr" = "Error: Wrong Cmdr"; +/* eddn.py: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this; In files: eddn.py:683; load.py:692; eddn.py:1144; */ +"Delay sending until docked" = "Delay sending until docked"; -/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:416; companion.py:501; */ -"Error" = "Error"; +/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:197; load.py:197; edsm.py:237; */ +"Send flight log and Cmdr status to EDSM" = "Send flight log and Cmdr status to EDSM"; -/* companion.py: Frontier auth, no 'usr' section in returned data; companion.py: Frontier auth, no 'customer_id' in 'usr' section in returned data; In files: companion.py:459; companion.py:464; */ -"Error: Couldn't check token customer_id" = "Error: Couldn't check token customer_id"; +/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:206; load.py:206; edsm.py:247; */ +"Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map credentials"; -/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:470; */ -"Error: customer_id doesn't match!" = "Error: customer_id doesn't match!"; +/* edsm.py: Game Commander name label in EDSM settings; theme.py: Label for commander name in main window; EDMarketConnector.py: Label for commander name in main window; stats.py: Cmdr stats; In files: edsm.py:216; load.py:216; edsm.py:258; theme.py:227; EDMarketConnector.py:822; stats.py:52; */ +"Cmdr" = "Cmdr"; -/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:492; */ -"Error: unable to get token" = "Error: unable to get token"; +/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:223; load.py:223; edsm.py:266; */ +"Commander Name" = "Commander Name"; -/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:799; */ -"Frontier CAPI down for maintenance" = "Frontier CAPI down for maintenance"; +/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:230; inara.py:233; load.py:230; load.py:233; edsm.py:274; inara.py:243; */ +"API Key" = "API Key"; -/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:811; */ -"Frontier CAPI query failure" = "Frontier CAPI query failure"; +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:256; load.py:256; edsm.py:301; prefs.py:520; prefs.py:1174; prefs.py:1207; stats.py:143; stats.py:162; stats.py:181; stats.py:199; */ +"None" = "None"; + +/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:622; edsm.py:724; load.py:627; load.py:729; edsm.py:747; edsm.py:875; */ +"Error: EDSM {MSG}" = "Error: EDSM {MSG}"; + +/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:658; edsm.py:720; load.py:663; load.py:725; edsm.py:784; edsm.py:870; */ +"Error: Can't connect to EDSM" = "Error: Can't connect to EDSM"; + +/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:215; load.py:215; inara.py:222; */ +"Send flight log and Cmdr status to Inara" = "Send flight log and Cmdr status to Inara"; + +/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:225; load.py:225; inara.py:234; */ +"Inara credentials" = "Inara credentials"; + +/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1316; inara.py:1328; load.py:1319; load.py:1331; inara.py:1587; inara.py:1600; */ +"Error: Inara {MSG}" = "Error: Inara {MSG}"; + +/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: load.py:51; load.py:51; load.py:88; load.py:104; load.py:110; coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ +"Auto" = "Auto"; + +/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: load.py:51; load.py:88; load.py:102; coriolis.py:53; coriolis.py:99; coriolis.py:115; */ +"Normal" = "Normal"; + +/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: load.py:51; load.py:88; load.py:103; coriolis.py:54; coriolis.py:100; coriolis.py:116; */ +"Beta" = "Beta"; + +/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: load.py:64:66; coriolis.py:69:71; */ +"Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='" = "Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='"; + +/* coriolis.py: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL; In files: load.py:69; coriolis.py:75; */ +"Normal URL" = "Normal URL"; + +/* coriolis.py: Generic 'Reset' button label; In files: load.py:71; load.py:78; coriolis.py:78; coriolis.py:87; */ +"Reset" = "Reset"; + +/* coriolis.py: Settings>Coriolis: Label for 'alpha/beta game version' URL; In files: load.py:76; coriolis.py:84; */ +"Beta URL" = "Beta URL"; + +/* In files: load.py:83; */ +"Override Beta/Normal selection" = "Override Beta/Normal selection"; + +/* coriolis.py: Settings>Coriolis - invalid override mode found; In files: load.py:120; coriolis.py:134; */ +"Invalid Coriolis override mode!" = "Invalid Coriolis override mode!"; + +/* eddb.py: Journal Processing disabled due to an active killswitch; In files: load.py:99; eddb.py:100; */ +"EDDB Journal processing disabled. See Log." = "EDDB Journal processing disabled. See Log."; + +/* eddn.py: Killswitch disabled EDDN; In files: load.py:756; eddn.py:1233; */ +"EDDN journal handler disabled. See Log." = "EDDN journal handler disabled. See Log."; + +/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: load.py:346; edsm.py:402; */ +"EDSM Handler disabled. See Log." = "EDSM Handler disabled. See Log."; + +/* inara.py: INARA support disabled via killswitch; In files: load.py:331; inara.py:341; */ +"Inara disabled. See Log." = "Inara disabled. See Log."; + +/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: test_ast_stuff.py:2; EDMarketConnector.py:1047; */ +"CAPI: No commander data returned" = "CAPI: No commander data returned"; + +/* coriolis.py: Settings>Coriolis: Label for selection of using Normal, Beta or 'auto' Coriolis URL; In files: coriolis.py:94; */ +"Override Beta/Normal Selection" = "Override Beta/Normal Selection"; + +/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:334; */ +"EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN Error: EDMC is too old for EDDN. Please update."; + +/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:340; */ +"EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "EDDN Error: Validation Failed (EDMC Too Old?). See Log"; + +/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:345; */ +"EDDN Error: Returned {STATUS} status code" = "EDDN Error: Returned {STATUS} status code"; + +/* eddn.py: No 'Route' found in NavRoute.json file; In files: eddn.py:971; */ +"No 'Route' array in NavRoute.json contents" = "No 'Route' array in NavRoute.json contents"; + +/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ +"Journal directory already locked" = "Journal directory already locked"; + +/* journal_lock.py: Text for when newly selected Journal directory is already locked; In files: journal_lock.py:223:224; */ +"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this."; + +/* journal_lock.py: Generic 'Retry' button label; In files: journal_lock.py:228; */ +"Retry" = "Retry"; + +/* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ +"Ignore" = "Ignore"; + +/* ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; In files: ttkHyperlinkLabel.py:42; EDMarketConnector.py:866; */ +"Copy" = "Copy"; /* EDMarketConnector.py: Update button in main window; In files: EDMarketConnector.py:529; EDMarketConnector.py:828; EDMarketConnector.py:1520; */ "Update" = "Update"; @@ -46,25 +133,22 @@ /* EDMarketConnector.py: ED Journal file location appears to be in error; In files: EDMarketConnector.py:815; */ "Error: Check E:D journal file location" = "Error: Check E:D journal file location"; -/* EDMarketConnector.py: Label for commander name in main window; edsm.py: Game Commander name label in EDSM settings; stats.py: Cmdr stats; theme.py: Label for commander name in main window; In files: EDMarketConnector.py:822; edsm.py:257; stats.py:52; theme.py:227; */ -"Cmdr" = "Cmdr"; - /* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: Multicrew role label in main window; In files: EDMarketConnector.py:824; EDMarketConnector.py:1279; */ "Role" = "Role"; -/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: 'Ship' label in main UI; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:824; EDMarketConnector.py:1289; EDMarketConnector.py:1312; stats.py:367; */ +/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: 'Ship' label in main UI; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:824; EDMarketConnector.py:1289; EDMarketConnector.py:1312; stats.py:398; */ "Ship" = "Ship"; /* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:825; */ "Suit" = "Suit"; -/* EDMarketConnector.py: Label for 'System' line in main UI; prefs.py: Configuration - Label for selection of 'System' provider website; stats.py: Main window; In files: EDMarketConnector.py:826; prefs.py:607; stats.py:369; */ +/* EDMarketConnector.py: Label for 'System' line in main UI; prefs.py: Configuration - Label for selection of 'System' provider website; stats.py: Main window; In files: EDMarketConnector.py:826; prefs.py:607; stats.py:400; */ "System" = "System"; -/* EDMarketConnector.py: Label for 'Station' line in main UI; prefs.py: Configuration - Label for selection of 'Station' provider website; prefs.py: Appearance - Example 'Normal' text; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:827; prefs.py:625; prefs.py:762; stats.py:370; */ +/* EDMarketConnector.py: Label for 'Station' line in main UI; prefs.py: Configuration - Label for selection of 'Station' provider website; prefs.py: Appearance - Example 'Normal' text; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:827; prefs.py:625; prefs.py:762; stats.py:401; */ "Station" = "Station"; -/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:830; EDMarketConnector.py:845; EDMarketConnector.py:848; EDMarketConnector.py:2015; */ +/* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:830; EDMarketConnector.py:845; EDMarketConnector.py:848; EDMarketConnector.py:2019; */ "File" = "File"; /* EDMarketConnector.py: 'Edit' menu title on OSX; EDMarketConnector.py: 'Edit' menu title; In files: EDMarketConnector.py:831; EDMarketConnector.py:846; EDMarketConnector.py:849; */ @@ -88,7 +172,7 @@ /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:840; EDMarketConnector.py:854; */ "Save Raw Data..." = "Save Raw Data..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:841; EDMarketConnector.py:853; stats.py:364; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:841; EDMarketConnector.py:853; stats.py:395; */ "Status" = "Status"; /* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:842; EDMarketConnector.py:860; */ @@ -97,7 +181,7 @@ /* EDMarketConnector.py: Help > Release Notes; In files: EDMarketConnector.py:843; EDMarketConnector.py:861; EDMarketConnector.py:1600; */ "Release Notes" = "Release Notes"; -/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:855; EDMarketConnector.py:2016; prefs.py:255; */ +/* EDMarketConnector.py: File > Settings; prefs.py: File > Settings (macOS); In files: EDMarketConnector.py:855; EDMarketConnector.py:2020; prefs.py:255; */ "Settings" = "Settings"; /* EDMarketConnector.py: File > Exit; In files: EDMarketConnector.py:856; */ @@ -106,9 +190,6 @@ /* EDMarketConnector.py: Help > Documentation; In files: EDMarketConnector.py:859; */ "Documentation" = "Documentation"; -/* EDMarketConnector.py: Label for 'Copy' as in 'Copy and Paste'; ttkHyperlinkLabel.py: Label for 'Copy' as in 'Copy and Paste'; In files: EDMarketConnector.py:866; ttkHyperlinkLabel.py:42; */ -"Copy" = "Copy"; - /* EDMarketConnector.py: Status - Attempting to get a Frontier Auth Access Token; In files: EDMarketConnector.py:872; */ "Logging in..." = "Logging in..."; @@ -142,16 +223,13 @@ /* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:1006; */ "Fetching data..." = "Fetching data..."; -/* EDMarketConnector.py: No data was returned for the commander from the Frontier CAPI; In files: EDMarketConnector.py:1047; */ -"CAPI: No commander data returned" = "CAPI: No commander data returned"; - -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1051; stats.py:298; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1051; stats.py:323; */ "Who are you?!" = "Who are you?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1057; stats.py:308; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1057; stats.py:333; */ "Where are you?!" = "Where are you?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1064; stats.py:316; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1064; stats.py:341; */ "What are you flying?!" = "What are you flying?!"; /* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1176; */ @@ -181,126 +259,51 @@ /* EDMarketConnector.py: The application is shutting down; In files: EDMarketConnector.py:1693; */ "Shutting down..." = "Shutting down..."; -/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2004:2010; */ +/* EDMarketConnector.py: Popup-text about 'active' plugins without Python 3.x support; In files: EDMarketConnector.py:2008:2014; */ "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." = "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."; -/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2014; prefs.py:978; */ +/* EDMarketConnector.py: Settings > Plugins tab; prefs.py: Label on Settings > Plugins tab; In files: EDMarketConnector.py:2018; prefs.py:978; */ "Plugins" = "Plugins"; -/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2025; */ +/* EDMarketConnector.py: Popup window title for list of 'enabled' plugins that don't work with Python 3.x; In files: EDMarketConnector.py:2029; */ "EDMC: Plugins Without Python 3.x Support" = "EDMC: Plugins Without Python 3.x Support"; -/* journal_lock.py: Title text on popup when Journal directory already locked; In files: journal_lock.py:206; */ -"Journal directory already locked" = "Journal directory already locked"; +/* companion.py: Frontier CAPI didn't respond; In files: companion.py:218; */ +"Error: Frontier CAPI didn't respond" = "Error: Frontier CAPI didn't respond"; -/* journal_lock.py: Text for when newly selected Journal directory is already locked; In files: journal_lock.py:223:224; */ -"The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this." = "The new Journal Directory location is already locked.{CR}You can either attempt to resolve this and then Retry, or choose to Ignore this."; +/* companion.py: Frontier CAPI data doesn't agree with latest Journal game location; In files: companion.py:237; */ +"Error: Frontier server is lagging" = "Error: Frontier server is lagging"; -/* journal_lock.py: Generic 'Retry' button label; In files: journal_lock.py:228; */ -"Retry" = "Retry"; +/* companion.py: Commander is docked at an EDO settlement, got out and back in, we forgot the station; In files: companion.py:253; */ +"Docked but unknown station: EDO Settlement?" = "Docked but unknown station: EDO Settlement?"; -/* journal_lock.py: Generic 'Ignore' button label; In files: journal_lock.py:232; */ -"Ignore" = "Ignore"; +/* companion.py: Generic "something went wrong with Frontier Auth" error; In files: companion.py:263; */ +"Error: Invalid Credentials" = "Error: Invalid Credentials"; + +/* companion.py: Frontier CAPI authorisation not for currently game-active commander; In files: companion.py:288; */ +"Error: Wrong Cmdr" = "Error: Wrong Cmdr"; + +/* companion.py: Generic error prefix - following text is from Frontier auth service; In files: companion.py:414; companion.py:499; */ +"Error" = "Error"; + +/* companion.py: Frontier auth, no 'usr' section in returned data; companion.py: Frontier auth, no 'customer_id' in 'usr' section in returned data; In files: companion.py:457; companion.py:462; */ +"Error: Couldn't check token customer_id" = "Error: Couldn't check token customer_id"; + +/* companion.py: Frontier auth customer_id doesn't match game session FID; In files: companion.py:468; */ +"Error: customer_id doesn't match!" = "Error: customer_id doesn't match!"; + +/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:490; */ +"Error: unable to get token" = "Error: unable to get token"; + +/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:797; */ +"Frontier CAPI down for maintenance" = "Frontier CAPI down for maintenance"; + +/* companion.py: Frontier CAPI data retrieval failed; In files: companion.py:809; */ +"Frontier CAPI query failure" = "Frontier CAPI query failure"; /* l10n.py: The system default language choice in Settings > Appearance; prefs.py: Settings > Configuration - Label on 'reset journal files location to default' button; prefs.py: The system default language choice in Settings > Appearance; prefs.py: Label for 'Default' theme radio button; In files: l10n.py:194; prefs.py:468; prefs.py:702; prefs.py:735; */ "Default" = "Default"; -/* coriolis.py: 'Auto' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - auto; In files: coriolis.py:52; coriolis.py:55; coriolis.py:101; coriolis.py:117; coriolis.py:123; */ -"Auto" = "Auto"; - -/* coriolis.py: 'Normal' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - normal; In files: coriolis.py:53; coriolis.py:99; coriolis.py:115; */ -"Normal" = "Normal"; - -/* coriolis.py: 'Beta' label for Coriolis site override selection; coriolis.py: Coriolis normal/beta selection - beta; In files: coriolis.py:54; coriolis.py:100; coriolis.py:116; */ -"Beta" = "Beta"; - -/* coriolis.py: Settings>Coriolis: Help/hint for changing coriolis URLs; In files: coriolis.py:69:71; */ -"Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='" = "Set the URL to use with coriolis.io ship loadouts. Note that this MUST end with '/import?data='"; - -/* coriolis.py: Settings>Coriolis: Label for 'NOT alpha/beta game version' URL; In files: coriolis.py:75; */ -"Normal URL" = "Normal URL"; - -/* coriolis.py: Generic 'Reset' button label; In files: coriolis.py:78; coriolis.py:87; */ -"Reset" = "Reset"; - -/* coriolis.py: Settings>Coriolis: Label for 'alpha/beta game version' URL; In files: coriolis.py:84; */ -"Beta URL" = "Beta URL"; - -/* coriolis.py: Settings>Coriolis: Label for selection of using Normal, Beta or 'auto' Coriolis URL; In files: coriolis.py:94; */ -"Override Beta/Normal Selection" = "Override Beta/Normal Selection"; - -/* coriolis.py: Settings>Coriolis - invalid override mode found; In files: coriolis.py:134; */ -"Invalid Coriolis override mode!" = "Invalid Coriolis override mode!"; - -/* eddb.py: Journal Processing disabled due to an active killswitch; In files: eddb.py:100; */ -"EDDB Journal processing disabled. See Log." = "EDDB Journal processing disabled. See Log."; - -/* eddn.py: Status text shown while attempting to send data; In files: eddn.py:251; eddn.py:698; eddn.py:1438; */ -"Sending data to EDDN..." = "Sending data to EDDN..."; - -/* eddn.py: Error while trying to send data to EDDN; In files: eddn.py:315; eddn.py:1374; eddn.py:1409; eddn.py:1450; */ -"Error: Can't connect to EDDN" = "Error: Can't connect to EDDN"; - -/* eddn.py: EDDN has banned this version of our client; In files: eddn.py:333; */ -"EDDN Error: EDMC is too old for EDDN. Please update." = "EDDN Error: EDMC is too old for EDDN. Please update."; - -/* eddn.py: EDDN returned an error that indicates something about what we sent it was wrong; In files: eddn.py:339; */ -"EDDN Error: Validation Failed (EDMC Too Old?). See Log" = "EDDN Error: Validation Failed (EDMC Too Old?). See Log"; - -/* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:344; */ -"EDDN Error: Returned {STATUS} status code" = "EDDN Error: Returned {STATUS} status code"; - -/* eddn.py: No 'Route' found in NavRoute.json file; In files: eddn.py:970; */ -"No 'Route' array in NavRoute.json contents" = "No 'Route' array in NavRoute.json contents"; - -/* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1121; */ -"Send station data to the Elite Dangerous Data Network" = "Send station data to the Elite Dangerous Data Network"; - -/* eddn.py: Enable EDDN support for system and other scan data checkbox label; In files: eddn.py:1132; */ -"Send system and scan data to the Elite Dangerous Data Network" = "Send system and scan data to the Elite Dangerous Data Network"; - -/* eddn.py: EDDN delay sending until docked option is on, this message notes that a send was skipped due to this; In files: eddn.py:1143; */ -"Delay sending until docked" = "Delay sending until docked"; - -/* eddn.py: Killswitch disabled EDDN; In files: eddn.py:1232; */ -"EDDN journal handler disabled. See Log." = "EDDN journal handler disabled. See Log."; - -/* edsm.py: Settings>EDSM - Label on checkbox for 'send data'; In files: edsm.py:236; */ -"Send flight log and Cmdr status to EDSM" = "Send flight log and Cmdr status to EDSM"; - -/* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:246; */ -"Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map credentials"; - -/* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:265; */ -"Commander Name" = "Commander Name"; - -/* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:273; inara.py:243; */ -"API Key" = "API Key"; - -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:300; prefs.py:520; prefs.py:1174; prefs.py:1207; stats.py:119; stats.py:138; stats.py:157; stats.py:174; */ -"None" = "None"; - -/* edsm.py: EDSM plugin - Journal handling disabled by killswitch; In files: edsm.py:401; */ -"EDSM Handler disabled. See Log." = "EDSM Handler disabled. See Log."; - -/* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:746; edsm.py:874; */ -"Error: EDSM {MSG}" = "Error: EDSM {MSG}"; - -/* edsm.py: EDSM Plugin - Error connecting to EDSM API; In files: edsm.py:783; edsm.py:869; */ -"Error: Can't connect to EDSM" = "Error: Can't connect to EDSM"; - -/* inara.py: Checkbox to enable INARA API Usage; In files: inara.py:222; */ -"Send flight log and Cmdr status to Inara" = "Send flight log and Cmdr status to Inara"; - -/* inara.py: Text for INARA API keys link ( goes to https://inara.cz/settings-api ); In files: inara.py:234; */ -"Inara credentials" = "Inara credentials"; - -/* inara.py: INARA support disabled via killswitch; In files: inara.py:341; */ -"Inara disabled. See Log." = "Inara disabled. See Log."; - -/* inara.py: INARA API returned some kind of error (error message will be contained in {MSG}); In files: inara.py:1580; inara.py:1593; */ -"Error: Inara {MSG}" = "Error: Inara {MSG}"; - /* prefs.py: File > Preferences menu entry for macOS; In files: prefs.py:251; */ "Preferences" = "Preferences"; @@ -458,220 +461,271 @@ "Explorer" = "Explorer"; /* stats.py: Ranking; In files: stats.py:62; */ -"CQC" = "CQC"; +"Mercenary" = "Mercenary"; /* stats.py: Ranking; In files: stats.py:63; */ -"Federation" = "Federation"; +"Exobiologist" = "Exobiologist"; /* stats.py: Ranking; In files: stats.py:64; */ -"Empire" = "Empire"; +"CQC" = "CQC"; /* stats.py: Ranking; In files: stats.py:65; */ +"Federation" = "Federation"; + +/* stats.py: Ranking; In files: stats.py:66; */ +"Empire" = "Empire"; + +/* stats.py: Ranking; In files: stats.py:67; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:73; */ +/* stats.py: Combat rank; In files: stats.py:75; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:74; */ +/* stats.py: Combat rank; In files: stats.py:76; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:75; */ +/* stats.py: Combat rank; In files: stats.py:77; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:76; */ +/* stats.py: Combat rank; In files: stats.py:78; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:77; */ +/* stats.py: Combat rank; In files: stats.py:79; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:78; stats.py:141; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:80; stats.py:165; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:79; */ +/* stats.py: Combat rank; In files: stats.py:81; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:80; */ +/* stats.py: Combat rank; In files: stats.py:82; */ "Deadly" = "Deadly"; -/* stats.py: Top rank; In files: stats.py:81; stats.py:92; stats.py:103; stats.py:114; */ +/* stats.py: Top rank; In files: stats.py:83; stats.py:94; stats.py:105; stats.py:116; stats.py:127; stats.py:138; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:84; */ +/* stats.py: Trade rank; In files: stats.py:86; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:85; */ +/* stats.py: Trade rank; In files: stats.py:87; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Trade rank; In files: stats.py:88; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:89; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:90; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:91; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:90; */ +/* stats.py: Trade rank; In files: stats.py:92; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:91; */ +/* stats.py: Trade rank; In files: stats.py:93; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:95; */ +/* stats.py: Explorer rank; In files: stats.py:97; */ "Aimless" = "Aimless"; -/* stats.py: Explorer rank; In files: stats.py:96; */ +/* stats.py: Explorer rank; In files: stats.py:98; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:99; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:100; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:101; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:102; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:101; */ +/* stats.py: Explorer rank; In files: stats.py:103; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:102; */ +/* stats.py: Explorer rank; In files: stats.py:104; */ "Pioneer" = "Pioneer"; -/* stats.py: CQC rank; In files: stats.py:106; */ +/* stats.py: Mercenary rank; In files: stats.py:108; */ +"Defenceless" = "Defenceless"; + +/* stats.py: Mercenary rank; In files: stats.py:109; */ +"Mostly Defenceless" = "Mostly Defenceless"; + +/* stats.py: Mercenary rank; In files: stats.py:110; */ +"Rookie" = "Rookie"; + +/* stats.py: Mercenary rank; In files: stats.py:111; */ +"Soldier" = "Soldier"; + +/* stats.py: Mercenary rank; In files: stats.py:112; stats.py:114; */ +"Gunslinger" = "Gunslinger"; + +/* stats.py: Mercenary rank; In files: stats.py:113; */ +"Warrior" = "Warrior"; + +/* stats.py: Mercenary rank; In files: stats.py:115; */ +"Deadeye" = "Deadeye"; + +/* stats.py: Exobiologist rank; In files: stats.py:119; */ +"Directionless" = "Directionless"; + +/* stats.py: Exobiologist rank; In files: stats.py:120; */ +"Mostly Directionless" = "Mostly Directionless"; + +/* stats.py: Exobiologist rank; In files: stats.py:121; */ +"Compiler" = "Compiler"; + +/* stats.py: Exobiologist rank; In files: stats.py:122; */ +"Collector" = "Collector"; + +/* stats.py: Exobiologist rank; In files: stats.py:123; */ +"Cataloguer" = "Cataloguer"; + +/* stats.py: Exobiologist rank; In files: stats.py:124; */ +"Taxonomist" = "Taxonomist"; + +/* stats.py: Exobiologist rank; In files: stats.py:125; */ +"Ecologist" = "Ecologist"; + +/* stats.py: Exobiologist rank; In files: stats.py:126; */ +"Geneticist" = "Geneticist"; + +/* stats.py: CQC rank; In files: stats.py:130; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:107; */ +/* stats.py: CQC rank; In files: stats.py:131; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:108; */ +/* stats.py: CQC rank; In files: stats.py:132; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:109; */ +/* stats.py: CQC rank; In files: stats.py:133; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:110; */ +/* stats.py: CQC rank; In files: stats.py:134; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:111; */ +/* stats.py: CQC rank; In files: stats.py:135; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:112; */ +/* stats.py: CQC rank; In files: stats.py:136; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:113; */ +/* stats.py: CQC rank; In files: stats.py:137; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:120; */ +/* stats.py: Federation rank; In files: stats.py:144; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:121; */ +/* stats.py: Federation rank; In files: stats.py:145; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:122; */ +/* stats.py: Federation rank; In files: stats.py:146; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:123; */ +/* stats.py: Federation rank; In files: stats.py:147; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:124; */ +/* stats.py: Federation rank; In files: stats.py:148; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:125; */ +/* stats.py: Federation rank; In files: stats.py:149; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:126; */ +/* stats.py: Federation rank; In files: stats.py:150; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:127; */ +/* stats.py: Federation rank; In files: stats.py:151; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:128; */ +/* stats.py: Federation rank; In files: stats.py:152; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:129; */ +/* stats.py: Federation rank; In files: stats.py:153; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:130; */ +/* stats.py: Federation rank; In files: stats.py:154; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:131; */ +/* stats.py: Federation rank; In files: stats.py:155; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:132; */ +/* stats.py: Federation rank; In files: stats.py:156; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:133; */ +/* stats.py: Federation rank; In files: stats.py:157; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:139; */ +/* stats.py: Empire rank; In files: stats.py:163; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:140; */ +/* stats.py: Empire rank; In files: stats.py:164; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:142; */ +/* stats.py: Empire rank; In files: stats.py:166; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:143; */ +/* stats.py: Empire rank; In files: stats.py:167; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:144; */ +/* stats.py: Empire rank; In files: stats.py:168; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:145; */ +/* stats.py: Empire rank; In files: stats.py:169; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:146; */ +/* stats.py: Empire rank; In files: stats.py:170; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:147; */ +/* stats.py: Empire rank; In files: stats.py:171; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:148; */ +/* stats.py: Empire rank; In files: stats.py:172; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:149; */ +/* stats.py: Empire rank; In files: stats.py:173; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:150; */ +/* stats.py: Empire rank; In files: stats.py:174; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:151; */ +/* stats.py: Empire rank; In files: stats.py:175; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:152; */ +/* stats.py: Empire rank; In files: stats.py:176; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:158; */ +/* stats.py: Power rank; In files: stats.py:182; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:159; */ +/* stats.py: Power rank; In files: stats.py:183; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:160; */ +/* stats.py: Power rank; In files: stats.py:184; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:161; */ +/* stats.py: Power rank; In files: stats.py:185; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:162; */ +/* stats.py: Power rank; In files: stats.py:186; */ "Rating 5" = "Rating 5"; -/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:280; */ +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:305; */ "Status: Don't yet know your Commander name" = "Status: Don't yet know your Commander name"; -/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:288; */ +/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:313; */ "Status: No CAPI data yet" = "Status: No CAPI data yet"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:371; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:402; */ "Value" = "Value"; -/* stats.py: Status dialog title; In files: stats.py:380; */ +/* stats.py: Status dialog title; In files: stats.py:411; */ "Ships" = "Ships"; From 046c07e89c4dca4dcddd500a7ec955ad739d983b Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 25 Jan 2022 18:43:04 +0200 Subject: [PATCH 072/186] add Elite I-V --- L10n/en.template | 286 +++++++++++++++++++++++++---------------------- stats.py | 29 ++--- 2 files changed, 167 insertions(+), 148 deletions(-) diff --git a/L10n/en.template b/L10n/en.template index e1849115..0ee1c172 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -31,7 +31,7 @@ /* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:230; inara.py:233; load.py:230; load.py:233; edsm.py:274; inara.py:243; */ "API Key" = "API Key"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:256; load.py:256; edsm.py:301; prefs.py:520; prefs.py:1174; prefs.py:1207; stats.py:143; stats.py:162; stats.py:181; stats.py:199; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:256; load.py:256; edsm.py:301; prefs.py:520; prefs.py:1174; prefs.py:1207; stats.py:147; stats.py:166; stats.py:185; stats.py:203; */ "None" = "None"; /* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:622; edsm.py:724; load.py:627; load.py:729; edsm.py:747; edsm.py:875; */ @@ -136,16 +136,16 @@ /* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: Multicrew role label in main window; In files: EDMarketConnector.py:824; EDMarketConnector.py:1279; */ "Role" = "Role"; -/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: 'Ship' label in main UI; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:824; EDMarketConnector.py:1289; EDMarketConnector.py:1312; stats.py:398; */ +/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: 'Ship' label in main UI; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:824; EDMarketConnector.py:1289; EDMarketConnector.py:1312; stats.py:402; */ "Ship" = "Ship"; /* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:825; */ "Suit" = "Suit"; -/* EDMarketConnector.py: Label for 'System' line in main UI; prefs.py: Configuration - Label for selection of 'System' provider website; stats.py: Main window; In files: EDMarketConnector.py:826; prefs.py:607; stats.py:400; */ +/* EDMarketConnector.py: Label for 'System' line in main UI; prefs.py: Configuration - Label for selection of 'System' provider website; stats.py: Main window; In files: EDMarketConnector.py:826; prefs.py:607; stats.py:404; */ "System" = "System"; -/* EDMarketConnector.py: Label for 'Station' line in main UI; prefs.py: Configuration - Label for selection of 'Station' provider website; prefs.py: Appearance - Example 'Normal' text; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:827; prefs.py:625; prefs.py:762; stats.py:401; */ +/* EDMarketConnector.py: Label for 'Station' line in main UI; prefs.py: Configuration - Label for selection of 'Station' provider website; prefs.py: Appearance - Example 'Normal' text; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:827; prefs.py:625; prefs.py:762; stats.py:405; */ "Station" = "Station"; /* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:830; EDMarketConnector.py:845; EDMarketConnector.py:848; EDMarketConnector.py:2019; */ @@ -172,7 +172,7 @@ /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:840; EDMarketConnector.py:854; */ "Save Raw Data..." = "Save Raw Data..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:841; EDMarketConnector.py:853; stats.py:395; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:841; EDMarketConnector.py:853; stats.py:399; */ "Status" = "Status"; /* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:842; EDMarketConnector.py:860; */ @@ -223,13 +223,13 @@ /* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:1006; */ "Fetching data..." = "Fetching data..."; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1051; stats.py:323; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1051; stats.py:327; */ "Who are you?!" = "Who are you?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1057; stats.py:333; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1057; stats.py:337; */ "Where are you?!" = "Where are you?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1064; stats.py:341; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1064; stats.py:345; */ "What are you flying?!" = "What are you flying?!"; /* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1176; */ @@ -451,281 +451,297 @@ /* stats.py: Cmdr stats; In files: stats.py:54; */ "Loan" = "Loan"; -/* stats.py: Ranking; In files: stats.py:59; */ -"Combat" = "Combat"; - -/* stats.py: Ranking; In files: stats.py:60; */ -"Trade" = "Trade"; - -/* stats.py: Ranking; In files: stats.py:61; */ -"Explorer" = "Explorer"; - -/* stats.py: Ranking; In files: stats.py:62; */ -"Mercenary" = "Mercenary"; - -/* stats.py: Ranking; In files: stats.py:63; */ -"Exobiologist" = "Exobiologist"; - -/* stats.py: Ranking; In files: stats.py:64; */ -"CQC" = "CQC"; - -/* stats.py: Ranking; In files: stats.py:65; */ -"Federation" = "Federation"; - -/* stats.py: Ranking; In files: stats.py:66; */ -"Empire" = "Empire"; - -/* stats.py: Ranking; In files: stats.py:67; */ -"Powerplay" = "Powerplay"; - -/* stats.py: Combat rank; In files: stats.py:75; */ -"Harmless" = "Harmless"; - -/* stats.py: Combat rank; In files: stats.py:76; */ -"Mostly Harmless" = "Mostly Harmless"; - -/* stats.py: Combat rank; In files: stats.py:77; */ -"Novice" = "Novice"; - -/* stats.py: Combat rank; In files: stats.py:78; */ -"Competent" = "Competent"; - -/* stats.py: Combat rank; In files: stats.py:79; */ -"Expert" = "Expert"; - -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:80; stats.py:165; */ -"Master" = "Master"; - -/* stats.py: Combat rank; In files: stats.py:81; */ -"Dangerous" = "Dangerous"; - -/* stats.py: Combat rank; In files: stats.py:82; */ -"Deadly" = "Deadly"; - -/* stats.py: Top rank; In files: stats.py:83; stats.py:94; stats.py:105; stats.py:116; stats.py:127; stats.py:138; */ +/* stats.py: Top rank; In files: stats.py:58; */ "Elite" = "Elite"; -/* stats.py: Trade rank; In files: stats.py:86; */ +/* stats.py: Top rank +1; In files: stats.py:59; */ +"Elite I" = "Elite I"; + +/* stats.py: Top rank +2; In files: stats.py:60; */ +"Elite II" = "Elite II"; + +/* stats.py: Top rank +3; In files: stats.py:61; */ +"Elite III" = "Elite III"; + +/* stats.py: Top rank +4; In files: stats.py:62; */ +"Elite IV" = "Elite IV"; + +/* stats.py: Top rank +5; In files: stats.py:63; */ +"Elite V" = "Elite V"; + +/* stats.py: Ranking; In files: stats.py:68; */ +"Combat" = "Combat"; + +/* stats.py: Ranking; In files: stats.py:69; */ +"Trade" = "Trade"; + +/* stats.py: Ranking; In files: stats.py:70; */ +"Explorer" = "Explorer"; + +/* stats.py: Ranking; In files: stats.py:71; */ +"Mercenary" = "Mercenary"; + +/* stats.py: Ranking; In files: stats.py:72; */ +"Exobiologist" = "Exobiologist"; + +/* stats.py: Ranking; In files: stats.py:73; */ +"CQC" = "CQC"; + +/* stats.py: Ranking; In files: stats.py:74; */ +"Federation" = "Federation"; + +/* stats.py: Ranking; In files: stats.py:75; */ +"Empire" = "Empire"; + +/* stats.py: Ranking; In files: stats.py:76; */ +"Powerplay" = "Powerplay"; + +/* stats.py: Combat rank; In files: stats.py:84; */ +"Harmless" = "Harmless"; + +/* stats.py: Combat rank; In files: stats.py:85; */ +"Mostly Harmless" = "Mostly Harmless"; + +/* stats.py: Combat rank; In files: stats.py:86; */ +"Novice" = "Novice"; + +/* stats.py: Combat rank; In files: stats.py:87; */ +"Competent" = "Competent"; + +/* stats.py: Combat rank; In files: stats.py:88; */ +"Expert" = "Expert"; + +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:89; stats.py:169; */ +"Master" = "Master"; + +/* stats.py: Combat rank; In files: stats.py:90; */ +"Dangerous" = "Dangerous"; + +/* stats.py: Combat rank; In files: stats.py:91; */ +"Deadly" = "Deadly"; + +/* stats.py: Trade rank; In files: stats.py:94; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:87; */ +/* stats.py: Trade rank; In files: stats.py:95; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:88; */ +/* stats.py: Trade rank; In files: stats.py:96; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:89; */ +/* stats.py: Trade rank; In files: stats.py:97; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:90; */ +/* stats.py: Trade rank; In files: stats.py:98; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:91; */ +/* stats.py: Trade rank; In files: stats.py:99; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:92; */ +/* stats.py: Trade rank; In files: stats.py:100; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:93; */ +/* stats.py: Trade rank; In files: stats.py:101; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:97; */ +/* stats.py: Explorer rank; In files: stats.py:104; */ "Aimless" = "Aimless"; -/* stats.py: Explorer rank; In files: stats.py:98; */ +/* stats.py: Explorer rank; In files: stats.py:105; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:99; */ +/* stats.py: Explorer rank; In files: stats.py:106; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:100; */ +/* stats.py: Explorer rank; In files: stats.py:107; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:101; */ +/* stats.py: Explorer rank; In files: stats.py:108; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:102; */ +/* stats.py: Explorer rank; In files: stats.py:109; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:103; */ +/* stats.py: Explorer rank; In files: stats.py:110; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:104; */ +/* stats.py: Explorer rank; In files: stats.py:111; */ "Pioneer" = "Pioneer"; -/* stats.py: Mercenary rank; In files: stats.py:108; */ +/* stats.py: Mercenary rank; In files: stats.py:115; */ "Defenceless" = "Defenceless"; -/* stats.py: Mercenary rank; In files: stats.py:109; */ +/* stats.py: Mercenary rank; In files: stats.py:116; */ "Mostly Defenceless" = "Mostly Defenceless"; -/* stats.py: Mercenary rank; In files: stats.py:110; */ +/* stats.py: Mercenary rank; In files: stats.py:117; */ "Rookie" = "Rookie"; -/* stats.py: Mercenary rank; In files: stats.py:111; */ +/* stats.py: Mercenary rank; In files: stats.py:118; */ "Soldier" = "Soldier"; -/* stats.py: Mercenary rank; In files: stats.py:112; stats.py:114; */ +/* stats.py: Mercenary rank; In files: stats.py:119; stats.py:121; */ "Gunslinger" = "Gunslinger"; -/* stats.py: Mercenary rank; In files: stats.py:113; */ +/* stats.py: Mercenary rank; In files: stats.py:120; */ "Warrior" = "Warrior"; -/* stats.py: Mercenary rank; In files: stats.py:115; */ +/* stats.py: Mercenary rank; In files: stats.py:122; */ "Deadeye" = "Deadeye"; -/* stats.py: Exobiologist rank; In files: stats.py:119; */ +/* stats.py: Exobiologist rank; In files: stats.py:125; */ "Directionless" = "Directionless"; -/* stats.py: Exobiologist rank; In files: stats.py:120; */ +/* stats.py: Exobiologist rank; In files: stats.py:126; */ "Mostly Directionless" = "Mostly Directionless"; -/* stats.py: Exobiologist rank; In files: stats.py:121; */ +/* stats.py: Exobiologist rank; In files: stats.py:127; */ "Compiler" = "Compiler"; -/* stats.py: Exobiologist rank; In files: stats.py:122; */ +/* stats.py: Exobiologist rank; In files: stats.py:128; */ "Collector" = "Collector"; -/* stats.py: Exobiologist rank; In files: stats.py:123; */ +/* stats.py: Exobiologist rank; In files: stats.py:129; */ "Cataloguer" = "Cataloguer"; -/* stats.py: Exobiologist rank; In files: stats.py:124; */ +/* stats.py: Exobiologist rank; In files: stats.py:130; */ "Taxonomist" = "Taxonomist"; -/* stats.py: Exobiologist rank; In files: stats.py:125; */ +/* stats.py: Exobiologist rank; In files: stats.py:131; */ "Ecologist" = "Ecologist"; -/* stats.py: Exobiologist rank; In files: stats.py:126; */ +/* stats.py: Exobiologist rank; In files: stats.py:132; */ "Geneticist" = "Geneticist"; -/* stats.py: CQC rank; In files: stats.py:130; */ +/* stats.py: CQC rank; In files: stats.py:135; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:131; */ +/* stats.py: CQC rank; In files: stats.py:136; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:132; */ +/* stats.py: CQC rank; In files: stats.py:137; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:133; */ +/* stats.py: CQC rank; In files: stats.py:138; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:134; */ +/* stats.py: CQC rank; In files: stats.py:139; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:135; */ +/* stats.py: CQC rank; In files: stats.py:140; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:136; */ +/* stats.py: CQC rank; In files: stats.py:141; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:137; */ +/* stats.py: CQC rank; In files: stats.py:142; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:144; */ +/* stats.py: Federation rank; In files: stats.py:148; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:145; */ +/* stats.py: Federation rank; In files: stats.py:149; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:146; */ +/* stats.py: Federation rank; In files: stats.py:150; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:147; */ +/* stats.py: Federation rank; In files: stats.py:151; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:148; */ +/* stats.py: Federation rank; In files: stats.py:152; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:149; */ +/* stats.py: Federation rank; In files: stats.py:153; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:150; */ +/* stats.py: Federation rank; In files: stats.py:154; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:151; */ +/* stats.py: Federation rank; In files: stats.py:155; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:152; */ +/* stats.py: Federation rank; In files: stats.py:156; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:153; */ +/* stats.py: Federation rank; In files: stats.py:157; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:154; */ +/* stats.py: Federation rank; In files: stats.py:158; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:155; */ +/* stats.py: Federation rank; In files: stats.py:159; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:156; */ +/* stats.py: Federation rank; In files: stats.py:160; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:157; */ +/* stats.py: Federation rank; In files: stats.py:161; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:163; */ +/* stats.py: Empire rank; In files: stats.py:167; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:164; */ +/* stats.py: Empire rank; In files: stats.py:168; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:166; */ +/* stats.py: Empire rank; In files: stats.py:170; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:167; */ +/* stats.py: Empire rank; In files: stats.py:171; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:168; */ +/* stats.py: Empire rank; In files: stats.py:172; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:169; */ +/* stats.py: Empire rank; In files: stats.py:173; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:170; */ +/* stats.py: Empire rank; In files: stats.py:174; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:171; */ +/* stats.py: Empire rank; In files: stats.py:175; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:172; */ +/* stats.py: Empire rank; In files: stats.py:176; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:173; */ +/* stats.py: Empire rank; In files: stats.py:177; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:174; */ +/* stats.py: Empire rank; In files: stats.py:178; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:175; */ +/* stats.py: Empire rank; In files: stats.py:179; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:176; */ +/* stats.py: Empire rank; In files: stats.py:180; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:182; */ +/* stats.py: Power rank; In files: stats.py:186; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:183; */ +/* stats.py: Power rank; In files: stats.py:187; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:184; */ +/* stats.py: Power rank; In files: stats.py:188; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:185; */ +/* stats.py: Power rank; In files: stats.py:189; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:186; */ +/* stats.py: Power rank; In files: stats.py:190; */ "Rating 5" = "Rating 5"; -/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:305; */ +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:309; */ "Status: Don't yet know your Commander name" = "Status: Don't yet know your Commander name"; -/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:313; */ +/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:317; */ "Status: No CAPI data yet" = "Status: No CAPI data yet"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:402; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:406; */ "Value" = "Value"; -/* stats.py: Status dialog title; In files: stats.py:411; */ +/* stats.py: Status dialog title; In files: stats.py:415; */ "Ships" = "Ships"; + diff --git a/stats.py b/stats.py index f48142aa..5a7d4641 100644 --- a/stats.py +++ b/stats.py @@ -54,6 +54,15 @@ def status(data: Dict[str, Any]) -> List[List[str]]: [_('Loan'), str(data['commander'].get('debt', 0))], # LANG: Cmdr stats ] + _ELITE_RANKS = [ + _('Elite'), # LANG: Top rank + _('Elite I'), # LANG: Top rank +1 + _('Elite II'), # LANG: Top rank +2 + _('Elite III'), # LANG: Top rank +3 + _('Elite IV'), # LANG: Top rank +4 + _('Elite V'), # LANG: Top rank +5 + ] + RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime # in output order (_('Combat'), 'combat'), # LANG: Ranking @@ -80,8 +89,7 @@ def status(data: Dict[str, Any]) -> List[List[str]]: _('Master'), # LANG: Combat rank _('Dangerous'), # LANG: Combat rank _('Deadly'), # LANG: Combat rank - _('Elite'), # LANG: Top rank - ], + ] + _ELITE_RANKS, 'trade': [ _('Penniless'), # LANG: Trade rank _('Mostly Penniless'), # LANG: Trade rank @@ -91,8 +99,7 @@ def status(data: Dict[str, Any]) -> List[List[str]]: _('Broker'), # LANG: Trade rank _('Entrepreneur'), # LANG: Trade rank _('Tycoon'), # LANG: Trade rank - _('Elite') # LANG: Top rank - ], + ] + _ELITE_RANKS, 'explore': [ _('Aimless'), # LANG: Explorer rank _('Mostly Aimless'), # LANG: Explorer rank @@ -102,8 +109,8 @@ def status(data: Dict[str, Any]) -> List[List[str]]: _('Pathfinder'), # LANG: Explorer rank _('Ranger'), # LANG: Explorer rank _('Pioneer'), # LANG: Explorer rank - _('Elite') # LANG: Top rank - ], + + ] + _ELITE_RANKS, 'mercenary': [ _('Defenceless'), # LANG: Mercenary rank _('Mostly Defenceless'), # LANG: Mercenary rank @@ -113,8 +120,7 @@ def status(data: Dict[str, Any]) -> List[List[str]]: _('Warrior'), # LANG: Mercenary rank _('Gunslinger'), # LANG: Mercenary rank _('Deadeye'), # LANG: Mercenary rank - _('Elite'), # LANG: Top rank - ], + ] + _ELITE_RANKS, 'exobiologist': [ _('Directionless'), # LANG: Exobiologist rank _('Mostly Directionless'), # LANG: Exobiologist rank @@ -124,8 +130,7 @@ def status(data: Dict[str, Any]) -> List[List[str]]: _('Taxonomist'), # LANG: Exobiologist rank _('Ecologist'), # LANG: Exobiologist rank _('Geneticist'), # LANG: Exobiologist rank - _('Elite'), # LANG: Top rank - ], + ] + _ELITE_RANKS, 'cqc': [ _('Helpless'), # LANG: CQC rank _('Mostly Helpless'), # LANG: CQC rank @@ -135,8 +140,7 @@ def status(data: Dict[str, Any]) -> List[List[str]]: _('Champion'), # LANG: CQC rank _('Hero'), # LANG: CQC rank _('Gladiator'), # LANG: CQC rank - _('Elite') # LANG: Top rank - ], + ] + _ELITE_RANKS, # http://elite-dangerous.wikia.com/wiki/Federation#Ranks 'federation': [ @@ -191,7 +195,6 @@ def status(data: Dict[str, Any]) -> List[List[str]]: for title, thing in RANKS: rank = ranks.get(thing) names = RANK_NAMES[thing] - # TODO: handle Elite I-V here. if isinstance(rank, int): res.append([title, names[rank] if rank < len(names) else f'Rank {rank}']) From cf4c2d18f1a9c094f216a8231770933a5eebaf2b Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 25 Jan 2022 18:54:42 +0200 Subject: [PATCH 073/186] silence flake8 N806 --- stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stats.py b/stats.py index 5a7d4641..6a8de02f 100644 --- a/stats.py +++ b/stats.py @@ -54,7 +54,7 @@ def status(data: Dict[str, Any]) -> List[List[str]]: [_('Loan'), str(data['commander'].get('debt', 0))], # LANG: Cmdr stats ] - _ELITE_RANKS = [ + _ELITE_RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime _('Elite'), # LANG: Top rank _('Elite I'), # LANG: Top rank +1 _('Elite II'), # LANG: Top rank +2 From 2a4199e098dfe2e2eed4538baf14f232b4a63ef9 Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 25 Jan 2022 19:13:32 +0200 Subject: [PATCH 074/186] de-magic-number row splitting --- stats.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/stats.py b/stats.py index 6a8de02f..563c7697 100644 --- a/stats.py +++ b/stats.py @@ -40,6 +40,13 @@ if platform == 'win32': CalculatePopupWindowPosition = None # type: ignore +CR_LINES_START = 1 +CR_LINES_END = 3 +RANK_LINES_START = 3 +RANK_LINES_END = 9 +POWERPLAY_LINES_START = 9 + + def status(data: Dict[str, Any]) -> List[List[str]]: """ Get the current status of the cmdr referred to by data. @@ -381,17 +388,16 @@ class StatsResults(tk.Toplevel): notebook = nb.Notebook(frame) page = self.addpage(notebook) - for thing in stats[1:3]: + for thing in stats[CR_LINES_START:CR_LINES_END]: # assumes things two and three are money self.addpagerow(page, [thing[0], self.credits(int(thing[1]))], with_copy=True) - # TODO: Headers, and a bit saner of some splitting up of things here self.addpagespacer(page) - for thing in stats[3:9]: + for thing in stats[RANK_LINES_START:RANK_LINES_END]: self.addpagerow(page, thing, with_copy=True) self.addpagespacer(page) - for thing in stats[9:]: + for thing in stats[POWERPLAY_LINES_START:]: self.addpagerow(page, thing, with_copy=True) ttk.Frame(page).grid(pady=5) # bottom spacer From ddcb5219b4425b932382f4e2c10b2747247ec347 Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 25 Jan 2022 19:23:22 +0200 Subject: [PATCH 075/186] fix mercenary rank, add some comments --- L10n/en.template | 218 +++++++++++++++++++++++------------------------ stats.py | 6 +- 2 files changed, 113 insertions(+), 111 deletions(-) diff --git a/L10n/en.template b/L10n/en.template index 0ee1c172..f6ebd8e1 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -22,7 +22,7 @@ /* edsm.py: Settings>EDSM - Label on header/URL to EDSM API key page; In files: edsm.py:206; load.py:206; edsm.py:247; */ "Elite Dangerous Star Map credentials" = "Elite Dangerous Star Map credentials"; -/* edsm.py: Game Commander name label in EDSM settings; theme.py: Label for commander name in main window; EDMarketConnector.py: Label for commander name in main window; stats.py: Cmdr stats; In files: edsm.py:216; load.py:216; edsm.py:258; theme.py:227; EDMarketConnector.py:822; stats.py:52; */ +/* edsm.py: Game Commander name label in EDSM settings; theme.py: Label for commander name in main window; EDMarketConnector.py: Label for commander name in main window; stats.py: Cmdr stats; In files: edsm.py:216; load.py:216; edsm.py:258; theme.py:227; EDMarketConnector.py:822; stats.py:59; */ "Cmdr" = "Cmdr"; /* edsm.py: EDSM Commander name label in EDSM settings; In files: edsm.py:223; load.py:223; edsm.py:266; */ @@ -31,7 +31,7 @@ /* edsm.py: EDSM API key label; inara.py: Inara API key label; In files: edsm.py:230; inara.py:233; load.py:230; load.py:233; edsm.py:274; inara.py:243; */ "API Key" = "API Key"; -/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:256; load.py:256; edsm.py:301; prefs.py:520; prefs.py:1174; prefs.py:1207; stats.py:147; stats.py:166; stats.py:185; stats.py:203; */ +/* edsm.py: We have no data on the current commander; prefs.py: No hotkey/shortcut set; stats.py: No rank; In files: edsm.py:256; load.py:256; edsm.py:301; prefs.py:520; prefs.py:1174; prefs.py:1207; stats.py:156; stats.py:175; stats.py:194; stats.py:211; */ "None" = "None"; /* edsm.py: EDSM Plugin - Error message from EDSM API; In files: edsm.py:622; edsm.py:724; load.py:627; load.py:729; edsm.py:747; edsm.py:875; */ @@ -136,16 +136,16 @@ /* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: Multicrew role label in main window; In files: EDMarketConnector.py:824; EDMarketConnector.py:1279; */ "Role" = "Role"; -/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: 'Ship' label in main UI; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:824; EDMarketConnector.py:1289; EDMarketConnector.py:1312; stats.py:402; */ +/* EDMarketConnector.py: 'Ship' or multi-crew role label in main window, as applicable; EDMarketConnector.py: 'Ship' label in main UI; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:824; EDMarketConnector.py:1289; EDMarketConnector.py:1312; stats.py:409; */ "Ship" = "Ship"; /* EDMarketConnector.py: Label for 'Suit' line in main UI; In files: EDMarketConnector.py:825; */ "Suit" = "Suit"; -/* EDMarketConnector.py: Label for 'System' line in main UI; prefs.py: Configuration - Label for selection of 'System' provider website; stats.py: Main window; In files: EDMarketConnector.py:826; prefs.py:607; stats.py:404; */ +/* EDMarketConnector.py: Label for 'System' line in main UI; prefs.py: Configuration - Label for selection of 'System' provider website; stats.py: Main window; In files: EDMarketConnector.py:826; prefs.py:607; stats.py:411; */ "System" = "System"; -/* EDMarketConnector.py: Label for 'Station' line in main UI; prefs.py: Configuration - Label for selection of 'Station' provider website; prefs.py: Appearance - Example 'Normal' text; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:827; prefs.py:625; prefs.py:762; stats.py:405; */ +/* EDMarketConnector.py: Label for 'Station' line in main UI; prefs.py: Configuration - Label for selection of 'Station' provider website; prefs.py: Appearance - Example 'Normal' text; stats.py: Status dialog subtitle; In files: EDMarketConnector.py:827; prefs.py:625; prefs.py:762; stats.py:412; */ "Station" = "Station"; /* EDMarketConnector.py: 'File' menu title on OSX; EDMarketConnector.py: 'File' menu title; EDMarketConnector.py: 'File' menu; In files: EDMarketConnector.py:830; EDMarketConnector.py:845; EDMarketConnector.py:848; EDMarketConnector.py:2019; */ @@ -172,7 +172,7 @@ /* EDMarketConnector.py: File > Save Raw Data...; In files: EDMarketConnector.py:840; EDMarketConnector.py:854; */ "Save Raw Data..." = "Save Raw Data..."; -/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:841; EDMarketConnector.py:853; stats.py:399; */ +/* EDMarketConnector.py: File > Status; stats.py: Status dialog title; In files: EDMarketConnector.py:841; EDMarketConnector.py:853; stats.py:406; */ "Status" = "Status"; /* EDMarketConnector.py: Help > Privacy Policy; In files: EDMarketConnector.py:842; EDMarketConnector.py:860; */ @@ -223,13 +223,13 @@ /* EDMarketConnector.py: Status - Attempting to retrieve data from Frontier CAPI; In files: EDMarketConnector.py:1006; */ "Fetching data..." = "Fetching data..."; -/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1051; stats.py:327; */ +/* EDMarketConnector.py: We didn't have the commander name when we should have; stats.py: Unknown commander; In files: EDMarketConnector.py:1051; stats.py:335; */ "Who are you?!" = "Who are you?!"; -/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1057; stats.py:337; */ +/* EDMarketConnector.py: We don't know where the commander is, when we should; stats.py: Unknown location; In files: EDMarketConnector.py:1057; stats.py:345; */ "Where are you?!" = "Where are you?!"; -/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1064; stats.py:345; */ +/* EDMarketConnector.py: We don't know what ship the commander is in, when we should; stats.py: Unknown ship; In files: EDMarketConnector.py:1064; stats.py:353; */ "What are you flying?!" = "What are you flying?!"; /* EDMarketConnector.py: Frontier CAPI server error when fetching data; In files: EDMarketConnector.py:1176; */ @@ -445,303 +445,303 @@ /* prefs.py: Lable on list of user-disabled plugins; In files: prefs.py:969; */ "Disabled Plugins" = "Disabled Plugins"; -/* stats.py: Cmdr stats; In files: stats.py:53; */ +/* stats.py: Cmdr stats; In files: stats.py:60; */ "Balance" = "Balance"; -/* stats.py: Cmdr stats; In files: stats.py:54; */ +/* stats.py: Cmdr stats; In files: stats.py:61; */ "Loan" = "Loan"; -/* stats.py: Top rank; In files: stats.py:58; */ +/* stats.py: Top rank; In files: stats.py:65; */ "Elite" = "Elite"; -/* stats.py: Top rank +1; In files: stats.py:59; */ +/* stats.py: Top rank +1; In files: stats.py:66; */ "Elite I" = "Elite I"; -/* stats.py: Top rank +2; In files: stats.py:60; */ +/* stats.py: Top rank +2; In files: stats.py:67; */ "Elite II" = "Elite II"; -/* stats.py: Top rank +3; In files: stats.py:61; */ +/* stats.py: Top rank +3; In files: stats.py:68; */ "Elite III" = "Elite III"; -/* stats.py: Top rank +4; In files: stats.py:62; */ +/* stats.py: Top rank +4; In files: stats.py:69; */ "Elite IV" = "Elite IV"; -/* stats.py: Top rank +5; In files: stats.py:63; */ +/* stats.py: Top rank +5; In files: stats.py:70; */ "Elite V" = "Elite V"; -/* stats.py: Ranking; In files: stats.py:68; */ +/* stats.py: Ranking; In files: stats.py:76; */ "Combat" = "Combat"; -/* stats.py: Ranking; In files: stats.py:69; */ +/* stats.py: Ranking; In files: stats.py:77; */ "Trade" = "Trade"; -/* stats.py: Ranking; In files: stats.py:70; */ +/* stats.py: Ranking; In files: stats.py:78; */ "Explorer" = "Explorer"; -/* stats.py: Ranking; In files: stats.py:71; */ +/* stats.py: Ranking; In files: stats.py:79; */ "Mercenary" = "Mercenary"; -/* stats.py: Ranking; In files: stats.py:72; */ +/* stats.py: Ranking; In files: stats.py:80; */ "Exobiologist" = "Exobiologist"; -/* stats.py: Ranking; In files: stats.py:73; */ +/* stats.py: Ranking; In files: stats.py:81; */ "CQC" = "CQC"; -/* stats.py: Ranking; In files: stats.py:74; */ +/* stats.py: Ranking; In files: stats.py:82; */ "Federation" = "Federation"; -/* stats.py: Ranking; In files: stats.py:75; */ +/* stats.py: Ranking; In files: stats.py:83; */ "Empire" = "Empire"; -/* stats.py: Ranking; In files: stats.py:76; */ +/* stats.py: Ranking; In files: stats.py:84; */ "Powerplay" = "Powerplay"; -/* stats.py: Combat rank; In files: stats.py:84; */ +/* stats.py: Combat rank; In files: stats.py:93; */ "Harmless" = "Harmless"; -/* stats.py: Combat rank; In files: stats.py:85; */ +/* stats.py: Combat rank; In files: stats.py:94; */ "Mostly Harmless" = "Mostly Harmless"; -/* stats.py: Combat rank; In files: stats.py:86; */ +/* stats.py: Combat rank; In files: stats.py:95; */ "Novice" = "Novice"; -/* stats.py: Combat rank; In files: stats.py:87; */ +/* stats.py: Combat rank; In files: stats.py:96; */ "Competent" = "Competent"; -/* stats.py: Combat rank; In files: stats.py:88; */ +/* stats.py: Combat rank; In files: stats.py:97; */ "Expert" = "Expert"; -/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:89; stats.py:169; */ +/* stats.py: Combat rank; stats.py: Empire rank; In files: stats.py:98; stats.py:178; */ "Master" = "Master"; -/* stats.py: Combat rank; In files: stats.py:90; */ +/* stats.py: Combat rank; In files: stats.py:99; */ "Dangerous" = "Dangerous"; -/* stats.py: Combat rank; In files: stats.py:91; */ +/* stats.py: Combat rank; In files: stats.py:100; */ "Deadly" = "Deadly"; -/* stats.py: Trade rank; In files: stats.py:94; */ +/* stats.py: Trade rank; In files: stats.py:103; */ "Penniless" = "Penniless"; -/* stats.py: Trade rank; In files: stats.py:95; */ +/* stats.py: Trade rank; In files: stats.py:104; */ "Mostly Penniless" = "Mostly Penniless"; -/* stats.py: Trade rank; In files: stats.py:96; */ +/* stats.py: Trade rank; In files: stats.py:105; */ "Peddler" = "Peddler"; -/* stats.py: Trade rank; In files: stats.py:97; */ +/* stats.py: Trade rank; In files: stats.py:106; */ "Dealer" = "Dealer"; -/* stats.py: Trade rank; In files: stats.py:98; */ +/* stats.py: Trade rank; In files: stats.py:107; */ "Merchant" = "Merchant"; -/* stats.py: Trade rank; In files: stats.py:99; */ +/* stats.py: Trade rank; In files: stats.py:108; */ "Broker" = "Broker"; -/* stats.py: Trade rank; In files: stats.py:100; */ +/* stats.py: Trade rank; In files: stats.py:109; */ "Entrepreneur" = "Entrepreneur"; -/* stats.py: Trade rank; In files: stats.py:101; */ +/* stats.py: Trade rank; In files: stats.py:110; */ "Tycoon" = "Tycoon"; -/* stats.py: Explorer rank; In files: stats.py:104; */ +/* stats.py: Explorer rank; In files: stats.py:113; */ "Aimless" = "Aimless"; -/* stats.py: Explorer rank; In files: stats.py:105; */ +/* stats.py: Explorer rank; In files: stats.py:114; */ "Mostly Aimless" = "Mostly Aimless"; -/* stats.py: Explorer rank; In files: stats.py:106; */ +/* stats.py: Explorer rank; In files: stats.py:115; */ "Scout" = "Scout"; -/* stats.py: Explorer rank; In files: stats.py:107; */ +/* stats.py: Explorer rank; In files: stats.py:116; */ "Surveyor" = "Surveyor"; -/* stats.py: Explorer rank; In files: stats.py:108; */ +/* stats.py: Explorer rank; In files: stats.py:117; */ "Trailblazer" = "Trailblazer"; -/* stats.py: Explorer rank; In files: stats.py:109; */ +/* stats.py: Explorer rank; In files: stats.py:118; */ "Pathfinder" = "Pathfinder"; -/* stats.py: Explorer rank; In files: stats.py:110; */ +/* stats.py: Explorer rank; In files: stats.py:119; */ "Ranger" = "Ranger"; -/* stats.py: Explorer rank; In files: stats.py:111; */ +/* stats.py: Explorer rank; In files: stats.py:120; */ "Pioneer" = "Pioneer"; -/* stats.py: Mercenary rank; In files: stats.py:115; */ +/* stats.py: Mercenary rank; In files: stats.py:124; */ "Defenceless" = "Defenceless"; -/* stats.py: Mercenary rank; In files: stats.py:116; */ +/* stats.py: Mercenary rank; In files: stats.py:125; */ "Mostly Defenceless" = "Mostly Defenceless"; -/* stats.py: Mercenary rank; In files: stats.py:117; */ +/* stats.py: Mercenary rank; In files: stats.py:126; */ "Rookie" = "Rookie"; -/* stats.py: Mercenary rank; In files: stats.py:118; */ +/* stats.py: Mercenary rank; In files: stats.py:127; */ "Soldier" = "Soldier"; -/* stats.py: Mercenary rank; In files: stats.py:119; stats.py:121; */ +/* stats.py: Mercenary rank; In files: stats.py:128; stats.py:130; */ "Gunslinger" = "Gunslinger"; -/* stats.py: Mercenary rank; In files: stats.py:120; */ +/* stats.py: Mercenary rank; In files: stats.py:129; */ "Warrior" = "Warrior"; -/* stats.py: Mercenary rank; In files: stats.py:122; */ +/* stats.py: Mercenary rank; In files: stats.py:131; */ "Deadeye" = "Deadeye"; -/* stats.py: Exobiologist rank; In files: stats.py:125; */ +/* stats.py: Exobiologist rank; In files: stats.py:134; */ "Directionless" = "Directionless"; -/* stats.py: Exobiologist rank; In files: stats.py:126; */ +/* stats.py: Exobiologist rank; In files: stats.py:135; */ "Mostly Directionless" = "Mostly Directionless"; -/* stats.py: Exobiologist rank; In files: stats.py:127; */ +/* stats.py: Exobiologist rank; In files: stats.py:136; */ "Compiler" = "Compiler"; -/* stats.py: Exobiologist rank; In files: stats.py:128; */ +/* stats.py: Exobiologist rank; In files: stats.py:137; */ "Collector" = "Collector"; -/* stats.py: Exobiologist rank; In files: stats.py:129; */ +/* stats.py: Exobiologist rank; In files: stats.py:138; */ "Cataloguer" = "Cataloguer"; -/* stats.py: Exobiologist rank; In files: stats.py:130; */ +/* stats.py: Exobiologist rank; In files: stats.py:139; */ "Taxonomist" = "Taxonomist"; -/* stats.py: Exobiologist rank; In files: stats.py:131; */ +/* stats.py: Exobiologist rank; In files: stats.py:140; */ "Ecologist" = "Ecologist"; -/* stats.py: Exobiologist rank; In files: stats.py:132; */ +/* stats.py: Exobiologist rank; In files: stats.py:141; */ "Geneticist" = "Geneticist"; -/* stats.py: CQC rank; In files: stats.py:135; */ +/* stats.py: CQC rank; In files: stats.py:144; */ "Helpless" = "Helpless"; -/* stats.py: CQC rank; In files: stats.py:136; */ +/* stats.py: CQC rank; In files: stats.py:145; */ "Mostly Helpless" = "Mostly Helpless"; -/* stats.py: CQC rank; In files: stats.py:137; */ +/* stats.py: CQC rank; In files: stats.py:146; */ "Amateur" = "Amateur"; -/* stats.py: CQC rank; In files: stats.py:138; */ +/* stats.py: CQC rank; In files: stats.py:147; */ "Semi Professional" = "Semi Professional"; -/* stats.py: CQC rank; In files: stats.py:139; */ +/* stats.py: CQC rank; In files: stats.py:148; */ "Professional" = "Professional"; -/* stats.py: CQC rank; In files: stats.py:140; */ +/* stats.py: CQC rank; In files: stats.py:149; */ "Champion" = "Champion"; -/* stats.py: CQC rank; In files: stats.py:141; */ +/* stats.py: CQC rank; In files: stats.py:150; */ "Hero" = "Hero"; -/* stats.py: CQC rank; In files: stats.py:142; */ +/* stats.py: CQC rank; In files: stats.py:151; */ "Gladiator" = "Gladiator"; -/* stats.py: Federation rank; In files: stats.py:148; */ +/* stats.py: Federation rank; In files: stats.py:157; */ "Recruit" = "Recruit"; -/* stats.py: Federation rank; In files: stats.py:149; */ +/* stats.py: Federation rank; In files: stats.py:158; */ "Cadet" = "Cadet"; -/* stats.py: Federation rank; In files: stats.py:150; */ +/* stats.py: Federation rank; In files: stats.py:159; */ "Midshipman" = "Midshipman"; -/* stats.py: Federation rank; In files: stats.py:151; */ +/* stats.py: Federation rank; In files: stats.py:160; */ "Petty Officer" = "Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:152; */ +/* stats.py: Federation rank; In files: stats.py:161; */ "Chief Petty Officer" = "Chief Petty Officer"; -/* stats.py: Federation rank; In files: stats.py:153; */ +/* stats.py: Federation rank; In files: stats.py:162; */ "Warrant Officer" = "Warrant Officer"; -/* stats.py: Federation rank; In files: stats.py:154; */ +/* stats.py: Federation rank; In files: stats.py:163; */ "Ensign" = "Ensign"; -/* stats.py: Federation rank; In files: stats.py:155; */ +/* stats.py: Federation rank; In files: stats.py:164; */ "Lieutenant" = "Lieutenant"; -/* stats.py: Federation rank; In files: stats.py:156; */ +/* stats.py: Federation rank; In files: stats.py:165; */ "Lieutenant Commander" = "Lieutenant Commander"; -/* stats.py: Federation rank; In files: stats.py:157; */ +/* stats.py: Federation rank; In files: stats.py:166; */ "Post Commander" = "Post Commander"; -/* stats.py: Federation rank; In files: stats.py:158; */ +/* stats.py: Federation rank; In files: stats.py:167; */ "Post Captain" = "Post Captain"; -/* stats.py: Federation rank; In files: stats.py:159; */ +/* stats.py: Federation rank; In files: stats.py:168; */ "Rear Admiral" = "Rear Admiral"; -/* stats.py: Federation rank; In files: stats.py:160; */ +/* stats.py: Federation rank; In files: stats.py:169; */ "Vice Admiral" = "Vice Admiral"; -/* stats.py: Federation rank; In files: stats.py:161; */ +/* stats.py: Federation rank; In files: stats.py:170; */ "Admiral" = "Admiral"; -/* stats.py: Empire rank; In files: stats.py:167; */ +/* stats.py: Empire rank; In files: stats.py:176; */ "Outsider" = "Outsider"; -/* stats.py: Empire rank; In files: stats.py:168; */ +/* stats.py: Empire rank; In files: stats.py:177; */ "Serf" = "Serf"; -/* stats.py: Empire rank; In files: stats.py:170; */ +/* stats.py: Empire rank; In files: stats.py:179; */ "Squire" = "Squire"; -/* stats.py: Empire rank; In files: stats.py:171; */ +/* stats.py: Empire rank; In files: stats.py:180; */ "Knight" = "Knight"; -/* stats.py: Empire rank; In files: stats.py:172; */ +/* stats.py: Empire rank; In files: stats.py:181; */ "Lord" = "Lord"; -/* stats.py: Empire rank; In files: stats.py:173; */ +/* stats.py: Empire rank; In files: stats.py:182; */ "Baron" = "Baron"; -/* stats.py: Empire rank; In files: stats.py:174; */ +/* stats.py: Empire rank; In files: stats.py:183; */ "Viscount" = "Viscount"; -/* stats.py: Empire rank; In files: stats.py:175; */ +/* stats.py: Empire rank; In files: stats.py:184; */ "Count" = "Count"; -/* stats.py: Empire rank; In files: stats.py:176; */ +/* stats.py: Empire rank; In files: stats.py:185; */ "Earl" = "Earl"; -/* stats.py: Empire rank; In files: stats.py:177; */ +/* stats.py: Empire rank; In files: stats.py:186; */ "Marquis" = "Marquis"; -/* stats.py: Empire rank; In files: stats.py:178; */ +/* stats.py: Empire rank; In files: stats.py:187; */ "Duke" = "Duke"; -/* stats.py: Empire rank; In files: stats.py:179; */ +/* stats.py: Empire rank; In files: stats.py:188; */ "Prince" = "Prince"; -/* stats.py: Empire rank; In files: stats.py:180; */ +/* stats.py: Empire rank; In files: stats.py:189; */ "King" = "King"; -/* stats.py: Power rank; In files: stats.py:186; */ +/* stats.py: Power rank; In files: stats.py:195; */ "Rating 1" = "Rating 1"; -/* stats.py: Power rank; In files: stats.py:187; */ +/* stats.py: Power rank; In files: stats.py:196; */ "Rating 2" = "Rating 2"; -/* stats.py: Power rank; In files: stats.py:188; */ +/* stats.py: Power rank; In files: stats.py:197; */ "Rating 3" = "Rating 3"; -/* stats.py: Power rank; In files: stats.py:189; */ +/* stats.py: Power rank; In files: stats.py:198; */ "Rating 4" = "Rating 4"; -/* stats.py: Power rank; In files: stats.py:190; */ +/* stats.py: Power rank; In files: stats.py:199; */ "Rating 5" = "Rating 5"; -/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:309; */ +/* stats.py: Current commander unknown when trying to use 'File' > 'Status'; In files: stats.py:317; */ "Status: Don't yet know your Commander name" = "Status: Don't yet know your Commander name"; -/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:317; */ +/* stats.py: No Frontier CAPI data yet when trying to use 'File' > 'Status'; In files: stats.py:325; */ "Status: No CAPI data yet" = "Status: No CAPI data yet"; -/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:406; */ +/* stats.py: Status dialog subtitle - CR value of ship; In files: stats.py:413; */ "Value" = "Value"; -/* stats.py: Status dialog title; In files: stats.py:415; */ +/* stats.py: Status dialog title; In files: stats.py:422; */ "Ships" = "Ships"; diff --git a/stats.py b/stats.py index 563c7697..ef993a53 100644 --- a/stats.py +++ b/stats.py @@ -72,10 +72,11 @@ def status(data: Dict[str, Any]) -> List[List[str]]: RANKS = [ # noqa: N806 # Its a constant, just needs to be updated at runtime # in output order + # Names we show people, vs internal names (_('Combat'), 'combat'), # LANG: Ranking (_('Trade'), 'trade'), # LANG: Ranking (_('Explorer'), 'explore'), # LANG: Ranking - (_('Mercenary'), 'mercenary'), # LANG: Ranking + (_('Mercenary'), 'soldier'), # LANG: Ranking (_('Exobiologist'), 'exobiologist'), # LANG: Ranking (_('CQC'), 'cqc'), # LANG: Ranking (_('Federation'), 'federation'), # LANG: Ranking @@ -86,6 +87,7 @@ def status(data: Dict[str, Any]) -> List[List[str]]: ] RANK_NAMES = { # noqa: N806 # Its a constant, just needs to be updated at runtime + # These names are the fdev side name (but lower()ed) # http://elite-dangerous.wikia.com/wiki/Pilots_Federation#Ranks 'combat': [ _('Harmless'), # LANG: Combat rank @@ -118,7 +120,7 @@ def status(data: Dict[str, Any]) -> List[List[str]]: _('Pioneer'), # LANG: Explorer rank ] + _ELITE_RANKS, - 'mercenary': [ + 'soldier': [ _('Defenceless'), # LANG: Mercenary rank _('Mostly Defenceless'), # LANG: Mercenary rank _('Rookie'), # LANG: Mercenary rank From 0c938bddfca4ea3eabbed815da47063c90046beb Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 25 Jan 2022 17:43:04 +0000 Subject: [PATCH 076/186] Inara: API stated to not support compression --- plugins/inara.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/inara.py b/plugins/inara.py index 86e3ec25..2be87cea 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1574,6 +1574,7 @@ def send_data(url: str, data: Mapping[str, Any]) -> bool: # noqa: CCR001 :param data: the data to POST :return: success state """ + # NB: As of 2022-01-25 Artie has stated the Inara API does *not* support compression r = this.session.post(url, data=json.dumps(data, separators=(',', ':')), timeout=_TIMEOUT) r.raise_for_status() reply = r.json() From d4c6cd94fea603b15864285597668c3240eee442 Mon Sep 17 00:00:00 2001 From: A_D Date: Sat, 22 Jan 2022 13:14:39 +0200 Subject: [PATCH 077/186] retry navroute parsing after failure --- monitor.py | 66 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/monitor.py b/monitor.py index ac3f55e1..01b70c74 100644 --- a/monitor.py +++ b/monitor.py @@ -10,7 +10,7 @@ from collections import OrderedDict, defaultdict from os import SEEK_END, SEEK_SET, listdir from os.path import basename, expanduser, isdir, join from sys import platform -from time import gmtime, localtime, sleep, strftime, strptime, time +from time import gmtime, localtime, mktime, sleep, strftime, strptime, time from typing import TYPE_CHECKING, Any, BinaryIO, Dict, List, MutableMapping, Optional from typing import OrderedDict as OrderedDictT from typing import Tuple @@ -23,8 +23,11 @@ from config import config from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised from EDMCLogging import get_main_logger +# spell-checker: words navroute + logger = get_main_logger() STARTUP = 'journal.startup' +MAX_NAVROUTE_DISCREPANCY = 5 if TYPE_CHECKING: def _(x: str) -> str: @@ -111,6 +114,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.systempopulation: Optional[int] = None self.started: Optional[int] = None # Timestamp of the LoadGame event + self._navroute_retries = 0 + self.__init_state() def __init_state(self) -> None: @@ -167,6 +172,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below 'Dropship': None, # Best effort as to whether or not the above taxi is a dropship. 'Body': None, 'BodyType': None, + + 'NavRoute': None, } def start(self, root: 'tkinter.Tk') -> bool: # noqa: CCR001 @@ -494,6 +501,26 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below entry: MutableMapping[str, Any] = json.loads(line, object_pairs_hook=OrderedDict) entry['timestamp'] # we expect this to exist # TODO: replace with assert? or an if key in check + if self._navroute_retries > 0: + logger.debug(f'Navroute read retry [{self._navroute_retries}]') + self._navroute_retries -= 1 + nv_res = self._parse_navroute_file() + if ( + nv_res is None + or time() - mktime(strptime(nv_res['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) > MAX_NAVROUTE_DISCREPANCY + ): + logger.debug( + 'Failed to parse navroute. ' + 'trying again...' if self._navroute_retries > 0 else 'Giving up' + ) + + if self._navroute_retries == 0: + self.state['NavRoute'] = None + + else: + self.state['NavRoute'] = nv_res + logger.debug('successfully read navroute file') + self._navroute_retries = 0 + event_type = entry['event'].lower() if event_type == 'fileheader': self.live = False @@ -1306,16 +1333,26 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below elif event_type == 'navroute': # Added in ED 3.7 - multi-hop route details in NavRoute.json - with open(join(self.currentdir, 'NavRoute.json'), 'rb') as rf: # type: ignore - try: - entry = json.load(rf) + if (nv_json := self._parse_navroute_file()) is not None: + entry_time = mktime(strptime(nv_json['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) + c_time = time() - except json.JSONDecodeError: - logger.exception('Failed decoding NavRoute.json', exc_info=True) + if c_time - entry_time > MAX_NAVROUTE_DISCREPANCY: + logger.warning( + f'Parsed NavRoute.json timestamp is off by more than ' + f'{MAX_NAVROUTE_DISCREPANCY} ({c_time-entry_time=}).Trying again.' + ) + + self._navroute_retries = 10 else: + entry = nv_json self.state['NavRoute'] = entry + else: + logger.info('Failed to decode NavRoute.json. Trying again') + self._navroute_retries = 10 + elif event_type == 'moduleinfo': with open(join(self.currentdir, 'ModulesInfo.json'), 'rb') as mf: # type: ignore try: @@ -2157,6 +2194,23 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below return slots + def _parse_navroute_file(self) -> dict[str, Any] | None: + """Read and parse NavRoute.json.""" + if self.currentdir is None: + raise ValueError('currentdir unset') + + with open(join(self.currentdir, 'NavRoute.json'), 'r') as f: + raw = f.read() + + try: + data = json.loads(raw) + + except json.JSONDecodeError: + logger.exception('Failed to decode NavRoute.json', exc_info=True) + return None + + return data + # singleton monitor = EDLogs() From 06d4842da27b391b154e9b3c09a7ee7c4043f906 Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 25 Jan 2022 20:16:59 +0200 Subject: [PATCH 078/186] Be more parinoid about navroute files --- monitor.py | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/monitor.py b/monitor.py index 01b70c74..231871b8 100644 --- a/monitor.py +++ b/monitor.py @@ -115,6 +115,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.started: Optional[int] = None # Timestamp of the LoadGame event self._navroute_retries = 0 + self._navroute_orig_time: Optional[float] = None self.__init_state() @@ -505,9 +506,13 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below logger.debug(f'Navroute read retry [{self._navroute_retries}]') self._navroute_retries -= 1 nv_res = self._parse_navroute_file() + if self._navroute_orig_time is None: + logger.critical('Asked to retry for navroute but also no set time to compare? This is a bug.') + if ( nv_res is None - or time() - mktime(strptime(nv_res['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) > MAX_NAVROUTE_DISCREPANCY + or self._navroute_orig_time is None + or self._navroute_orig_time - self._parse_time(nv_res['timestamp']) > MAX_NAVROUTE_DISCREPANCY ): logger.debug( 'Failed to parse navroute. ' + 'trying again...' if self._navroute_retries > 0 else 'Giving up' @@ -520,6 +525,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.state['NavRoute'] = nv_res logger.debug('successfully read navroute file') self._navroute_retries = 0 + self._navroute_orig_time = None event_type = entry['event'].lower() if event_type == 'fileheader': @@ -1332,26 +1338,30 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.state['Taxi'] = False elif event_type == 'navroute': + # assume we've failed out the gate, then pull it back if things are fine + self._navroute_orig_time = mktime(strptime(entry['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) + self._navroute_retries = 10 # Added in ED 3.7 - multi-hop route details in NavRoute.json - if (nv_json := self._parse_navroute_file()) is not None: - entry_time = mktime(strptime(nv_json['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) - c_time = time() + if (nv_json := self._parse_navroute_file()) is not None and 'timestamp' in nv_json: + file_time = mktime(strptime(nv_json['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) - if c_time - entry_time > MAX_NAVROUTE_DISCREPANCY: + if self._navroute_orig_time - file_time > MAX_NAVROUTE_DISCREPANCY: logger.warning( f'Parsed NavRoute.json timestamp is off by more than ' - f'{MAX_NAVROUTE_DISCREPANCY} ({c_time-entry_time=}).Trying again.' + f'{MAX_NAVROUTE_DISCREPANCY} ({self._navroute_orig_time-file_time=}).Trying again.' ) - self._navroute_retries = 10 - else: + # TODO: rather update entry here, overwriting existing fields but leaving others? + + # everything is happy, dont retry, dont set the original time, proceed as expected. entry = nv_json self.state['NavRoute'] = entry + self._navroute_retries = 0 + self._navroute_orig_time = None else: logger.info('Failed to decode NavRoute.json. Trying again') - self._navroute_retries = 10 elif event_type == 'moduleinfo': with open(join(self.currentdir, 'ModulesInfo.json'), 'rb') as mf: # type: ignore @@ -2194,11 +2204,14 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below return slots - def _parse_navroute_file(self) -> dict[str, Any] | None: + def _parse_navroute_file(self) -> Optional[dict[str, Any]]: """Read and parse NavRoute.json.""" if self.currentdir is None: raise ValueError('currentdir unset') + if not (pathlib.Path(self.currentdir) / 'NavRoute.json').exists(): + return None + with open(join(self.currentdir, 'NavRoute.json'), 'r') as f: raw = f.read() @@ -2209,8 +2222,15 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below logger.exception('Failed to decode NavRoute.json', exc_info=True) return None + if 'timestamp' not in data: # quick sanity check + return None + return data + @staticmethod + def _parse_time(source: str) -> float: + return mktime(strptime(source, '%Y-%m-%dT%H:%M:%SZ')) + # singleton monitor = EDLogs() From 36ecb69964d667570977170af62116005ae57ead Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 26 Jan 2022 13:17:27 +0200 Subject: [PATCH 079/186] refactor to use retry function for all attempts at navroute --- monitor.py | 97 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/monitor.py b/monitor.py index 231871b8..e01bfc41 100644 --- a/monitor.py +++ b/monitor.py @@ -502,30 +502,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below entry: MutableMapping[str, Any] = json.loads(line, object_pairs_hook=OrderedDict) entry['timestamp'] # we expect this to exist # TODO: replace with assert? or an if key in check - if self._navroute_retries > 0: - logger.debug(f'Navroute read retry [{self._navroute_retries}]') - self._navroute_retries -= 1 - nv_res = self._parse_navroute_file() - if self._navroute_orig_time is None: - logger.critical('Asked to retry for navroute but also no set time to compare? This is a bug.') - - if ( - nv_res is None - or self._navroute_orig_time is None - or self._navroute_orig_time - self._parse_time(nv_res['timestamp']) > MAX_NAVROUTE_DISCREPANCY - ): - logger.debug( - 'Failed to parse navroute. ' + 'trying again...' if self._navroute_retries > 0 else 'Giving up' - ) - - if self._navroute_retries == 0: - self.state['NavRoute'] = None - - else: - self.state['NavRoute'] = nv_res - logger.debug('successfully read navroute file') - self._navroute_retries = 0 - self._navroute_orig_time = None + self.__navroute_retry() event_type = entry['event'].lower() if event_type == 'fileheader': @@ -1340,28 +1317,12 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below elif event_type == 'navroute': # assume we've failed out the gate, then pull it back if things are fine self._navroute_orig_time = mktime(strptime(entry['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) - self._navroute_retries = 10 + self._navroute_retries = 11 + # Added in ED 3.7 - multi-hop route details in NavRoute.json - if (nv_json := self._parse_navroute_file()) is not None and 'timestamp' in nv_json: - file_time = mktime(strptime(nv_json['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) - - if self._navroute_orig_time - file_time > MAX_NAVROUTE_DISCREPANCY: - logger.warning( - f'Parsed NavRoute.json timestamp is off by more than ' - f'{MAX_NAVROUTE_DISCREPANCY} ({self._navroute_orig_time-file_time=}).Trying again.' - ) - - else: - # TODO: rather update entry here, overwriting existing fields but leaving others? - - # everything is happy, dont retry, dont set the original time, proceed as expected. - entry = nv_json - self.state['NavRoute'] = entry - self._navroute_retries = 0 - self._navroute_orig_time = None - - else: - logger.info('Failed to decode NavRoute.json. Trying again') + # rather than duplicating this, lets just call the function + if self.__navroute_retry(): + entry = self.state['NavRoute'] elif event_type == 'moduleinfo': with open(join(self.currentdir, 'ModulesInfo.json'), 'rb') as mf: # type: ignore @@ -2212,8 +2173,14 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below if not (pathlib.Path(self.currentdir) / 'NavRoute.json').exists(): return None - with open(join(self.currentdir, 'NavRoute.json'), 'r') as f: - raw = f.read() + try: + + with open(join(self.currentdir, 'NavRoute.json'), 'r') as f: + raw = f.read() + + except Exception as e: + logger.exception(f'Could not open navroute file. Bailing: {e}') + return None try: data = json.loads(raw) @@ -2231,6 +2198,42 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below def _parse_time(source: str) -> float: return mktime(strptime(source, '%Y-%m-%dT%H:%M:%SZ')) + def __navroute_retry(self) -> bool: + """Retry reading navroute files.""" + if self._navroute_retries == 0: + return False + + logger.info(f'Navroute read retry [{self._navroute_retries}]') + self._navroute_retries -= 1 + + if self._navroute_orig_time is None: + logger.critical('Asked to retry for navroute but also no set time to compare? This is a bug.') + return False + + if (file := self._parse_navroute_file()) is None: + logger.debug( + 'Failed to parse NavRoute.json. ' + + ('Trying again' if self._navroute_retries > 0 else 'Giving up') + ) + return False + + # _parse_navroute_file verifies that this exists for us + file_time = self._parse_time(file['timestamp']) + if abs(file_time - self._navroute_orig_time) > MAX_NAVROUTE_DISCREPANCY: + logger.debug( + f'Time discrepancy of more than {MAX_NAVROUTE_DISCREPANCY}s --' + f' ({abs(file_time - self._navroute_orig_time)}).' + f' {"Trying again" if self._navroute_retries > 0 else "Giving up"}.' + ) + return False + + # everything is good, lets set what we need to and make sure we dont try again + logger.info('Successfully read NavRoute file for last NavRoute event.') + self.state['NavRoute'] = file + self._navroute_retries = 0 + self._navroute_orig_time = None + return True + # singleton monitor = EDLogs() From 62a0a96dc646863bd0ea01e8f0419b4a581ba690 Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 26 Jan 2022 17:42:50 +0200 Subject: [PATCH 080/186] resolve final review comments --- monitor.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/monitor.py b/monitor.py index e01bfc41..f5e9a3ac 100644 --- a/monitor.py +++ b/monitor.py @@ -114,8 +114,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.systempopulation: Optional[int] = None self.started: Optional[int] = None # Timestamp of the LoadGame event - self._navroute_retries = 0 - self._navroute_orig_time: Optional[float] = None + self._navroute_retries_remaining = 0 + self._last_navroute_journal_timestamp: Optional[float] = None self.__init_state() @@ -1316,8 +1316,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below elif event_type == 'navroute': # assume we've failed out the gate, then pull it back if things are fine - self._navroute_orig_time = mktime(strptime(entry['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) - self._navroute_retries = 11 + self._last_navroute_journal_timestamp = mktime(strptime(entry['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) + self._navroute_retries_remaining = 11 # Added in ED 3.7 - multi-hop route details in NavRoute.json # rather than duplicating this, lets just call the function @@ -2170,9 +2170,6 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below if self.currentdir is None: raise ValueError('currentdir unset') - if not (pathlib.Path(self.currentdir) / 'NavRoute.json').exists(): - return None - try: with open(join(self.currentdir, 'NavRoute.json'), 'r') as f: @@ -2195,43 +2192,43 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below return data @staticmethod - def _parse_time(source: str) -> float: + def _parse_journal_timestamp(source: str) -> float: return mktime(strptime(source, '%Y-%m-%dT%H:%M:%SZ')) def __navroute_retry(self) -> bool: """Retry reading navroute files.""" - if self._navroute_retries == 0: + if self._navroute_retries_remaining == 0: return False - logger.info(f'Navroute read retry [{self._navroute_retries}]') - self._navroute_retries -= 1 + logger.info(f'Navroute read retry [{self._navroute_retries_remaining}]') + self._navroute_retries_remaining -= 1 - if self._navroute_orig_time is None: + if self._last_navroute_journal_timestamp is None: logger.critical('Asked to retry for navroute but also no set time to compare? This is a bug.') return False if (file := self._parse_navroute_file()) is None: logger.debug( 'Failed to parse NavRoute.json. ' - + ('Trying again' if self._navroute_retries > 0 else 'Giving up') + + ('Trying again' if self._navroute_retries_remaining > 0 else 'Giving up') ) return False # _parse_navroute_file verifies that this exists for us - file_time = self._parse_time(file['timestamp']) - if abs(file_time - self._navroute_orig_time) > MAX_NAVROUTE_DISCREPANCY: + file_time = self._parse_journal_timestamp(file['timestamp']) + if abs(file_time - self._last_navroute_journal_timestamp) > MAX_NAVROUTE_DISCREPANCY: logger.debug( f'Time discrepancy of more than {MAX_NAVROUTE_DISCREPANCY}s --' - f' ({abs(file_time - self._navroute_orig_time)}).' - f' {"Trying again" if self._navroute_retries > 0 else "Giving up"}.' + f' ({abs(file_time - self._last_navroute_journal_timestamp)}).' + f' {"Trying again" if self._navroute_retries_remaining > 0 else "Giving up"}.' ) return False # everything is good, lets set what we need to and make sure we dont try again logger.info('Successfully read NavRoute file for last NavRoute event.') self.state['NavRoute'] = file - self._navroute_retries = 0 - self._navroute_orig_time = None + self._navroute_retries_remaining = 0 + self._last_navroute_journal_timestamp = None return True From e98aec0169ed85267ab86941ad272a8bdf7af518 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 26 Jan 2022 16:38:01 +0000 Subject: [PATCH 081/186] monitor: Comment what MAX_NAVROUTE_DISCREPANCY is So as to disambiguate from "some discrepancy within the route data". --- monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index f5e9a3ac..42d282b1 100644 --- a/monitor.py +++ b/monitor.py @@ -27,7 +27,7 @@ from EDMCLogging import get_main_logger logger = get_main_logger() STARTUP = 'journal.startup' -MAX_NAVROUTE_DISCREPANCY = 5 +MAX_NAVROUTE_DISCREPANCY = 5 # Timestamp difference in seconds if TYPE_CHECKING: def _(x: str) -> str: From eb28a3b502f7881f08a9d4da02d4b891fd89c9d5 Mon Sep 17 00:00:00 2001 From: A_D Date: Mon, 24 Jan 2022 13:04:22 +0200 Subject: [PATCH 082/186] Use sys.platform, minor type updates --- monitor.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/monitor.py b/monitor.py index 42d282b1..be52290a 100644 --- a/monitor.py +++ b/monitor.py @@ -1,15 +1,18 @@ """Monitor for new Journal files and contents of latest.""" +# v [sic] +# spell-checker: words onfoot unforseen relog fsdjump suitloadoutid slotid suitid loadoutid fauto Intimidator +# spell-checker: words joinacrew quitacrew sellshiponrebuy newbal navroute npccrewpaidwage sauto import json import pathlib import queue import re +import sys import threading from calendar import timegm from collections import OrderedDict, defaultdict from os import SEEK_END, SEEK_SET, listdir from os.path import basename, expanduser, isdir, join -from sys import platform from time import gmtime, localtime, mktime, sleep, strftime, strptime, time from typing import TYPE_CHECKING, Any, BinaryIO, Dict, List, MutableMapping, Optional from typing import OrderedDict as OrderedDictT @@ -33,7 +36,7 @@ if TYPE_CHECKING: def _(x: str) -> str: return x -if platform == 'darwin': +if sys.platform == 'darwin': from fcntl import fcntl from AppKit import NSWorkspace @@ -41,7 +44,7 @@ if platform == 'darwin': from watchdog.observers import Observer F_GLOBAL_NOCACHE = 55 -elif platform == 'win32': +elif sys.platform == 'win32': import ctypes from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR @@ -62,6 +65,10 @@ elif platform == 'win32': else: # Linux's inotify doesn't work over CIFS or NFS, so poll FileSystemEventHandler = object # dummy + if TYPE_CHECKING: + # this isn't ever used, but this will make type checking happy + from watchdog.events import FileCreatedEvent + from watchdog.observers import Observer # Journal handler @@ -223,7 +230,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # File system events are unreliable/non-existent over network drives on Linux. # We can't easily tell whether a path points to a network drive, so assume # any non-standard logdir might be on a network drive and poll instead. - polling = bool(config.get_str('journaldir')) and platform != 'win32' + polling = bool(config.get_str('journaldir')) and sys.platform != 'win32' if not polling and not self.observer: logger.debug('Not polling, no observer, starting an observer...') self.observer = Observer() @@ -280,6 +287,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below if self.observed: logger.debug('self.observed: Calling unschedule_all()') self.observed = None + assert self.observer is not None, 'Observer was none but it is in use?' self.observer.unschedule_all() logger.debug('Done') @@ -339,7 +347,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below logfile = self.logfile if logfile: loghandle: BinaryIO = open(logfile, 'rb', 0) # unbuffered - if platform == 'darwin': + if sys.platform == 'darwin': fcntl(loghandle, F_GLOBAL_NOCACHE, -1) # required to avoid corruption on macOS over SMB for line in loghandle: @@ -390,9 +398,13 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.event_queue.put(None) self.live = False + emitter = None # Watchdog thread -- there is a way to get this by using self.observer.emitters and checking for an attribute: # watch, but that may have unforseen differences in behaviour. - emitter = self.observed and self.observer._emitter_for_watch[self.observed] # Note: Uses undocumented attribute + if self.observed: + assert self.observer is not None, 'self.observer is None but also in use?' + # Note: Uses undocumented attribute + emitter = self.observed and self.observer._emitter_for_watch[self.observed] logger.debug('Entering loop...') while True: @@ -448,7 +460,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below if logfile: loghandle = open(logfile, 'rb', 0) # unbuffered - if platform == 'darwin': + if sys.platform == 'darwin': fcntl(loghandle, F_GLOBAL_NOCACHE, -1) # required to avoid corruption on macOS over SMB log_pos = 0 @@ -695,7 +707,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # This event is logged when a player (on foot) gets into a ship or SRV # Parameters: # • SRV: true if getting into SRV, false if getting into a ship - # • Taxi: true when boarding a taxi transposrt ship + # • Taxi: true when boarding a taxi transport ship # • Multicrew: true when boarding another player’s vessel # • ID: player’s ship ID (if players own vessel) # • StarSystem @@ -723,7 +735,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # # Parameters: # • SRV: true if getting out of SRV, false if getting out of a ship - # • Taxi: true when getting out of a taxi transposrt ship + # • Taxi: true when getting out of a taxi transport ship # • Multicrew: true when getting out of another player’s vessel # • ID: player’s ship ID (if players own vessel) # • StarSystem @@ -1938,12 +1950,12 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below :return: bool - True if the game is running. """ - if platform == 'darwin': + if sys.platform == 'darwin': for app in NSWorkspace.sharedWorkspace().runningApplications(): if app.bundleIdentifier() == 'uk.co.frontier.EliteDangerous': return True - elif platform == 'win32': + elif sys.platform == 'win32': def WindowTitle(h): # noqa: N802 # type: ignore if h: length = GetWindowTextLength(h) + 1 From 86292e02e2f9443640060ee83dcc083566d9a402 Mon Sep 17 00:00:00 2001 From: A_D Date: Mon, 24 Jan 2022 13:40:36 +0200 Subject: [PATCH 083/186] Move config to module, separate out implementation Does what it says on the tin. Moves config implementations out to individual files, guards those around platforms to make stuff more reasonably split out. --- config.py | 1115 -------------------------------------------- config/__init__.py | 473 +++++++++++++++++++ config/darwin.py | 184 ++++++++ config/linux.py | 245 ++++++++++ config/windows.py | 259 ++++++++++ 5 files changed, 1161 insertions(+), 1115 deletions(-) delete mode 100644 config.py create mode 100644 config/__init__.py create mode 100644 config/darwin.py create mode 100644 config/linux.py create mode 100644 config/windows.py diff --git a/config.py b/config.py deleted file mode 100644 index b32b2613..00000000 --- a/config.py +++ /dev/null @@ -1,1115 +0,0 @@ -""" -Code dealing with the configuration of the program. - -Windows uses the Registry to store values in a flat manner. -Linux uses a file, but for commonality it's still a flat data structure. -macOS uses a 'defaults' object. -""" - -# spell-checker: words HKEY FOLDERID wchar wstring edcdhkey - -import abc -import contextlib -import functools -import logging -import os -import pathlib -import re -import subprocess -import sys -import traceback -import warnings -from abc import abstractmethod -from sys import platform -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Type, TypeVar, Union - -import semantic_version - -from constants import GITVERSION_FILE, applongname, appname - -# Any of these may be imported by plugins -appcmdname = 'EDMC' -# appversion **MUST** follow Semantic Versioning rules: -# -# Major.Minor.Patch(-prerelease)(+buildmetadata) -# NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.3.0-beta4' -_cached_version: Optional[semantic_version.Version] = None -copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD' - -update_feed = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' -update_interval = 8*60*60 -# Providers marked to be in debug mode. Generally this is expected to switch to sending data to a log file -debug_senders: List[str] = [] -# TRACE logging code that should actually be used. Means not spamming it -# *all* if only interested in some things. -trace_on: List[str] = [] - -capi_pretend_down: bool = False -capi_debug_access_token: Optional[str] = None -# This must be done here in order to avoid an import cycle with EDMCLogging. -# Other code should use EDMCLogging.get_main_logger -if os.getenv("EDMC_NO_UI"): - logger = logging.getLogger(appcmdname) - -else: - logger = logging.getLogger(appname) - -if platform == 'darwin': - from Foundation import ( # type: ignore - NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, - NSUserDefaults, NSUserDomainMask - ) - -elif platform == 'win32': - import ctypes - import uuid - import winreg - from ctypes.wintypes import DWORD, HANDLE - if TYPE_CHECKING: - import ctypes.windll # type: ignore - - REG_RESERVED_ALWAYS_ZERO = 0 - - # This is the only way to do this from python without external deps (which do this anyway). - FOLDERID_Documents = uuid.UUID('{FDD39AD0-238F-46AF-ADB4-6C85480369C7}') - FOLDERID_LocalAppData = uuid.UUID('{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}') - FOLDERID_Profile = uuid.UUID('{5E6C858F-0E22-4760-9AFE-EA3317B67173}') - FOLDERID_SavedGames = uuid.UUID('{4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4}') - - SHGetKnownFolderPath = ctypes.windll.shell32.SHGetKnownFolderPath - SHGetKnownFolderPath.argtypes = [ctypes.c_char_p, DWORD, HANDLE, ctypes.POINTER(ctypes.c_wchar_p)] - - CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree - CoTaskMemFree.argtypes = [ctypes.c_void_p] - - def known_folder_path(guid: uuid.UUID) -> Optional[str]: - """Look up a Windows GUID to actual folder path name.""" - buf = ctypes.c_wchar_p() - if SHGetKnownFolderPath(ctypes.create_string_buffer(guid.bytes_le), 0, 0, ctypes.byref(buf)): - return None - retval = buf.value # copy data - CoTaskMemFree(buf) # and free original - return retval - -elif platform == 'linux': - from configparser import ConfigParser - - -_T = TypeVar('_T') - - -########################################################################### -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 - - try: - git_cmd = subprocess.Popen('git rev-parse --short HEAD'.split(), - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT - ) - out, err = git_cmd.communicate() - - except Exception as e: - logger.info(f"Couldn't run git command for short hash: {e!r}") - - else: - shorthash = out.decode().rstrip('\n') - if re.match(r'^[0-9a-f]{7,}$', shorthash) is None: - logger.error(f"'{shorthash}' doesn't look like a valid git short hash, forcing to None") - shorthash = None # type: ignore - - if shorthash is not None: - with contextlib.suppress(Exception): - result = subprocess.run('git diff --stat HEAD'.split(), capture_output=True) - if len(result.stdout) > 0: - shorthash += '.DIRTY' - - if len(result.stderr) > 0: - logger.warning(f'Data from git on stderr:\n{str(result.stderr)}') - - return shorthash - - -def appversion() -> semantic_version.Version: - """ - Determine app version including git short hash if possible. - - :return: The augmented app version. - """ - global _cached_version - if _cached_version is not None: - return _cached_version - - if getattr(sys, 'frozen', False): - # Running frozen, so we should have a .gitversion file - # Yes, .parent because if frozen we're inside library.zip - with open(pathlib.Path(sys.path[0]).parent / GITVERSION_FILE, 'r', encoding='utf-8') as gitv: - shorthash = gitv.read() - - else: - # Running from source - shorthash = git_shorthash_from_head() - if shorthash is None: - shorthash = 'UNKNOWN' - - _cached_version = semantic_version.Version(f'{_static_appversion}+{shorthash}') - return _cached_version - - -user_agent = f'EDCD-{appname}-{appversion()}' - - -def appversion_nobuild() -> semantic_version.Version: - """ - Determine app version without *any* build meta data. - - This will not only strip any added git short hash, but also any trailing - '+' in _static_appversion. - - :return: App version without any build meta data. - """ - return appversion().truncate('prerelease') -########################################################################### - - -class AbstractConfig(abc.ABC): - """Abstract root class of all platform specific Config implementations.""" - - OUT_MKT_EDDN = 1 - # OUT_MKT_BPC = 2 # No longer supported - OUT_MKT_TD = 4 - OUT_MKT_CSV = 8 - OUT_SHIP = 16 - # OUT_SHIP_EDS = 16 # Replaced by OUT_SHIP - # OUT_SYS_FILE = 32 # No longer supported - # OUT_STAT = 64 # No longer available - # OUT_SHIP_CORIOLIS = 128 # Replaced by OUT_SHIP - OUT_STATION_ANY = OUT_MKT_EDDN | OUT_MKT_TD | OUT_MKT_CSV - # OUT_SYS_EDSM = 256 # Now a plugin - # OUT_SYS_AUTO = 512 # Now always automatic - OUT_MKT_MANUAL = 1024 - OUT_SYS_EDDN = 2048 - OUT_SYS_DELAY = 4096 - - app_dir_path: pathlib.Path - plugin_dir_path: pathlib.Path - internal_plugin_dir_path: pathlib.Path - respath_path: pathlib.Path - home_path: pathlib.Path - default_journal_dir_path: pathlib.Path - - identifier: str - - __in_shutdown = False # Is the application currently shutting down ? - __auth_force_localserver = False # Should we use localhost for auth callback ? - __auth_force_edmc_protocol = False # Should we force edmc:// protocol ? - __eddn_url = None # Non-default EDDN URL - __eddn_tracking_ui = False # Show EDDN tracking UI ? - - def __init__(self) -> None: - self.home_path = pathlib.Path.home() - - def set_shutdown(self): - """Set flag denoting we're in the shutdown sequence.""" - self.__in_shutdown = True - - @property - def shutting_down(self) -> bool: - """ - Determine if we're in the shutdown sequence. - - :return: bool - True if in shutdown sequence. - """ - return self.__in_shutdown - - def set_auth_force_localserver(self): - """Set flag to force use of localhost web server for Frontier Auth callback.""" - self.__auth_force_localserver = True - - @property - def auth_force_localserver(self) -> bool: - """ - Determine if use of localhost is forced for Frontier Auth callback. - - :return: bool - True if we should use localhost web server. - """ - return self.__auth_force_localserver - - def set_auth_force_edmc_protocol(self): - """Set flag to force use of localhost web server for Frontier Auth callback.""" - self.__auth_force_edmc_protocol = True - - @property - def auth_force_edmc_protocol(self) -> bool: - """ - Determine if use of localhost is forced for Frontier Auth callback. - - :return: bool - True if we should use localhost web server. - """ - return self.__auth_force_edmc_protocol - - def set_eddn_url(self, eddn_url: str): - """Set the specified eddn URL.""" - self.__eddn_url = eddn_url - - @property - def eddn_url(self) -> Optional[str]: - """ - Provide the custom EDDN URL. - - :return: str - Custom EDDN URL to use. - """ - return self.__eddn_url - - def set_eddn_tracking_ui(self): - """Activate EDDN tracking UI.""" - self.__eddn_tracking_ui = True - - @property - def eddn_tracking_ui(self) -> bool: - """ - Determine if the EDDN tracking UI be shown. - - :return: bool - Should tracking UI be active? - """ - return self.__eddn_tracking_ui - - @property - def app_dir(self) -> str: - """Return a string version of app_dir.""" - return str(self.app_dir_path) - - @property - def plugin_dir(self) -> str: - """Return a string version of plugin_dir.""" - return str(self.plugin_dir_path) - - @property - def internal_plugin_dir(self) -> str: - """Return a string version of internal_plugin_dir.""" - return str(self.internal_plugin_dir_path) - - @property - def respath(self) -> str: - """Return a string version of respath.""" - return str(self.respath_path) - - @property - def home(self) -> str: - """Return a string version of home.""" - return str(self.home_path) - - @property - def default_journal_dir(self) -> str: - """Return a string version of default_journal_dir.""" - return str(self.default_journal_dir_path) - - @staticmethod - def _suppress_call( - func: Callable[..., _T], exceptions: Union[Type[BaseException], List[Type[BaseException]]] = Exception, - *args: Any, **kwargs: Any - ) -> Optional[_T]: - if exceptions is None: - exceptions = [Exception] - - if not isinstance(exceptions, list): - exceptions = [exceptions] - - with contextlib.suppress(*exceptions): # type: ignore # it works fine, mypy - return func(*args, **kwargs) - - return None - - def get(self, key: str, default: Union[list, str, bool, int] = None) -> Union[list, str, bool, int]: - """ - Return the data for the requested key, or a default. - - :param key: The key data is being requested for. - :param default: The default to return if the key does not exist, defaults to None. - :raises OSError: On Windows, if a Registry error occurs. - :return: The data or the default. - """ - warnings.warn(DeprecationWarning('get is Deprecated. use the specific getter for your type')) - logger.debug('Attempt to use Deprecated get() method\n' + ''.join(traceback.format_stack())) - - if (l := self._suppress_call(self.get_list, ValueError, key, default=None)) is not None: - return l - - elif (s := self._suppress_call(self.get_str, ValueError, key, default=None)) is not None: - return s - - elif (b := self._suppress_call(self.get_bool, ValueError, key, default=None)) is not None: - return b - - elif (i := self._suppress_call(self.get_int, ValueError, key, default=None)) is not None: - return i - - return default # type: ignore - - @abstractmethod - def get_list(self, key: str, *, default: list = None) -> list: - """ - Return the list referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_list`. - """ - raise NotImplementedError - - @abstractmethod - def get_str(self, key: str, *, default: str = None) -> str: - """ - Return the string referred to by the given key if it exists, or the default. - - :param key: The key data is being requested for. - :param default: Default to return if the key does not exist, defaults to None. - :raises ValueError: If an internal error occurs getting or converting a value. - :raises OSError: On Windows, if a Registry error occurs. - :return: The requested data or the default. - """ - raise NotImplementedError - - @abstractmethod - def get_bool(self, key: str, *, default: bool = None) -> bool: - """ - Return the bool referred to by the given key if it exists, or the default. - - :param key: The key data is being requested for. - :param default: Default to return if the key does not exist, defaults to None - :raises ValueError: If an internal error occurs getting or converting a value - :raises OSError: On Windows, if a Registry error occurs. - :return: The requested data or the default - """ - raise NotImplementedError - - def getint(self, key: str, *, default: int = 0) -> int: - """ - Getint is a Deprecated getter method. - - See get_int for its replacement. - :raises OSError: On Windows, if a Registry error occurs. - """ - warnings.warn(DeprecationWarning('getint is Deprecated. Use get_int instead')) - logger.debug('Attempt to use Deprecated getint() method\n' + ''.join(traceback.format_stack())) - - return self.get_int(key, default=default) - - @abstractmethod - def get_int(self, key: str, *, default: int = 0) -> int: - """ - Return the int referred to by key if it exists in the config. - - For legacy reasons, the default is 0 and not None. - - :param key: The key data is being requested for. - :param default: Default to return if the key does not exist, defaults to 0. - :raises ValueError: If the internal representation of this key cannot be converted to an int. - :raises OSError: On Windows, if a Registry error occurs. - :return: The requested data or the default. - """ - raise NotImplementedError - - @abstractmethod - def set(self, key: str, val: Union[int, str, List[str], bool]) -> None: - """ - Set the given key's data to the given value. - - :param key: The key to set the value on. - :param val: The value to set the key's data to. - :raises ValueError: On an invalid type. - :raises OSError: On Windows, if a Registry error occurs. - """ - raise NotImplementedError - - @abstractmethod - def delete(self, key: str, *, suppress=False) -> None: - """ - Delete the given key from the config. - - :param key: The key to delete. - :param suppress: bool - Whether to suppress any errors. Useful in case - code to migrate settings is blindly removing an old key. - :raises OSError: On Windows, if a registry error occurs. - """ - raise NotImplementedError - - @abstractmethod - def save(self) -> None: - """ - Save the current configuration. - - :raises OSError: On Windows, if a Registry error occurs. - """ - raise NotImplementedError - - @abstractmethod - def close(self) -> None: - """Close this config and release any associated resources.""" - raise NotImplementedError - - def get_password(self, account: str) -> None: - """Legacy password retrieval.""" - warnings.warn("password subsystem is no longer supported", DeprecationWarning) - - def set_password(self, account: str, password: str) -> None: - """Legacy password setting.""" - warnings.warn("password subsystem is no longer supported", DeprecationWarning) - - def delete_password(self, account: str) -> None: - """Legacy password deletion.""" - warnings.warn("password subsystem is no longer supported", DeprecationWarning) - - -class WinConfig(AbstractConfig): - """Implementation of AbstractConfig for Windows.""" - - def __init__(self, do_winsparkle=True) -> None: - self.app_dir_path = pathlib.Path(str(known_folder_path(FOLDERID_LocalAppData))) / appname - self.app_dir_path.mkdir(exist_ok=True) - - self.plugin_dir_path = self.app_dir_path / 'plugins' - self.plugin_dir_path.mkdir(exist_ok=True) - - if getattr(sys, 'frozen', False): - self.respath_path = pathlib.Path(sys.executable).parent - self.internal_plugin_dir_path = self.respath_path / 'plugins' - - else: - self.respath_path = pathlib.Path(__file__).parent - self.internal_plugin_dir_path = self.respath_path / 'plugins' - - self.home_path = pathlib.Path.home() - - journal_dir_str = known_folder_path(FOLDERID_SavedGames) - journaldir = pathlib.Path(journal_dir_str) if journal_dir_str is not None else None - self.default_journal_dir_path = None # type: ignore - if journaldir is not None: - self.default_journal_dir_path = journaldir / 'Frontier Developments' / 'Elite Dangerous' - - create_key_defaults = functools.partial( - winreg.CreateKeyEx, - key=winreg.HKEY_CURRENT_USER, - access=winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY, - ) - - try: - self.__reg_handle: winreg.HKEYType = create_key_defaults( - sub_key=r'Software\Marginal\EDMarketConnector' - ) - if do_winsparkle: - self.__setup_winsparkle() - - except OSError: - logger.exception('could not create required registry keys') - raise - - self.identifier = applongname - if (outdir_str := self.get_str('outdir')) is None or not pathlib.Path(outdir_str).is_dir(): - docs = known_folder_path(FOLDERID_Documents) - self.set('outdir', docs if docs is not None else self.home) - - def __setup_winsparkle(self): - """Ensure the necessary Registry keys for WinSparkle are present.""" - create_key_defaults = functools.partial( - winreg.CreateKeyEx, - key=winreg.HKEY_CURRENT_USER, - access=winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY, - ) - try: - edcd_handle: winreg.HKEYType = create_key_defaults(sub_key=r'Software\EDCD\EDMarketConnector') - winsparkle_reg: winreg.HKEYType = winreg.CreateKeyEx( - edcd_handle, sub_key='WinSparkle', access=winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY - ) - - except OSError: - logger.exception('could not open WinSparkle handle') - raise - - # set WinSparkle defaults - https://github.com/vslavik/winsparkle/wiki/Registry-Settings - winreg.SetValueEx( - winsparkle_reg, 'UpdateInterval', REG_RESERVED_ALWAYS_ZERO, winreg.REG_SZ, str(update_interval) - ) - - try: - winreg.QueryValueEx(winsparkle_reg, 'CheckForUpdates') - - except FileNotFoundError: - # Key doesn't exist, set it to a default - winreg.SetValueEx(winsparkle_reg, 'CheckForUpdates', REG_RESERVED_ALWAYS_ZERO, winreg.REG_SZ, '1') - - winsparkle_reg.Close() - edcd_handle.Close() - - def __get_regentry(self, key: str) -> Union[None, list, str, int]: - """Access the Registry for the raw entry.""" - try: - value, _type = winreg.QueryValueEx(self.__reg_handle, key) - except FileNotFoundError: - # Key doesn't exist - return None - - # The type returned is actually as we'd expect for each of these. The casts are here for type checkers and - # For programmers who want to actually know what is going on - if _type == winreg.REG_SZ: - return str(value) - - elif _type == winreg.REG_DWORD: - return int(value) - - elif _type == winreg.REG_MULTI_SZ: - return list(value) - - else: - logger.warning(f'registry key {key=} returned unknown type {_type=} {value=}') - return None - - def get_str(self, key: str, *, default: str = None) -> str: - """ - Return the string referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_str`. - """ - res = self.__get_regentry(key) - if res is None: - return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default - - elif not isinstance(res, str): - raise ValueError(f'Data from registry is not a string: {type(res)=} {res=}') - - return res - - def get_list(self, key: str, *, default: list = None) -> list: - """ - Return the list referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_list`. - """ - res = self.__get_regentry(key) - if res is None: - return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default - - elif not isinstance(res, list): - raise ValueError(f'Data from registry is not a list: {type(res)=} {res}') - - return res - - def get_int(self, key: str, *, default: int = 0) -> int: - """ - Return the int referred to by key if it exists in the config. - - Implements :meth:`AbstractConfig.get_int`. - """ - res = self.__get_regentry(key) - if res is None: - return default - - if not isinstance(res, int): - raise ValueError(f'Data from registry is not an int: {type(res)=} {res}') - - return res - - def get_bool(self, key: str, *, default: bool = None) -> bool: - """ - Return the bool referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_bool`. - """ - res = self.get_int(key, default=default) # type: ignore - if res is None: - return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default - - return bool(res) - - def set(self, key: str, val: Union[int, str, List[str], bool]) -> None: - """ - Set the given key's data to the given value. - - Implements :meth:`AbstractConfig.set`. - """ - reg_type = None - if isinstance(val, str): - reg_type = winreg.REG_SZ - winreg.SetValueEx(self.__reg_handle, key, REG_RESERVED_ALWAYS_ZERO, winreg.REG_SZ, val) - - elif isinstance(val, int): # The original code checked for numbers.Integral, I dont think that is needed. - reg_type = winreg.REG_DWORD - - elif isinstance(val, list): - reg_type = winreg.REG_MULTI_SZ - - elif isinstance(val, bool): - reg_type = winreg.REG_DWORD - val = int(val) - - else: - raise ValueError(f'Unexpected type for value {type(val)=}') - - # Its complaining about the list, it works, tested on windows, ignored. - winreg.SetValueEx(self.__reg_handle, key, REG_RESERVED_ALWAYS_ZERO, reg_type, val) # type: ignore - - def delete(self, key: str, *, suppress=False) -> None: - """ - Delete the given key from the config. - - 'key' is relative to the base Registry path we use. - - Implements :meth:`AbstractConfig.delete`. - """ - try: - winreg.DeleteValue(self.__reg_handle, key) - except OSError: - if suppress: - return - - raise - - def save(self) -> None: - """ - Save the configuration. - - Not required for WinConfig as Registry keys are flushed on write. - """ - pass - - def close(self): - """ - Close this config and release any associated resources. - - Implements :meth:`AbstractConfig.close`. - """ - self.__reg_handle.Close() - - -class MacConfig(AbstractConfig): - """MacConfig is the implementation of AbstractConfig for Darwin based OSes.""" - - def __init__(self) -> None: - super().__init__() - support_path = pathlib.Path( - NSSearchPathForDirectoriesInDomains( - NSApplicationSupportDirectory, NSUserDomainMask, True - )[0] - ) - - self.app_dir_path = support_path / appname - self.app_dir_path.mkdir(exist_ok=True) - - self.plugin_dir_path = self.app_dir_path / 'plugins' - self.plugin_dir_path.mkdir(exist_ok=True) - - # Bundle IDs identify a singled app though out a system - - if getattr(sys, 'frozen', False): - exe_dir = pathlib.Path(sys.executable).parent - self.internal_plugin_dir_path = exe_dir.parent / 'Library' / 'plugins' - self.respath_path = exe_dir.parent / 'Resources' - self.identifier = NSBundle.mainBundle().bundleIdentifier() - - else: - file_dir = pathlib.Path(__file__).parent - self.internal_plugin_dir_path = file_dir / 'plugins' - self.respath_path = file_dir - - self.identifier = f'uk.org.marginal.{appname.lower()}' - NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier - - self.default_journal_dir_path = support_path / 'Frontier Developments' / 'Elite Dangerous' - self._defaults = NSUserDefaults.standardUserDefaults() - self._settings: Dict[str, Union[int, str, list]] = dict( - self._defaults.persistentDomainForName_(self.identifier) or {} - ) # make writeable - - if (out_dir := self.get_str('out_dir')) is None or not pathlib.Path(out_dir).exists(): - self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0]) - - def __raw_get(self, key: str) -> Union[None, list, str, int]: - """ - Retrieve the raw data for the given key. - - :param str: str - The key data is being requested for. - :return: The requested data. - """ - res = self._settings.get(key) - # On MacOS Catalina, with python.org python 3.9.2 any 'list' - # has type __NSCFArray so a simple `isinstance(res, list)` is - # False. So, check it's not-None, and not the other types. - # - # If we can find where to import the definition of NSCFArray - # then we could possibly test against that. - if res is not None and not isinstance(res, str) and not isinstance(res, int): - return list(res) - - return res - - def get_str(self, key: str, *, default: str = None) -> str: - """ - Return the string referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_str`. - """ - res = self.__raw_get(key) - if res is None: - return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default - - if not isinstance(res, str): - raise ValueError(f'unexpected data returned from __raw_get: {type(res)=} {res}') - - return res - - def get_list(self, key: str, *, default: list = None) -> list: - """ - Return the list referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_list`. - """ - res = self.__raw_get(key) - if res is None: - return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default - - elif not isinstance(res, list): - raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}') - - return res - - def get_int(self, key: str, *, default: int = 0) -> int: - """ - Return the int referred to by key if it exists in the config. - - Implements :meth:`AbstractConfig.get_int`. - """ - res = self.__raw_get(key) - if res is None: - return default - - elif not isinstance(res, (str, int)): - raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}') - - try: - return int(res) - - except ValueError as e: - logger.error(f'__raw_get returned {res!r} which cannot be parsed to an int: {e}') - return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default - - def get_bool(self, key: str, *, default: bool = None) -> bool: - """ - Return the bool referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_bool`. - """ - res = self.__raw_get(key) - if res is None: - return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default - - elif not isinstance(res, bool): - raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}') - - return res - - def set(self, key: str, val: Union[int, str, List[str], bool]) -> None: - """ - Set the given key's data to the given value. - - Implements :meth:`AbstractConfig.set`. - """ - if self._settings is None: - raise ValueError('attempt to use a closed _settings') - - if not isinstance(val, (bool, str, int, list)): - raise ValueError(f'Unexpected type for value {type(val)=}') - - self._settings[key] = val - - def delete(self, key: str, *, suppress=False) -> None: - """ - Delete the given key from the config. - - Implements :meth:`AbstractConfig.delete`. - """ - try: - del self._settings[key] - - except Exception: - if suppress: - pass - - def save(self) -> None: - """ - Save the current configuration. - - Implements :meth:`AbstractConfig.save`. - """ - self._defaults.setPersistentDomain_forName_(self._settings, self.identifier) - self._defaults.synchronize() - - def close(self) -> None: - """ - Close this config and release any associated resources. - - Implements :meth:`AbstractConfig.close`. - """ - self.save() - self._defaults = None - - -class LinuxConfig(AbstractConfig): - """Linux implementation of AbstractConfig.""" - - SECTION = 'config' - # TODO: I dislike this, would rather use a sane config file format. But here we are. - __unescape_lut = {'\\': '\\', 'n': '\n', ';': ';', 'r': '\r', '#': '#'} - __escape_lut = {'\\': '\\', '\n': 'n', ';': ';', '\r': 'r'} - - def __init__(self, filename: Optional[str] = None) -> None: - super().__init__() - # http://standards.freedesktop.org/basedir-spec/latest/ar01s03.html - xdg_data_home = pathlib.Path(os.getenv('XDG_DATA_HOME', default='~/.local/share')).expanduser() - self.app_dir_path = xdg_data_home / appname - self.app_dir_path.mkdir(exist_ok=True, parents=True) - - self.plugin_dir_path = self.app_dir_path / 'plugins' - self.plugin_dir_path.mkdir(exist_ok=True) - - self.respath_path = pathlib.Path(__file__).parent - - self.internal_plugin_dir_path = self.respath_path / 'plugins' - self.default_journal_dir_path = None # type: ignore - self.identifier = f'uk.org.marginal.{appname.lower()}' # TODO: Unused? - - config_home = pathlib.Path(os.getenv('XDG_CONFIG_HOME', default='~/.config')).expanduser() - - self.filename = config_home / appname / f'{appname}.ini' - if filename is not None: - self.filename = pathlib.Path(filename) - - self.filename.parent.mkdir(exist_ok=True, parents=True) - - self.config: Optional[ConfigParser] = ConfigParser(comment_prefixes=('#',), interpolation=None) - self.config.read(self.filename) # read() ignores files that dont exist - - # Ensure that our section exists. This is here because configparser will happily create files for us, but it - # does not magically create sections - try: - self.config[self.SECTION].get("this_does_not_exist", fallback=None) - except KeyError: - logger.info("Config section not found. Backing up existing file (if any) and readding a section header") - if self.filename.exists(): - (self.filename.parent / f'{appname}.ini.backup').write_bytes(self.filename.read_bytes()) - - self.config.add_section(self.SECTION) - - if (outdir := self.get_str('outdir')) is None or not pathlib.Path(outdir).is_dir(): - self.set('outdir', self.home) - - def __escape(self, s: str) -> str: - """ - Escape a string using self.__escape_lut. - - This does NOT support multi-character escapes. - - :param s: str - String to be escaped. - :return: str - The escaped string. - """ - out = "" - for c in s: - if c not in self.__escape_lut: - out += c - continue - - out += '\\' + self.__escape_lut[c] - - return out - - def __unescape(self, s: str) -> str: - """ - Unescape a string. - - :param s: str - The string to unescape. - :return: str - The unescaped string. - """ - out: List[str] = [] - i = 0 - while i < len(s): - c = s[i] - if c != '\\': - out.append(c) - i += 1 - continue - - # We have a backslash, check what its escaping - if i == len(s)-1: - raise ValueError('Escaped string has unescaped trailer') - - unescaped = self.__unescape_lut.get(s[i+1]) - if unescaped is None: - raise ValueError(f'Unknown escape: \\ {s[i+1]}') - - out.append(unescaped) - i += 2 - - return "".join(out) - - def __raw_get(self, key: str) -> Optional[str]: - """ - Get a raw data value from the config file. - - :param key: str - The key data is being requested for. - :return: str - The raw data, if found. - """ - if self.config is None: - raise ValueError('Attempt to use a closed config') - - return self.config[self.SECTION].get(key) - - def get_str(self, key: str, *, default: str = None) -> str: - """ - Return the string referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_str`. - """ - data = self.__raw_get(key) - if data is None: - return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default - - if '\n' in data: - raise ValueError('asked for string, got list') - - return self.__unescape(data) - - def get_list(self, key: str, *, default: list = None) -> list: - """ - Return the list referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_list`. - """ - data = self.__raw_get(key) - - if data is None: - return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default - - split = data.split('\n') - if split[-1] != ';': - raise ValueError('Encoded list does not have trailer sentinel') - - return list(map(self.__unescape, split[:-1])) - - def get_int(self, key: str, *, default: int = 0) -> int: - """ - Return the int referred to by key if it exists in the config. - - Implements :meth:`AbstractConfig.get_int`. - """ - data = self.__raw_get(key) - - if data is None: - return default - - try: - return int(data) - - except ValueError as e: - raise ValueError(f'requested {key=} as int cannot be converted to int') from e - - def get_bool(self, key: str, *, default: bool = None) -> bool: - """ - Return the bool referred to by the given key if it exists, or the default. - - Implements :meth:`AbstractConfig.get_bool`. - """ - if self.config is None: - raise ValueError('attempt to use a closed config') - - data = self.__raw_get(key) - if data is None: - return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default - - return bool(int(data)) - - def set(self, key: str, val: Union[int, str, List[str]]) -> None: - """ - Set the given key's data to the given value. - - Implements :meth:`AbstractConfig.set`. - """ - if self.config is None: - raise ValueError('attempt to use a closed config') - - to_set: Optional[str] = None - if isinstance(val, bool): - to_set = str(int(val)) - - elif isinstance(val, str): - to_set = self.__escape(val) - - elif isinstance(val, int): - to_set = str(val) - - elif isinstance(val, list): - to_set = '\n'.join([self.__escape(s) for s in val] + [';']) - - else: - raise ValueError(f'Unexpected type for value {type(val)=}') - - self.config.set(self.SECTION, key, to_set) - self.save() - - def delete(self, key: str, *, suppress=False) -> None: - """ - Delete the given key from the config. - - Implements :meth:`AbstractConfig.delete`. - """ - if self.config is None: - raise ValueError('attempt to use a closed config') - - self.config.remove_option(self.SECTION, key) - self.save() - - def save(self) -> None: - """ - Save the current configuration. - - Implements :meth:`AbstractConfig.save`. - """ - if self.config is None: - raise ValueError('attempt to use a closed config') - - with open(self.filename, 'w', encoding='utf-8') as f: - self.config.write(f) - - def close(self) -> None: - """ - Close this config and release any associated resources. - - Implements :meth:`AbstractConfig.close`. - """ - self.save() - self.config = None - - -def get_config(*args, **kwargs) -> AbstractConfig: - """ - Get the appropriate config class for the current platform. - - :param args: Args to be passed through to implementation. - :param kwargs: Args to be passed through to implementation. - :return: Instance of the implementation. - """ - if sys.platform == "darwin": - return MacConfig(*args, **kwargs) - elif sys.platform == "win32": - return WinConfig(*args, **kwargs) - elif sys.platform == "linux": - return LinuxConfig(*args, **kwargs) - else: - raise ValueError(f'Unknown platform: {sys.platform=}') - - -config = get_config() diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 00000000..eff2859b --- /dev/null +++ b/config/__init__.py @@ -0,0 +1,473 @@ +""" +Code dealing with the configuration of the program. + +Windows uses the Registry to store values in a flat manner. +Linux uses a file, but for commonality it's still a flat data structure. +macOS uses a 'defaults' object. +""" + + +__all__ = [ + # defined in the order they appear in the file + 'GITVERSION_FILE', + 'appname', + 'applongname', + 'appcmdname', + 'copyright', + 'update_feed', + 'update_interval', + 'debug_senders', + 'trace_on', + 'capi_pretend_down', + 'capi_debug_access_token', + 'logger', + 'git_shorthash_from_head', + 'appversion', + 'user_agent', + 'appversion_nobuild', + 'AbstractConfig', + 'config' +] + +import abc +import contextlib +import logging +import os +import pathlib +import re +import subprocess +import sys +import traceback +import warnings +from abc import abstractmethod +from typing import Any, Callable, List, Optional, Type, TypeVar, Union + +import semantic_version + +from constants import GITVERSION_FILE, applongname, appname + +# Any of these may be imported by plugins +appcmdname = 'EDMC' +# appversion **MUST** follow Semantic Versioning rules: +# +# Major.Minor.Patch(-prerelease)(+buildmetadata) +# NB: Do *not* import this, use the functions appversion() and appversion_nobuild() +_static_appversion = '5.3.0-beta4' +_cached_version: Optional[semantic_version.Version] = None +copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD' + +update_feed = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' +update_interval = 8*60*60 +# Providers marked to be in debug mode. Generally this is expected to switch to sending data to a log file +debug_senders: List[str] = [] +# TRACE logging code that should actually be used. Means not spamming it +# *all* if only interested in some things. +trace_on: List[str] = [] + +capi_pretend_down: bool = False +capi_debug_access_token: Optional[str] = None +# This must be done here in order to avoid an import cycle with EDMCLogging. +# Other code should use EDMCLogging.get_main_logger +if os.getenv("EDMC_NO_UI"): + logger = logging.getLogger(appcmdname) + +else: + logger = logging.getLogger(appname) + + +_T = TypeVar('_T') + + +########################################################################### +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 + + try: + git_cmd = subprocess.Popen('git rev-parse --short HEAD'.split(), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) + out, err = git_cmd.communicate() + + except Exception as e: + logger.info(f"Couldn't run git command for short hash: {e!r}") + + else: + shorthash = out.decode().rstrip('\n') + if re.match(r'^[0-9a-f]{7,}$', shorthash) is None: + logger.error(f"'{shorthash}' doesn't look like a valid git short hash, forcing to None") + shorthash = None # type: ignore + + if shorthash is not None: + with contextlib.suppress(Exception): + result = subprocess.run('git diff --stat HEAD'.split(), capture_output=True) + if len(result.stdout) > 0: + shorthash += '.DIRTY' + + if len(result.stderr) > 0: + logger.warning(f'Data from git on stderr:\n{str(result.stderr)}') + + return shorthash + + +def appversion() -> semantic_version.Version: + """ + Determine app version including git short hash if possible. + + :return: The augmented app version. + """ + global _cached_version + if _cached_version is not None: + return _cached_version + + if getattr(sys, 'frozen', False): + # Running frozen, so we should have a .gitversion file + # Yes, .parent because if frozen we're inside library.zip + with open(pathlib.Path(sys.path[0]).parent / GITVERSION_FILE, 'r', encoding='utf-8') as gitv: + shorthash = gitv.read() + + else: + # Running from source + shorthash = git_shorthash_from_head() + if shorthash is None: + shorthash = 'UNKNOWN' + + _cached_version = semantic_version.Version(f'{_static_appversion}+{shorthash}') + return _cached_version + + +user_agent = f'EDCD-{appname}-{appversion()}' + + +def appversion_nobuild() -> semantic_version.Version: + """ + Determine app version without *any* build meta data. + + This will not only strip any added git short hash, but also any trailing + '+' in _static_appversion. + + :return: App version without any build meta data. + """ + return appversion().truncate('prerelease') +########################################################################### + + +class AbstractConfig(abc.ABC): + """Abstract root class of all platform specific Config implementations.""" + + OUT_MKT_EDDN = 1 + # OUT_MKT_BPC = 2 # No longer supported + OUT_MKT_TD = 4 + OUT_MKT_CSV = 8 + OUT_SHIP = 16 + # OUT_SHIP_EDS = 16 # Replaced by OUT_SHIP + # OUT_SYS_FILE = 32 # No longer supported + # OUT_STAT = 64 # No longer available + # OUT_SHIP_CORIOLIS = 128 # Replaced by OUT_SHIP + OUT_STATION_ANY = OUT_MKT_EDDN | OUT_MKT_TD | OUT_MKT_CSV + # OUT_SYS_EDSM = 256 # Now a plugin + # OUT_SYS_AUTO = 512 # Now always automatic + OUT_MKT_MANUAL = 1024 + OUT_SYS_EDDN = 2048 + OUT_SYS_DELAY = 4096 + + app_dir_path: pathlib.Path + plugin_dir_path: pathlib.Path + internal_plugin_dir_path: pathlib.Path + respath_path: pathlib.Path + home_path: pathlib.Path + default_journal_dir_path: pathlib.Path + + identifier: str + + __in_shutdown = False # Is the application currently shutting down ? + __auth_force_localserver = False # Should we use localhost for auth callback ? + __auth_force_edmc_protocol = False # Should we force edmc:// protocol ? + __eddn_url = None # Non-default EDDN URL + __eddn_tracking_ui = False # Show EDDN tracking UI ? + + def __init__(self) -> None: + self.home_path = pathlib.Path.home() + + def set_shutdown(self): + """Set flag denoting we're in the shutdown sequence.""" + self.__in_shutdown = True + + @property + def shutting_down(self) -> bool: + """ + Determine if we're in the shutdown sequence. + + :return: bool - True if in shutdown sequence. + """ + return self.__in_shutdown + + def set_auth_force_localserver(self): + """Set flag to force use of localhost web server for Frontier Auth callback.""" + self.__auth_force_localserver = True + + @property + def auth_force_localserver(self) -> bool: + """ + Determine if use of localhost is forced for Frontier Auth callback. + + :return: bool - True if we should use localhost web server. + """ + return self.__auth_force_localserver + + def set_auth_force_edmc_protocol(self): + """Set flag to force use of localhost web server for Frontier Auth callback.""" + self.__auth_force_edmc_protocol = True + + @property + def auth_force_edmc_protocol(self) -> bool: + """ + Determine if use of localhost is forced for Frontier Auth callback. + + :return: bool - True if we should use localhost web server. + """ + return self.__auth_force_edmc_protocol + + def set_eddn_url(self, eddn_url: str): + """Set the specified eddn URL.""" + self.__eddn_url = eddn_url + + @property + def eddn_url(self) -> Optional[str]: + """ + Provide the custom EDDN URL. + + :return: str - Custom EDDN URL to use. + """ + return self.__eddn_url + + def set_eddn_tracking_ui(self): + """Activate EDDN tracking UI.""" + self.__eddn_tracking_ui = True + + @property + def eddn_tracking_ui(self) -> bool: + """ + Determine if the EDDN tracking UI be shown. + + :return: bool - Should tracking UI be active? + """ + return self.__eddn_tracking_ui + + @property + def app_dir(self) -> str: + """Return a string version of app_dir.""" + return str(self.app_dir_path) + + @property + def plugin_dir(self) -> str: + """Return a string version of plugin_dir.""" + return str(self.plugin_dir_path) + + @property + def internal_plugin_dir(self) -> str: + """Return a string version of internal_plugin_dir.""" + return str(self.internal_plugin_dir_path) + + @property + def respath(self) -> str: + """Return a string version of respath.""" + return str(self.respath_path) + + @property + def home(self) -> str: + """Return a string version of home.""" + return str(self.home_path) + + @property + def default_journal_dir(self) -> str: + """Return a string version of default_journal_dir.""" + return str(self.default_journal_dir_path) + + @staticmethod + def _suppress_call( + func: Callable[..., _T], exceptions: Union[Type[BaseException], List[Type[BaseException]]] = Exception, + *args: Any, **kwargs: Any + ) -> Optional[_T]: + if exceptions is None: + exceptions = [Exception] + + if not isinstance(exceptions, list): + exceptions = [exceptions] + + with contextlib.suppress(*exceptions): # type: ignore # it works fine, mypy + return func(*args, **kwargs) + + return None + + def get(self, key: str, default: Union[list, str, bool, int] = None) -> Union[list, str, bool, int]: + """ + Return the data for the requested key, or a default. + + :param key: The key data is being requested for. + :param default: The default to return if the key does not exist, defaults to None. + :raises OSError: On Windows, if a Registry error occurs. + :return: The data or the default. + """ + warnings.warn(DeprecationWarning('get is Deprecated. use the specific getter for your type')) + logger.debug('Attempt to use Deprecated get() method\n' + ''.join(traceback.format_stack())) + + if (l := self._suppress_call(self.get_list, ValueError, key, default=None)) is not None: + return l + + elif (s := self._suppress_call(self.get_str, ValueError, key, default=None)) is not None: + return s + + elif (b := self._suppress_call(self.get_bool, ValueError, key, default=None)) is not None: + return b + + elif (i := self._suppress_call(self.get_int, ValueError, key, default=None)) is not None: + return i + + return default # type: ignore + + @abstractmethod + def get_list(self, key: str, *, default: list = None) -> list: + """ + Return the list referred to by the given key if it exists, or the default. + + Implements :meth:`AbstractConfig.get_list`. + """ + raise NotImplementedError + + @abstractmethod + def get_str(self, key: str, *, default: str = None) -> str: + """ + Return the string referred to by the given key if it exists, or the default. + + :param key: The key data is being requested for. + :param default: Default to return if the key does not exist, defaults to None. + :raises ValueError: If an internal error occurs getting or converting a value. + :raises OSError: On Windows, if a Registry error occurs. + :return: The requested data or the default. + """ + raise NotImplementedError + + @abstractmethod + def get_bool(self, key: str, *, default: bool = None) -> bool: + """ + Return the bool referred to by the given key if it exists, or the default. + + :param key: The key data is being requested for. + :param default: Default to return if the key does not exist, defaults to None + :raises ValueError: If an internal error occurs getting or converting a value + :raises OSError: On Windows, if a Registry error occurs. + :return: The requested data or the default + """ + raise NotImplementedError + + def getint(self, key: str, *, default: int = 0) -> int: + """ + Getint is a Deprecated getter method. + + See get_int for its replacement. + :raises OSError: On Windows, if a Registry error occurs. + """ + warnings.warn(DeprecationWarning('getint is Deprecated. Use get_int instead')) + logger.debug('Attempt to use Deprecated getint() method\n' + ''.join(traceback.format_stack())) + + return self.get_int(key, default=default) + + @abstractmethod + def get_int(self, key: str, *, default: int = 0) -> int: + """ + Return the int referred to by key if it exists in the config. + + For legacy reasons, the default is 0 and not None. + + :param key: The key data is being requested for. + :param default: Default to return if the key does not exist, defaults to 0. + :raises ValueError: If the internal representation of this key cannot be converted to an int. + :raises OSError: On Windows, if a Registry error occurs. + :return: The requested data or the default. + """ + raise NotImplementedError + + @abstractmethod + def set(self, key: str, val: Union[int, str, List[str], bool]) -> None: + """ + Set the given key's data to the given value. + + :param key: The key to set the value on. + :param val: The value to set the key's data to. + :raises ValueError: On an invalid type. + :raises OSError: On Windows, if a Registry error occurs. + """ + raise NotImplementedError + + @abstractmethod + def delete(self, key: str, *, suppress=False) -> None: + """ + Delete the given key from the config. + + :param key: The key to delete. + :param suppress: bool - Whether to suppress any errors. Useful in case + code to migrate settings is blindly removing an old key. + :raises OSError: On Windows, if a registry error occurs. + """ + raise NotImplementedError + + @abstractmethod + def save(self) -> None: + """ + Save the current configuration. + + :raises OSError: On Windows, if a Registry error occurs. + """ + raise NotImplementedError + + @abstractmethod + def close(self) -> None: + """Close this config and release any associated resources.""" + raise NotImplementedError + + def get_password(self, account: str) -> None: + """Legacy password retrieval.""" + warnings.warn("password subsystem is no longer supported", DeprecationWarning) + + def set_password(self, account: str, password: str) -> None: + """Legacy password setting.""" + warnings.warn("password subsystem is no longer supported", DeprecationWarning) + + def delete_password(self, account: str) -> None: + """Legacy password deletion.""" + warnings.warn("password subsystem is no longer supported", DeprecationWarning) + + +def get_config(*args, **kwargs) -> AbstractConfig: + """ + Get the appropriate config class for the current platform. + + :param args: Args to be passed through to implementation. + :param kwargs: Args to be passed through to implementation. + :return: Instance of the implementation. + """ + if sys.platform == "darwin": + from .darwin import MacConfig + return MacConfig(*args, **kwargs) + + elif sys.platform == "win32": + from .windows import WinConfig + return WinConfig(*args, **kwargs) + + elif sys.platform == "linux": + from .linux import LinuxConfig + return LinuxConfig(*args, **kwargs) + + else: + raise ValueError(f'Unknown platform: {sys.platform=}') + + +config = get_config() diff --git a/config/darwin.py b/config/darwin.py new file mode 100644 index 00000000..eb2b887f --- /dev/null +++ b/config/darwin.py @@ -0,0 +1,184 @@ +import pathlib +import sys +from typing import Any, Dict, List, Union + +from Foundation import ( # type: ignore + NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, NSUserDefaults, + NSUserDomainMask +) + +from config import AbstractConfig, appname, logger + +assert sys.platform == 'darwin' + + +class MacConfig(AbstractConfig): + """MacConfig is the implementation of AbstractConfig for Darwin based OSes.""" + + def __init__(self) -> None: + super().__init__() + support_path = pathlib.Path( + NSSearchPathForDirectoriesInDomains( + NSApplicationSupportDirectory, NSUserDomainMask, True + )[0] + ) + + self.app_dir_path = support_path / appname + self.app_dir_path.mkdir(exist_ok=True) + + self.plugin_dir_path = self.app_dir_path / 'plugins' + self.plugin_dir_path.mkdir(exist_ok=True) + + # Bundle IDs identify a singled app though out a system + + if getattr(sys, 'frozen', False): + exe_dir = pathlib.Path(sys.executable).parent + self.internal_plugin_dir_path = exe_dir.parent / 'Library' / 'plugins' + self.respath_path = exe_dir.parent / 'Resources' + self.identifier = NSBundle.mainBundle().bundleIdentifier() + + else: + file_dir = pathlib.Path(__file__).parent.parent + self.internal_plugin_dir_path = file_dir / 'plugins' + self.respath_path = file_dir + + self.identifier = f'uk.org.marginal.{appname.lower()}' + NSBundle.mainBundle().infoDictionary()['CFBundleIdentifier'] = self.identifier + + self.default_journal_dir_path = support_path / 'Frontier Developments' / 'Elite Dangerous' + self._defaults: Any = NSUserDefaults.standardUserDefaults() + self._settings: Dict[str, Union[int, str, list]] = dict( + self._defaults.persistentDomainForName_(self.identifier) or {} + ) # make writeable + + if (out_dir := self.get_str('out_dir')) is None or not pathlib.Path(out_dir).exists(): + self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0]) + + def __raw_get(self, key: str) -> Union[None, list, str, int]: + """ + Retrieve the raw data for the given key. + + :param str: str - The key data is being requested for. + :return: The requested data. + """ + res = self._settings.get(key) + # On MacOS Catalina, with python.org python 3.9.2 any 'list' + # has type __NSCFArray so a simple `isinstance(res, list)` is + # False. So, check it's not-None, and not the other types. + # + # If we can find where to import the definition of NSCFArray + # then we could possibly test against that. + if res is not None and not isinstance(res, str) and not isinstance(res, int): + return list(res) + + return res + + def get_str(self, key: str, *, default: str = None) -> str: + """ + Return the string referred to by the given key if it exists, or the default. + + Implements :meth:`AbstractConfig.get_str`. + """ + res = self.__raw_get(key) + if res is None: + return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default + + if not isinstance(res, str): + raise ValueError(f'unexpected data returned from __raw_get: {type(res)=} {res}') + + return res + + def get_list(self, key: str, *, default: list = None) -> list: + """ + Return the list referred to by the given key if it exists, or the default. + + Implements :meth:`AbstractConfig.get_list`. + """ + res = self.__raw_get(key) + if res is None: + return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default + + elif not isinstance(res, list): + raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}') + + return res + + def get_int(self, key: str, *, default: int = 0) -> int: + """ + Return the int referred to by key if it exists in the config. + + Implements :meth:`AbstractConfig.get_int`. + """ + res = self.__raw_get(key) + if res is None: + return default + + elif not isinstance(res, (str, int)): + raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}') + + try: + return int(res) + + except ValueError as e: + logger.error(f'__raw_get returned {res!r} which cannot be parsed to an int: {e}') + return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default + + def get_bool(self, key: str, *, default: bool = None) -> bool: + """ + Return the bool referred to by the given key if it exists, or the default. + + Implements :meth:`AbstractConfig.get_bool`. + """ + res = self.__raw_get(key) + if res is None: + return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default + + elif not isinstance(res, bool): + raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}') + + return res + + def set(self, key: str, val: Union[int, str, List[str], bool]) -> None: + """ + Set the given key's data to the given value. + + Implements :meth:`AbstractConfig.set`. + """ + if self._settings is None: + raise ValueError('attempt to use a closed _settings') + + if not isinstance(val, (bool, str, int, list)): + raise ValueError(f'Unexpected type for value {type(val)=}') + + self._settings[key] = val + + def delete(self, key: str, *, suppress=False) -> None: + """ + Delete the given key from the config. + + Implements :meth:`AbstractConfig.delete`. + """ + try: + del self._settings[key] + + except Exception: + if suppress: + pass + + def save(self) -> None: + """ + Save the current configuration. + + Implements :meth:`AbstractConfig.save`. + """ + self._defaults.setPersistentDomain_forName_(self._settings, self.identifier) + self._defaults.synchronize() + + def close(self) -> None: + """ + Close this config and release any associated resources. + + Implements :meth:`AbstractConfig.close`. + """ + self.save() + self._defaults = None diff --git a/config/linux.py b/config/linux.py new file mode 100644 index 00000000..04087b32 --- /dev/null +++ b/config/linux.py @@ -0,0 +1,245 @@ +"""Linux config implementation.""" +import os +import pathlib +import sys +from configparser import ConfigParser +from typing import List, Optional, Union + +from config import AbstractConfig, appname, logger + +assert sys.platform == 'linux' + + +class LinuxConfig(AbstractConfig): + """Linux implementation of AbstractConfig.""" + + SECTION = 'config' + # TODO: I dislike this, would rather use a sane config file format. But here we are. + __unescape_lut = {'\\': '\\', 'n': '\n', ';': ';', 'r': '\r', '#': '#'} + __escape_lut = {'\\': '\\', '\n': 'n', ';': ';', '\r': 'r'} + + def __init__(self, filename: Optional[str] = None) -> None: + super().__init__() + # http://standards.freedesktop.org/basedir-spec/latest/ar01s03.html + xdg_data_home = pathlib.Path(os.getenv('XDG_DATA_HOME', default='~/.local/share')).expanduser() + self.app_dir_path = xdg_data_home / appname + self.app_dir_path.mkdir(exist_ok=True, parents=True) + + self.plugin_dir_path = self.app_dir_path / 'plugins' + self.plugin_dir_path.mkdir(exist_ok=True) + + self.respath_path = pathlib.Path(__file__).parent.parent + + self.internal_plugin_dir_path = self.respath_path / 'plugins' + self.default_journal_dir_path = None # type: ignore + self.identifier = f'uk.org.marginal.{appname.lower()}' # TODO: Unused? + + config_home = pathlib.Path(os.getenv('XDG_CONFIG_HOME', default='~/.config')).expanduser() + + self.filename = config_home / appname / f'{appname}.ini' + if filename is not None: + self.filename = pathlib.Path(filename) + + self.filename.parent.mkdir(exist_ok=True, parents=True) + + self.config: Optional[ConfigParser] = ConfigParser(comment_prefixes=('#',), interpolation=None) + self.config.read(self.filename) # read() ignores files that dont exist + + # Ensure that our section exists. This is here because configparser will happily create files for us, but it + # does not magically create sections + try: + self.config[self.SECTION].get("this_does_not_exist", fallback=None) + except KeyError: + logger.info("Config section not found. Backing up existing file (if any) and readding a section header") + if self.filename.exists(): + (self.filename.parent / f'{appname}.ini.backup').write_bytes(self.filename.read_bytes()) + + self.config.add_section(self.SECTION) + + if (outdir := self.get_str('outdir')) is None or not pathlib.Path(outdir).is_dir(): + self.set('outdir', self.home) + + def __escape(self, s: str) -> str: + """ + Escape a string using self.__escape_lut. + + This does NOT support multi-character escapes. + + :param s: str - String to be escaped. + :return: str - The escaped string. + """ + out = "" + for c in s: + if c not in self.__escape_lut: + out += c + continue + + out += '\\' + self.__escape_lut[c] + + return out + + def __unescape(self, s: str) -> str: + """ + Unescape a string. + + :param s: str - The string to unescape. + :return: str - The unescaped string. + """ + out: List[str] = [] + i = 0 + while i < len(s): + c = s[i] + if c != '\\': + out.append(c) + i += 1 + continue + + # We have a backslash, check what its escaping + if i == len(s)-1: + raise ValueError('Escaped string has unescaped trailer') + + unescaped = self.__unescape_lut.get(s[i+1]) + if unescaped is None: + raise ValueError(f'Unknown escape: \\ {s[i+1]}') + + out.append(unescaped) + i += 2 + + return "".join(out) + + def __raw_get(self, key: str) -> Optional[str]: + """ + Get a raw data value from the config file. + + :param key: str - The key data is being requested for. + :return: str - The raw data, if found. + """ + if self.config is None: + raise ValueError('Attempt to use a closed config') + + return self.config[self.SECTION].get(key) + + def get_str(self, key: str, *, default: str = None) -> str: + """ + Return the string referred to by the given key if it exists, or the default. + + Implements :meth:`AbstractConfig.get_str`. + """ + data = self.__raw_get(key) + if data is None: + return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default + + if '\n' in data: + raise ValueError('asked for string, got list') + + return self.__unescape(data) + + def get_list(self, key: str, *, default: list = None) -> list: + """ + Return the list referred to by the given key if it exists, or the default. + + Implements :meth:`AbstractConfig.get_list`. + """ + data = self.__raw_get(key) + + if data is None: + return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default + + split = data.split('\n') + if split[-1] != ';': + raise ValueError('Encoded list does not have trailer sentinel') + + return list(map(self.__unescape, split[:-1])) + + def get_int(self, key: str, *, default: int = 0) -> int: + """ + Return the int referred to by key if it exists in the config. + + Implements :meth:`AbstractConfig.get_int`. + """ + data = self.__raw_get(key) + + if data is None: + return default + + try: + return int(data) + + except ValueError as e: + raise ValueError(f'requested {key=} as int cannot be converted to int') from e + + def get_bool(self, key: str, *, default: bool = None) -> bool: + """ + Return the bool referred to by the given key if it exists, or the default. + + Implements :meth:`AbstractConfig.get_bool`. + """ + if self.config is None: + raise ValueError('attempt to use a closed config') + + data = self.__raw_get(key) + if data is None: + return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default + + return bool(int(data)) + + def set(self, key: str, val: Union[int, str, List[str]]) -> None: + """ + Set the given key's data to the given value. + + Implements :meth:`AbstractConfig.set`. + """ + if self.config is None: + raise ValueError('attempt to use a closed config') + + to_set: Optional[str] = None + if isinstance(val, bool): + to_set = str(int(val)) + + elif isinstance(val, str): + to_set = self.__escape(val) + + elif isinstance(val, int): + to_set = str(val) + + elif isinstance(val, list): + to_set = '\n'.join([self.__escape(s) for s in val] + [';']) + + else: + raise ValueError(f'Unexpected type for value {type(val)=}') + + self.config.set(self.SECTION, key, to_set) + self.save() + + def delete(self, key: str, *, suppress=False) -> None: + """ + Delete the given key from the config. + + Implements :meth:`AbstractConfig.delete`. + """ + if self.config is None: + raise ValueError('attempt to use a closed config') + + self.config.remove_option(self.SECTION, key) + self.save() + + def save(self) -> None: + """ + Save the current configuration. + + Implements :meth:`AbstractConfig.save`. + """ + if self.config is None: + raise ValueError('attempt to use a closed config') + + with open(self.filename, 'w', encoding='utf-8') as f: + self.config.write(f) + + def close(self) -> None: + """ + Close this config and release any associated resources. + + Implements :meth:`AbstractConfig.close`. + """ + self.save() + self.config = None diff --git a/config/windows.py b/config/windows.py new file mode 100644 index 00000000..d29a2b42 --- /dev/null +++ b/config/windows.py @@ -0,0 +1,259 @@ +"""Windows config implementation.""" + +# spell-checker: words folderid deps hkey edcd +import ctypes +import functools +import pathlib +import sys +import uuid +import winreg +from ctypes.wintypes import DWORD, HANDLE +from typing import List, Optional, Union + +from config import AbstractConfig, applongname, appname, logger, update_interval + +assert sys.platform == 'win32' + +REG_RESERVED_ALWAYS_ZERO = 0 + +# This is the only way to do this from python without external deps (which do this anyway). +FOLDERID_Documents = uuid.UUID('{FDD39AD0-238F-46AF-ADB4-6C85480369C7}') +FOLDERID_LocalAppData = uuid.UUID('{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}') +FOLDERID_Profile = uuid.UUID('{5E6C858F-0E22-4760-9AFE-EA3317B67173}') +FOLDERID_SavedGames = uuid.UUID('{4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4}') + +SHGetKnownFolderPath = ctypes.windll.shell32.SHGetKnownFolderPath +SHGetKnownFolderPath.argtypes = [ctypes.c_char_p, DWORD, HANDLE, ctypes.POINTER(ctypes.c_wchar_p)] + +CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree +CoTaskMemFree.argtypes = [ctypes.c_void_p] + + +def known_folder_path(guid: uuid.UUID) -> Optional[str]: + """Look up a Windows GUID to actual folder path name.""" + buf = ctypes.c_wchar_p() + if SHGetKnownFolderPath(ctypes.create_string_buffer(guid.bytes_le), 0, 0, ctypes.byref(buf)): + return None + retval = buf.value # copy data + CoTaskMemFree(buf) # and free original + return retval + + +class WinConfig(AbstractConfig): + """Implementation of AbstractConfig for Windows.""" + + def __init__(self, do_winsparkle=True) -> None: + self.app_dir_path = pathlib.Path(str(known_folder_path(FOLDERID_LocalAppData))) / appname + self.app_dir_path.mkdir(exist_ok=True) + + self.plugin_dir_path = self.app_dir_path / 'plugins' + self.plugin_dir_path.mkdir(exist_ok=True) + + if getattr(sys, 'frozen', False): + self.respath_path = pathlib.Path(sys.executable).parent + self.internal_plugin_dir_path = self.respath_path / 'plugins' + + else: + self.respath_path = pathlib.Path(__file__).parent.parent + self.internal_plugin_dir_path = self.respath_path / 'plugins' + + self.home_path = pathlib.Path.home() + + journal_dir_str = known_folder_path(FOLDERID_SavedGames) + journaldir = pathlib.Path(journal_dir_str) if journal_dir_str is not None else None + self.default_journal_dir_path = None # type: ignore + if journaldir is not None: + self.default_journal_dir_path = journaldir / 'Frontier Developments' / 'Elite Dangerous' + + create_key_defaults = functools.partial( + winreg.CreateKeyEx, + key=winreg.HKEY_CURRENT_USER, + access=winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY, + ) + + try: + self.__reg_handle: winreg.HKEYType = create_key_defaults( + sub_key=r'Software\Marginal\EDMarketConnector' + ) + if do_winsparkle: + self.__setup_winsparkle() + + except OSError: + logger.exception('could not create required registry keys') + raise + + self.identifier = applongname + if (outdir_str := self.get_str('outdir')) is None or not pathlib.Path(outdir_str).is_dir(): + docs = known_folder_path(FOLDERID_Documents) + self.set('outdir', docs if docs is not None else self.home) + + def __setup_winsparkle(self): + """Ensure the necessary Registry keys for WinSparkle are present.""" + create_key_defaults = functools.partial( + winreg.CreateKeyEx, + key=winreg.HKEY_CURRENT_USER, + access=winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY, + ) + try: + edcd_handle: winreg.HKEYType = create_key_defaults(sub_key=r'Software\EDCD\EDMarketConnector') + winsparkle_reg: winreg.HKEYType = winreg.CreateKeyEx( + edcd_handle, sub_key='WinSparkle', access=winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY + ) + + except OSError: + logger.exception('could not open WinSparkle handle') + raise + + # set WinSparkle defaults - https://github.com/vslavik/winsparkle/wiki/Registry-Settings + winreg.SetValueEx( + winsparkle_reg, 'UpdateInterval', REG_RESERVED_ALWAYS_ZERO, winreg.REG_SZ, str(update_interval) + ) + + try: + winreg.QueryValueEx(winsparkle_reg, 'CheckForUpdates') + + except FileNotFoundError: + # Key doesn't exist, set it to a default + winreg.SetValueEx(winsparkle_reg, 'CheckForUpdates', REG_RESERVED_ALWAYS_ZERO, winreg.REG_SZ, '1') + + winsparkle_reg.Close() + edcd_handle.Close() + + def __get_regentry(self, key: str) -> Union[None, list, str, int]: + """Access the Registry for the raw entry.""" + try: + value, _type = winreg.QueryValueEx(self.__reg_handle, key) + except FileNotFoundError: + # Key doesn't exist + return None + + # The type returned is actually as we'd expect for each of these. The casts are here for type checkers and + # For programmers who want to actually know what is going on + if _type == winreg.REG_SZ: + return str(value) + + elif _type == winreg.REG_DWORD: + return int(value) + + elif _type == winreg.REG_MULTI_SZ: + return list(value) + + else: + logger.warning(f'registry key {key=} returned unknown type {_type=} {value=}') + return None + + def get_str(self, key: str, *, default: str = None) -> str: + """ + Return the string referred to by the given key if it exists, or the default. + + Implements :meth:`AbstractConfig.get_str`. + """ + res = self.__get_regentry(key) + if res is None: + return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default + + elif not isinstance(res, str): + raise ValueError(f'Data from registry is not a string: {type(res)=} {res=}') + + return res + + def get_list(self, key: str, *, default: list = None) -> list: + """ + Return the list referred to by the given key if it exists, or the default. + + Implements :meth:`AbstractConfig.get_list`. + """ + res = self.__get_regentry(key) + if res is None: + return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default + + elif not isinstance(res, list): + raise ValueError(f'Data from registry is not a list: {type(res)=} {res}') + + return res + + def get_int(self, key: str, *, default: int = 0) -> int: + """ + Return the int referred to by key if it exists in the config. + + Implements :meth:`AbstractConfig.get_int`. + """ + res = self.__get_regentry(key) + if res is None: + return default + + if not isinstance(res, int): + raise ValueError(f'Data from registry is not an int: {type(res)=} {res}') + + return res + + def get_bool(self, key: str, *, default: bool = None) -> bool: + """ + Return the bool referred to by the given key if it exists, or the default. + + Implements :meth:`AbstractConfig.get_bool`. + """ + res = self.get_int(key, default=default) # type: ignore + if res is None: + return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default + + return bool(res) + + def set(self, key: str, val: Union[int, str, List[str], bool]) -> None: + """ + Set the given key's data to the given value. + + Implements :meth:`AbstractConfig.set`. + """ + reg_type = None + if isinstance(val, str): + reg_type = winreg.REG_SZ + winreg.SetValueEx(self.__reg_handle, key, REG_RESERVED_ALWAYS_ZERO, winreg.REG_SZ, val) + + elif isinstance(val, int): # The original code checked for numbers.Integral, I dont think that is needed. + reg_type = winreg.REG_DWORD + + elif isinstance(val, list): + reg_type = winreg.REG_MULTI_SZ + + elif isinstance(val, bool): + reg_type = winreg.REG_DWORD + val = int(val) + + else: + raise ValueError(f'Unexpected type for value {type(val)=}') + + # Its complaining about the list, it works, tested on windows, ignored. + winreg.SetValueEx(self.__reg_handle, key, REG_RESERVED_ALWAYS_ZERO, reg_type, val) # type: ignore + + def delete(self, key: str, *, suppress=False) -> None: + """ + Delete the given key from the config. + + 'key' is relative to the base Registry path we use. + + Implements :meth:`AbstractConfig.delete`. + """ + try: + winreg.DeleteValue(self.__reg_handle, key) + except OSError: + if suppress: + return + + raise + + def save(self) -> None: + """ + Save the configuration. + + Not required for WinConfig as Registry keys are flushed on write. + """ + pass + + def close(self): + """ + Close this config and release any associated resources. + + Implements :meth:`AbstractConfig.close`. + """ + self.__reg_handle.Close() From bff6175ee79c2687c07e5578a3ba3747ff7096b0 Mon Sep 17 00:00:00 2001 From: A_D Date: Mon, 24 Jan 2022 13:54:04 +0200 Subject: [PATCH 084/186] Update to use sys.platform over platform --- EDMarketConnector.py | 52 +++++---- dashboard.py | 15 ++- journal_lock.py | 10 +- l10n.py | 21 ++-- myNotebook.py | 82 +++++++------ prefs.py | 48 ++++---- stats.py | 16 ++- tests/config.py/_old_config.py | 13 +-- theme.py | 206 +++++++++++++++++---------------- ttkHyperlinkLabel.py | 57 ++++----- 10 files changed, 267 insertions(+), 253 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 01c9d846..6aa27ca3 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """Entry point for the main GUI application.""" +from __future__ import annotations import argparse import html @@ -14,7 +15,6 @@ import webbrowser from builtins import object, str from os import chdir, environ from os.path import dirname, join -from sys import platform from time import localtime, strftime, time from typing import TYPE_CHECKING, Optional, Tuple, Union @@ -23,7 +23,7 @@ from typing import TYPE_CHECKING, Optional, Tuple, Union # place for things like config.py reading .gitversion if getattr(sys, 'frozen', False): # Under py2exe sys.path[0] is the executable name - if platform == 'win32': + if sys.platform == 'win32': chdir(dirname(sys.path[0])) # Allow executable to be invoked from any cwd environ['TCL_LIBRARY'] = join(dirname(sys.path[0]), 'lib', 'tcl') @@ -234,7 +234,7 @@ if __name__ == '__main__': # noqa: C901 """Handle any edmc:// auth callback, else foreground existing window.""" logger.trace_if('frontier-auth.windows', 'Begin...') - if platform == 'win32': + if sys.platform == 'win32': # If *this* instance hasn't locked, then another already has and we # now need to do the edmc:// checks for auth callback @@ -370,8 +370,9 @@ if __name__ == '__main__': # noqa: C901 # isort: off if TYPE_CHECKING: from logging import TRACE # type: ignore # noqa: F401 # Needed to update mypy - import update - from infi.systray import SysTrayIcon + + if sys.platform == 'win32': + from infi.systray import SysTrayIcon # isort: on def _(x: str) -> str: @@ -443,7 +444,7 @@ class AppWindow(object): self.prefsdialog = None - if platform == 'win32': + if sys.platform == 'win32': from infi.systray import SysTrayIcon def open_window(systray: 'SysTrayIcon') -> None: @@ -456,8 +457,8 @@ class AppWindow(object): plug.load_plugins(master) - if platform != 'darwin': - if platform == 'win32': + if sys.platform != 'darwin': + if sys.platform == 'win32': self.w.wm_iconbitmap(default='EDMarketConnector.ico') else: @@ -527,7 +528,7 @@ class AppWindow(object): # LANG: Update button in main window self.button = ttk.Button(frame, text=_('Update'), width=28, default=tk.ACTIVE, state=tk.DISABLED) - self.theme_button = tk.Label(frame, width=32 if platform == 'darwin' else 28, state=tk.DISABLED) + self.theme_button = tk.Label(frame, width=32 if sys.platform == 'darwin' else 28, state=tk.DISABLED) self.status = tk.Label(frame, name='status', anchor=tk.W) ui_row = frame.grid_size()[1] @@ -540,14 +541,15 @@ class AppWindow(object): theme.button_bind(self.theme_button, self.capi_request_data) for child in frame.winfo_children(): - child.grid_configure(padx=self.PADX, pady=(platform != 'win32' or isinstance(child, tk.Frame)) and 2 or 0) + child.grid_configure(padx=self.PADX, pady=( + sys.platform != 'win32' or isinstance(child, tk.Frame)) and 2 or 0) # The type needs defining for adding the menu entry, but won't be # properly set until later self.updater: update.Updater = None self.menubar = tk.Menu() - if platform == 'darwin': + if sys.platform == 'darwin': # Can't handle (de)iconify if topmost is set, so suppress iconify button # http://wiki.tcl.tk/13428 and p15 of # https://developer.apple.com/legacy/library/documentation/Carbon/Conceptual/HandlingWindowsControls/windowscontrols.pdf @@ -603,7 +605,7 @@ class AppWindow(object): self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w)) self.menubar.add_cascade(menu=self.help_menu) - if platform == 'win32': + if sys.platform == 'win32': # Must be added after at least one "real" menu entry self.always_ontop = tk.BooleanVar(value=bool(config.get_int('always_ontop'))) self.system_menu = tk.Menu(self.menubar, name='system', tearoff=tk.FALSE) @@ -674,11 +676,11 @@ class AppWindow(object): if config.get_str('geometry'): match = re.match(r'\+([\-\d]+)\+([\-\d]+)', config.get_str('geometry')) if match: - if platform == 'darwin': + if sys.platform == 'darwin': # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 if int(match.group(2)) >= 0: self.w.geometry(config.get_str('geometry')) - elif platform == 'win32': + elif sys.platform == 'win32': # Check that the titlebar will be at least partly on screen import ctypes from ctypes.wintypes import POINT @@ -776,7 +778,7 @@ class AppWindow(object): self.suit_shown = True if not self.suit_shown: - if platform != 'win32': + if sys.platform != 'win32': pady = 2 else: @@ -826,7 +828,7 @@ class AppWindow(object): self.system_label['text'] = _('System') + ':' # LANG: Label for 'System' line in main UI self.station_label['text'] = _('Station') + ':' # LANG: Label for 'Station' line in main UI self.button['text'] = self.theme_button['text'] = _('Update') # LANG: Update button in main window - if platform == 'darwin': + if sys.platform == 'darwin': self.menubar.entryconfigure(1, label=_('File')) # LANG: 'File' menu title on OSX self.menubar.entryconfigure(2, label=_('Edit')) # LANG: 'Edit' menu title on OSX self.menubar.entryconfigure(3, label=_('View')) # LANG: 'View' menu title on OSX @@ -873,7 +875,7 @@ class AppWindow(object): self.button['state'] = self.theme_button['state'] = tk.DISABLED - if platform == 'darwin': + if sys.platform == 'darwin': self.view_menu.entryconfigure(0, state=tk.DISABLED) # Status self.file_menu.entryconfigure(0, state=tk.DISABLED) # Save Raw Data @@ -887,7 +889,7 @@ class AppWindow(object): # LANG: Successfully authenticated with the Frontier website self.status['text'] = _('Authentication successful') - if platform == 'darwin': + if sys.platform == 'darwin': self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data @@ -1211,7 +1213,7 @@ class AppWindow(object): companion.session.invalidate() self.login() - except companion.ServerConnectionError as e: + except companion.ServerConnectionError as e: # TODO: unreachable (subclass of ServerLagging -- move to above) logger.warning(f'Exception while contacting server: {e}') err = self.status['text'] = str(e) play_bad = True @@ -1429,7 +1431,7 @@ class AppWindow(object): companion.session.auth_callback() # LANG: Successfully authenticated with the Frontier website self.status['text'] = _('Authentication successful') - if platform == 'darwin': + if sys.platform == 'darwin': self.view_menu.entryconfigure(0, state=tk.NORMAL) # Status self.file_menu.entryconfigure(0, state=tk.NORMAL) # Save Raw Data @@ -1570,11 +1572,11 @@ class AppWindow(object): # position over parent # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 - if platform != 'darwin' or parent.winfo_rooty() > 0: + if sys.platform != 'darwin' or parent.winfo_rooty() > 0: self.geometry(f'+{parent.winfo_rootx():d}+{parent.winfo_rooty():d}') # remove decoration - if platform == 'win32': + if sys.platform == 'win32': self.attributes('-toolwindow', tk.TRUE) self.resizable(tk.FALSE, tk.FALSE) @@ -1651,7 +1653,7 @@ class AppWindow(object): """ default_extension: str = '' - if platform == 'darwin': + if sys.platform == 'darwin': default_extension = '.json' timestamp: str = strftime('%Y-%m-%dT%H.%M.%S', localtime()) @@ -1676,7 +1678,7 @@ class AppWindow(object): def onexit(self, event=None) -> None: """Application shutdown procedure.""" - if platform == 'win32': + if sys.platform == 'win32': shutdown_thread = threading.Thread(target=self.systray.shutdown) shutdown_thread.setDaemon(True) shutdown_thread.start() @@ -1684,7 +1686,7 @@ class AppWindow(object): config.set_shutdown() # Signal we're in shutdown now. # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 - if platform != 'darwin' or self.w.winfo_rooty() > 0: + if sys.platform != 'darwin' or self.w.winfo_rooty() > 0: x, y = self.w.geometry().split('+')[1:3] # e.g. '212x170+2881+1267' config.set('geometry', f'+{x}+{y}') diff --git a/dashboard.py b/dashboard.py index 43e7e52b..c371e2b7 100644 --- a/dashboard.py +++ b/dashboard.py @@ -2,11 +2,11 @@ import json import pathlib +import sys import time import tkinter as tk from calendar import timegm from os.path import getsize, isdir, isfile -from sys import platform from typing import Any, Dict from config import config @@ -14,11 +14,11 @@ from EDMCLogging import get_main_logger logger = get_main_logger() -if platform == 'darwin': +if sys.platform == 'darwin': from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer -elif platform == 'win32': +elif sys.platform == 'win32': from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer @@ -71,26 +71,25 @@ class Dashboard(FileSystemEventHandler): # File system events are unreliable/non-existent over network drives on Linux. # We can't easily tell whether a path points to a network drive, so assume # any non-standard logdir might be on a network drive and poll instead. - polling = platform != 'win32' - if not polling and not self.observer: + if not (sys.platform != 'win32') and not self.observer: logger.debug('Setting up observer...') self.observer = Observer() self.observer.daemon = True self.observer.start() logger.debug('Done') - elif polling and self.observer: + elif (sys.platform != 'win32') and self.observer: logger.debug('Using polling, stopping observer...') self.observer.stop() self.observer = None # type: ignore logger.debug('Done') - if not self.observed and not polling: + if not self.observed and not (sys.platform != 'win32'): logger.debug('Starting observer...') self.observed = self.observer.schedule(self, self.currentdir) logger.debug('Done') - logger.info(f'{polling and "Polling" or "Monitoring"} Dashboard "{self.currentdir}"') + logger.info(f'{(sys.platform != "win32") and "Polling" or "Monitoring"} Dashboard "{self.currentdir}"') # Even if we're not intending to poll, poll at least once to process pre-existing # data and to check whether the watchdog thread has crashed due to events not diff --git a/journal_lock.py b/journal_lock.py index 6b2e9951..1c984f90 100644 --- a/journal_lock.py +++ b/journal_lock.py @@ -1,10 +1,10 @@ """Implements locking of Journal directory.""" import pathlib +import sys import tkinter as tk from enum import Enum from os import getpid as os_getpid -from sys import platform from tkinter import ttk from typing import TYPE_CHECKING, Callable, Optional @@ -94,7 +94,7 @@ class JournalLock: :return: LockResult - See the class Enum definition """ - if platform == 'win32': + if sys.platform == 'win32': logger.trace_if('journal-lock', 'win32, using msvcrt') # win32 doesn't have fcntl, so we have to use msvcrt import msvcrt @@ -143,7 +143,7 @@ class JournalLock: return True # We weren't locked, and still aren't unlocked = False - if platform == 'win32': + if sys.platform == 'win32': logger.trace_if('journal-lock', 'win32, using msvcrt') # win32 doesn't have fcntl, so we have to use msvcrt import msvcrt @@ -206,10 +206,10 @@ class JournalLock: self.title(_('Journal directory already locked')) # remove decoration - if platform == 'win32': + if sys.platform == 'win32': self.attributes('-toolwindow', tk.TRUE) - elif platform == 'darwin': + elif sys.platform == 'darwin': # http://wiki.tcl.tk/13428 parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility') diff --git a/l10n.py b/l10n.py index b77c07de..9db28022 100755 --- a/l10n.py +++ b/l10n.py @@ -12,7 +12,6 @@ import warnings from collections import OrderedDict from contextlib import suppress from os.path import basename, dirname, isdir, isfile, join -from sys import platform from typing import TYPE_CHECKING, Dict, Iterable, Optional, Set, TextIO, Union, cast if TYPE_CHECKING: @@ -37,12 +36,12 @@ LANGUAGE_ID = '!Language' LOCALISATION_DIR = 'L10n' -if platform == 'darwin': +if sys.platform == 'darwin': from Foundation import ( # type: ignore # exists on Darwin NSLocale, NSNumberFormatter, NSNumberFormatterDecimalStyle ) -elif platform == 'win32': +elif sys.platform == 'win32': import ctypes from ctypes.wintypes import BOOL, DWORD, LPCVOID, LPCWSTR, LPWSTR if TYPE_CHECKING: @@ -176,7 +175,7 @@ class _Translations: def available(self) -> Set[str]: """Return a list of available language codes.""" path = self.respath() - if getattr(sys, 'frozen', False) and platform == 'darwin': + if getattr(sys, 'frozen', False) and sys.platform == 'darwin': available = { x[:-len('.lproj')] for x in os.listdir(path) if x.endswith('.lproj') and isfile(join(x, 'Localizable.strings')) @@ -204,7 +203,7 @@ class _Translations: def respath(self) -> pathlib.Path: """Path to localisation files.""" if getattr(sys, 'frozen', False): - if platform == 'darwin': + if sys.platform == 'darwin': return (pathlib.Path(sys.executable).parents[0] / os.pardir / 'Resources').resolve() return pathlib.Path(dirname(sys.executable)) / LOCALISATION_DIR @@ -233,7 +232,7 @@ class _Translations: except OSError: logger.exception(f'could not open {f}') - elif getattr(sys, 'frozen', False) and platform == 'darwin': + elif getattr(sys, 'frozen', False) and sys.platform == 'darwin': return (self.respath() / f'{lang}.lproj' / 'Localizable.strings').open('r', encoding='utf-16') return (self.respath() / f'{lang}.strings').open('r', encoding='utf-8') @@ -243,7 +242,7 @@ class _Locale: """Locale holds a few utility methods to convert data to and from localized versions.""" def __init__(self) -> None: - if platform == 'darwin': + if sys.platform == 'darwin': self.int_formatter = NSNumberFormatter.alloc().init() self.int_formatter.setNumberStyle_(NSNumberFormatterDecimalStyle) self.float_formatter = NSNumberFormatter.alloc().init() @@ -276,7 +275,7 @@ class _Locale: if decimals == 0 and not isinstance(number, numbers.Integral): number = int(round(number)) - if platform == 'darwin': + if sys.platform == 'darwin': if not decimals and isinstance(number, numbers.Integral): return self.int_formatter.stringFromNumber_(number) @@ -298,7 +297,7 @@ class _Locale: :param string: The string to convert :return: None if the string cannot be parsed, otherwise an int or float dependant on input data. """ - if platform == 'darwin': + if sys.platform == 'darwin': return self.float_formatter.numberFromString_(string) with suppress(ValueError): @@ -321,10 +320,10 @@ class _Locale: :return: The preferred language list """ languages: Iterable[str] - if platform == 'darwin': + if sys.platform == 'darwin': languages = NSLocale.preferredLanguages() - elif platform != 'win32': + elif sys.platform != 'win32': # POSIX lang = locale.getlocale()[0] languages = lang and [lang.replace('_', '-')] or [] diff --git a/myNotebook.py b/myNotebook.py index 19f2aaa6..85566a21 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -4,22 +4,21 @@ # - OSX: page background should be a darker gray than systemWindowBody # selected tab foreground should be White when the window is active # - -from sys import platform - +import sys import tkinter as tk from tkinter import ttk # Entire file may be imported by plugins # Can't do this with styles on OSX - http://www.tkdocs.com/tutorial/styles.html#whydifficult -if platform == 'darwin': +if sys.platform == 'darwin': from platform import mac_ver PAGEFG = 'systemButtonText' PAGEBG = 'systemButtonActiveDarkShadow' -elif platform == 'win32': + +elif sys.platform == 'win32': PAGEFG = 'SystemWindowText' - PAGEBG = 'SystemWindow' # typically white + PAGEBG = 'SystemWindow' # typically white class Notebook(ttk.Notebook): @@ -29,14 +28,14 @@ class Notebook(ttk.Notebook): ttk.Notebook.__init__(self, master, **kw) style = ttk.Style() - if platform=='darwin': - if list(map(int, mac_ver()[0].split('.'))) >= [10,10]: + if sys.platform == 'darwin': + if list(map(int, mac_ver()[0].split('.'))) >= [10, 10]: # Hack for tab appearance with 8.5 on Yosemite & El Capitan. For proper fix see # https://github.com/tcltk/tk/commit/55c4dfca9353bbd69bbcec5d63bf1c8dfb461e25 - style.configure('TNotebook.Tab', padding=(12,10,12,2)) + style.configure('TNotebook.Tab', padding=(12, 10, 12, 2)) style.map('TNotebook.Tab', foreground=[('selected', '!background', 'systemWhite')]) - self.grid(sticky=tk.NSEW) # Already padded apropriately - elif platform == 'win32': + self.grid(sticky=tk.NSEW) # Already padded apropriately + elif sys.platform == 'win32': style.configure('nb.TFrame', background=PAGEBG) style.configure('nb.TButton', background=PAGEBG) style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG) @@ -47,56 +46,60 @@ class Notebook(ttk.Notebook): self.grid(padx=10, pady=10, sticky=tk.NSEW) -class Frame(platform == 'darwin' and tk.Frame or ttk.Frame): +class Frame(sys.platform == 'darwin' and tk.Frame or ttk.Frame): def __init__(self, master=None, **kw): - if platform == 'darwin': + if sys.platform == 'darwin': kw['background'] = kw.pop('background', PAGEBG) tk.Frame.__init__(self, master, **kw) tk.Frame(self).grid(pady=5) - elif platform == 'win32': + elif sys.platform == 'win32': ttk.Frame.__init__(self, master, style='nb.TFrame', **kw) - ttk.Frame(self).grid(pady=5) # top spacer + ttk.Frame(self).grid(pady=5) # top spacer else: ttk.Frame.__init__(self, master, **kw) - ttk.Frame(self).grid(pady=5) # top spacer - self.configure(takefocus = 1) # let the frame take focus so that no particular child is focused + ttk.Frame(self).grid(pady=5) # top spacer + self.configure(takefocus=1) # let the frame take focus so that no particular child is focused + class Label(tk.Label): def __init__(self, master=None, **kw): - if platform in ['darwin', 'win32']: + if sys.platform in ['darwin', 'win32']: kw['foreground'] = kw.pop('foreground', PAGEFG) kw['background'] = kw.pop('background', PAGEBG) else: kw['foreground'] = kw.pop('foreground', ttk.Style().lookup('TLabel', 'foreground')) kw['background'] = kw.pop('background', ttk.Style().lookup('TLabel', 'background')) - tk.Label.__init__(self, master, **kw) # Just use tk.Label on all platforms + tk.Label.__init__(self, master, **kw) # Just use tk.Label on all platforms -class Entry(platform == 'darwin' and tk.Entry or ttk.Entry): + +class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry): def __init__(self, master=None, **kw): - if platform == 'darwin': + if sys.platform == 'darwin': kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG) tk.Entry.__init__(self, master, **kw) else: ttk.Entry.__init__(self, master, **kw) -class Button(platform == 'darwin' and tk.Button or ttk.Button): + +class Button(sys.platform == 'darwin' and tk.Button or ttk.Button): def __init__(self, master=None, **kw): - if platform == 'darwin': + if sys.platform == 'darwin': kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG) tk.Button.__init__(self, master, **kw) - elif platform == 'win32': + elif sys.platform == 'win32': ttk.Button.__init__(self, master, style='nb.TButton', **kw) else: ttk.Button.__init__(self, master, **kw) -class ColoredButton(platform == 'darwin' and tk.Label or tk.Button): + +class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button): def __init__(self, master=None, **kw): - if platform == 'darwin': + if sys.platform == 'darwin': # Can't set Button background on OSX, so use a Label instead kw['relief'] = kw.pop('relief', tk.RAISED) self._command = kw.pop('command', None) @@ -105,52 +108,55 @@ class ColoredButton(platform == 'darwin' and tk.Label or tk.Button): else: tk.Button.__init__(self, master, **kw) - if platform == 'darwin': + if sys.platform == 'darwin': def _press(self, event): self._command() -class Checkbutton(platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton): + +class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton): def __init__(self, master=None, **kw): - if platform == 'darwin': + if sys.platform == 'darwin': kw['foreground'] = kw.pop('foreground', PAGEFG) kw['background'] = kw.pop('background', PAGEBG) tk.Checkbutton.__init__(self, master, **kw) - elif platform == 'win32': + elif sys.platform == 'win32': ttk.Checkbutton.__init__(self, master, style='nb.TCheckbutton', **kw) else: ttk.Checkbutton.__init__(self, master, **kw) -class Radiobutton(platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton): + +class Radiobutton(sys.platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton): def __init__(self, master=None, **kw): - if platform == 'darwin': + if sys.platform == 'darwin': kw['foreground'] = kw.pop('foreground', PAGEFG) kw['background'] = kw.pop('background', PAGEBG) tk.Radiobutton.__init__(self, master, **kw) - elif platform == 'win32': + elif sys.platform == 'win32': ttk.Radiobutton.__init__(self, master, style='nb.TRadiobutton', **kw) else: ttk.Radiobutton.__init__(self, master, **kw) -class OptionMenu(platform == 'darwin' and tk.OptionMenu or ttk.OptionMenu): + +class OptionMenu(sys.platform == 'darwin' and tk.OptionMenu or ttk.OptionMenu): def __init__(self, master, variable, default=None, *values, **kw): - if platform == 'darwin': + if sys.platform == 'darwin': variable.set(default) bg = kw.pop('background', PAGEBG) tk.OptionMenu.__init__(self, master, variable, *values, **kw) self['background'] = bg - elif platform == 'win32': + elif sys.platform == 'win32': # OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw) - self['menu'].configure(background = PAGEBG) + self['menu'].configure(background=PAGEBG) # Workaround for https://bugs.python.org/issue25684 for i in range(0, self['menu'].index('end')+1): self['menu'].entryconfig(i, variable=variable) else: ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw) - self['menu'].configure(background = ttk.Style().lookup('TMenu', 'background')) + self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background')) # Workaround for https://bugs.python.org/issue25684 for i in range(0, self['menu'].index('end')+1): self['menu'].entryconfig(i, variable=variable) diff --git a/prefs.py b/prefs.py index d6728ab4..03b8dcd8 100644 --- a/prefs.py +++ b/prefs.py @@ -3,10 +3,10 @@ import contextlib import logging +import sys import tkinter as tk import webbrowser from os.path import expanduser, expandvars, join, normpath -from sys import platform from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812 from tkinter import ttk from types import TracebackType @@ -154,7 +154,7 @@ class AutoInc(contextlib.AbstractContextManager): return None -if platform == 'darwin': +if sys.platform == 'darwin': import objc # type: ignore from Foundation import NSFileManager # type: ignore try: @@ -179,7 +179,7 @@ if platform == 'darwin': was_accessible_at_launch = AXIsProcessTrusted() # type: ignore -elif platform == 'win32': +elif sys.platform == 'win32': import ctypes import winreg from ctypes.wintypes import HINSTANCE, HWND, LPARAM, LPCWSTR, LPVOID, LPWSTR, MAX_PATH, POINT, RECT, SIZE, UINT @@ -246,7 +246,7 @@ class PreferencesDialog(tk.Toplevel): self.parent = parent self.callback = callback - if platform == 'darwin': + if sys.platform == 'darwin': # LANG: File > Preferences menu entry for macOS self.title(_('Preferences')) @@ -258,15 +258,15 @@ class PreferencesDialog(tk.Toplevel): self.transient(parent) # position over parent - if platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 + if sys.platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 # TODO this is fixed supposedly. self.geometry(f'+{parent.winfo_rootx()}+{parent.winfo_rooty()}') # remove decoration - if platform == 'win32': + if sys.platform == 'win32': self.attributes('-toolwindow', tk.TRUE) - elif platform == 'darwin': + elif sys.platform == 'darwin': # http://wiki.tcl.tk/13428 parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility') @@ -294,7 +294,7 @@ class PreferencesDialog(tk.Toplevel): self.__setup_appearance_tab(notebook) self.__setup_plugin_tab(notebook) - if platform == 'darwin': + if sys.platform == 'darwin': self.protocol("WM_DELETE_WINDOW", self.apply) # close button applies changes else: @@ -322,7 +322,7 @@ class PreferencesDialog(tk.Toplevel): self.grab_set() # Ensure fully on-screen - if platform == 'win32' and CalculatePopupWindowPosition: + if sys.platform == 'win32' and CalculatePopupWindowPosition: position = RECT() GetWindowRect(GetParent(self.winfo_id()), position) if CalculatePopupWindowPosition( @@ -396,7 +396,7 @@ class PreferencesDialog(tk.Toplevel): self.outdir_entry = nb.Entry(output_frame, takefocus=False) self.outdir_entry.grid(columnspan=2, padx=self.PADX, pady=(0, self.PADY), sticky=tk.EW, row=row.get()) - if platform == 'darwin': + if sys.platform == 'darwin': text = (_('Change...')) # LANG: macOS Preferences - files location selection button else: @@ -446,7 +446,7 @@ class PreferencesDialog(tk.Toplevel): self.logdir_entry.grid(columnspan=4, padx=self.PADX, pady=(0, self.PADY), sticky=tk.EW, row=row.get()) - if platform == 'darwin': + if sys.platform == 'darwin': text = (_('Change...')) # LANG: macOS Preferences - files location selection button else: @@ -470,7 +470,7 @@ class PreferencesDialog(tk.Toplevel): state=tk.NORMAL if config.get_str('journaldir') else tk.DISABLED ).grid(column=2, pady=self.PADY, sticky=tk.EW, row=row.get()) - if platform in ('darwin', 'win32'): + if sys.platform in ('darwin', 'win32'): ttk.Separator(config_frame, orient=tk.HORIZONTAL).grid( columnspan=4, padx=self.PADX, pady=self.PADY*4, sticky=tk.EW, row=row.get() ) @@ -482,11 +482,11 @@ class PreferencesDialog(tk.Toplevel): nb.Label( config_frame, text=_('Keyboard shortcut') if # LANG: Hotkey/Shortcut settings prompt on OSX - platform == 'darwin' else + sys.platform == 'darwin' else _('Hotkey') # LANG: Hotkey/Shortcut settings prompt on Windows ).grid(padx=self.PADX, sticky=tk.W, row=row.get()) - if platform == 'darwin' and not was_accessible_at_launch: + if sys.platform == 'darwin' and not was_accessible_at_launch: if AXIsProcessTrusted(): # Shortcut settings prompt on OSX nb.Label( @@ -511,7 +511,8 @@ class PreferencesDialog(tk.Toplevel): ) else: - self.hotkey_text = nb.Entry(config_frame, width=(20 if platform == 'darwin' else 30), justify=tk.CENTER) + self.hotkey_text = nb.Entry(config_frame, width=( + 20 if sys.platform == 'darwin' else 30), justify=tk.CENTER) self.hotkey_text.insert( 0, # No hotkey/shortcut currently defined @@ -741,7 +742,7 @@ class PreferencesDialog(tk.Toplevel): appearance_frame, text=_('Dark'), variable=self.theme, value=1, command=self.themevarchanged ).grid(columnspan=3, padx=self.BUTTONX, sticky=tk.W, row=row.get()) - if platform == 'win32': + if sys.platform == 'win32': nb.Radiobutton( appearance_frame, # LANG: Label for 'Transparent' theme radio button @@ -870,7 +871,7 @@ class PreferencesDialog(tk.Toplevel): ) self.ontop_button.grid(columnspan=3, padx=self.BUTTONX, sticky=tk.W, row=row.get()) # Appearance setting - if platform == 'win32': + if sys.platform == 'win32': nb.Checkbutton( appearance_frame, # LANG: Appearance option for Windows "minimize to system tray" @@ -997,7 +998,7 @@ class PreferencesDialog(tk.Toplevel): def tabchanged(self, event: tk.Event) -> None: """Handle preferences active tab changing.""" self.outvarchanged() - if platform == 'darwin': + if sys.platform == 'darwin': # Hack to recompute size so that buttons show up under Mojave notebook = event.widget frame = self.nametowidget(notebook.winfo_parent()) @@ -1027,9 +1028,8 @@ class PreferencesDialog(tk.Toplevel): # If encoding isn't UTF-8 we can't use the tkinter dialog current_locale = locale.getlocale(locale.LC_CTYPE) - from sys import platform as sys_platform directory = None - if sys_platform == 'win32' and current_locale[1] not in ('utf8', 'UTF8', 'utf-8', 'UTF-8'): + if sys.platform == 'win32' and current_locale[1] not in ('utf8', 'UTF8', 'utf-8', 'UTF-8'): def browsecallback(hwnd, uMsg, lParam, lpData): # noqa: N803 # Windows API convention # set initial folder if uMsg == BFFM_INITIALIZED and lpData: @@ -1075,7 +1075,7 @@ class PreferencesDialog(tk.Toplevel): # TODO: This is awful. entryfield['state'] = tk.NORMAL # must be writable to update entryfield.delete(0, tk.END) - if platform == 'win32': + if sys.platform == 'win32': start = len(config.home.split('\\')) if pathvar.get().lower().startswith(config.home.lower()) else 0 display = [] components = normpath(pathvar.get()).split('\\') @@ -1096,7 +1096,7 @@ class PreferencesDialog(tk.Toplevel): entryfield.insert(0, '\\'.join(display)) # None if path doesn't exist - elif platform == 'darwin' and NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get()): + elif sys.platform == 'darwin' and NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get()): if pathvar.get().startswith(config.home): display = ['~'] + NSFileManager.defaultManager().componentsToDisplayForPath_(pathvar.get())[ len(NSFileManager.defaultManager().componentsToDisplayForPath_(config.home)): @@ -1236,7 +1236,7 @@ class PreferencesDialog(tk.Toplevel): else: config.set('journaldir', logdir) - if platform in ('darwin', 'win32'): + if sys.platform in ('darwin', 'win32'): config.set('hotkey_code', self.hotkey_code) config.set('hotkey_mods', self.hotkey_mods) config.set('hotkey_always', int(not self.hotkey_only.get())) @@ -1282,7 +1282,7 @@ class PreferencesDialog(tk.Toplevel): self.parent.wm_attributes('-topmost', 1 if config.get_int('always_ontop') else 0) self.destroy() - if platform == 'darwin': + if sys.platform == 'darwin': def enableshortcuts(self) -> None: """Set up macOS preferences shortcut.""" self.apply() diff --git a/stats.py b/stats.py index ef993a53..6886eb23 100644 --- a/stats.py +++ b/stats.py @@ -1,9 +1,9 @@ """CMDR Status information.""" import csv import json +import sys import tkinter import tkinter as tk -from sys import platform from tkinter import ttk from typing import TYPE_CHECKING, Any, AnyStr, Callable, Dict, List, NamedTuple, Optional, Sequence, cast @@ -20,11 +20,9 @@ logger = EDMCLogging.get_main_logger() if TYPE_CHECKING: def _(x: str) -> str: ... -if platform == 'win32': +if sys.platform == 'win32': import ctypes from ctypes.wintypes import HWND, POINT, RECT, SIZE, UINT - if TYPE_CHECKING: - import ctypes.windll # type: ignore # Fake this into existing, its really a magic dll thing try: CalculatePopupWindowPosition = ctypes.windll.user32.CalculatePopupWindowPosition @@ -372,15 +370,15 @@ class StatsResults(tk.Toplevel): self.transient(parent) # position over parent - if platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 + if sys.platform != 'darwin' or parent.winfo_rooty() > 0: # http://core.tcl.tk/tk/tktview/c84f660833546b1b84e7 self.geometry(f"+{parent.winfo_rootx()}+{parent.winfo_rooty()}") # remove decoration self.resizable(tk.FALSE, tk.FALSE) - if platform == 'win32': + if sys.platform == 'win32': self.attributes('-toolwindow', tk.TRUE) - elif platform == 'darwin': + elif sys.platform == 'darwin': # http://wiki.tcl.tk/13428 parent.call('tk::unsupported::MacWindowStyle', 'style', self, 'utility') @@ -421,7 +419,7 @@ class StatsResults(tk.Toplevel): ttk.Frame(page).grid(pady=5) # bottom spacer notebook.add(page, text=_('Ships')) # LANG: Status dialog title - if platform != 'darwin': + if sys.platform != 'darwin': buttonframe = ttk.Frame(frame) buttonframe.grid(padx=10, pady=(0, 10), sticky=tk.NSEW) # type: ignore # the tuple is supported buttonframe.columnconfigure(0, weight=1) @@ -433,7 +431,7 @@ class StatsResults(tk.Toplevel): self.grab_set() # Ensure fully on-screen - if platform == 'win32' and CalculatePopupWindowPosition: + if sys.platform == 'win32' and CalculatePopupWindowPosition: position = RECT() GetWindowRect(GetParent(self.winfo_id()), position) if CalculatePopupWindowPosition( diff --git a/tests/config.py/_old_config.py b/tests/config.py/_old_config.py index b1609975..5983e050 100644 --- a/tests/config.py/_old_config.py +++ b/tests/config.py/_old_config.py @@ -4,7 +4,6 @@ import warnings from configparser import NoOptionError from os import getenv, makedirs, mkdir, pardir from os.path import dirname, expanduser, isdir, join, normpath -from sys import platform from typing import TYPE_CHECKING, Optional, Union from config import applongname, appname, update_interval @@ -12,13 +11,13 @@ from EDMCLogging import get_main_logger logger = get_main_logger() -if platform == 'darwin': +if sys.platform == 'darwin': from Foundation import ( # type: ignore NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, NSUserDefaults, NSUserDomainMask ) -elif platform == 'win32': +elif sys.platform == 'win32': import ctypes import uuid from ctypes.wintypes import DWORD, HANDLE, HKEY, LONG, LPCVOID, LPCWSTR @@ -90,7 +89,7 @@ elif platform == 'win32': CoTaskMemFree(buf) # and free original return retval -elif platform == 'linux': +elif sys.platform == 'linux': import codecs from configparser import RawConfigParser @@ -114,7 +113,7 @@ class OldConfig(): OUT_SYS_EDDN = 2048 OUT_SYS_DELAY = 4096 - if platform == 'darwin': # noqa: C901 # It's gating *all* the functions + if sys.platform == 'darwin': # noqa: C901 # It's gating *all* the functions def __init__(self): self.app_dir = join( @@ -199,7 +198,7 @@ class OldConfig(): self.save() self.defaults = None - elif platform == 'win32': + elif sys.platform == 'win32': def __init__(self): self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname) # type: ignore # Not going to change @@ -362,7 +361,7 @@ class OldConfig(): RegCloseKey(self.hkey) self.hkey = None - elif platform == 'linux': + elif sys.platform == 'linux': SECTION = 'config' def __init__(self): diff --git a/theme.py b/theme.py index cac65ba7..8515bfbb 100644 --- a/theme.py +++ b/theme.py @@ -6,9 +6,9 @@ # import os +import sys import tkinter as tk from os.path import join -from sys import platform from tkinter import font as tkFont from tkinter import ttk @@ -18,20 +18,20 @@ from ttkHyperlinkLabel import HyperlinkLabel if __debug__: from traceback import print_exc -if platform == "linux": +if sys.platform == "linux": from ctypes import POINTER, Structure, byref, c_char_p, c_int, c_long, c_uint, c_ulong, c_void_p, cdll -if platform == 'win32': +if sys.platform == 'win32': import ctypes from ctypes.wintypes import DWORD, LPCVOID, LPCWSTR AddFontResourceEx = ctypes.windll.gdi32.AddFontResourceExW AddFontResourceEx.restypes = [LPCWSTR, DWORD, LPCVOID] - FR_PRIVATE = 0x10 + FR_PRIVATE = 0x10 FR_NOT_ENUM = 0x20 AddFontResourceEx(join(config.respath, u'EUROCAPS.TTF'), FR_PRIVATE, 0) -elif platform == 'linux': +elif sys.platform == 'linux': # pyright: reportUnboundVariable=false XID = c_ulong # from X.h: typedef unsigned long XID Window = XID @@ -40,7 +40,7 @@ elif platform == 'linux': PropModeReplace = 0 PropModePrepend = 1 - PropModeAppend = 2 + PropModeAppend = 2 # From xprops.h MWM_HINTS_FUNCTIONS = 1 << 0 @@ -69,16 +69,17 @@ elif platform == 'linux': ('input_mode', c_long), ('status', c_ulong), ] - + # workaround for https://github.com/EDCD/EDMarketConnector/issues/568 - if not os.getenv("EDMC_NO_UI") : + if not os.getenv("EDMC_NO_UI"): try: xlib = cdll.LoadLibrary('libX11.so.6') XInternAtom = xlib.XInternAtom XInternAtom.argtypes = [POINTER(Display), c_char_p, c_int] XInternAtom.restype = Atom XChangeProperty = xlib.XChangeProperty - XChangeProperty.argtypes = [POINTER(Display), Window, Atom, Atom, c_int, c_int, POINTER(MotifWmHints), c_int] + XChangeProperty.argtypes = [POINTER(Display), Window, Atom, Atom, c_int, + c_int, POINTER(MotifWmHints), c_int] XChangeProperty.restype = c_int XFlush = xlib.XFlush XFlush.argtypes = [POINTER(Display)] @@ -87,29 +88,31 @@ elif platform == 'linux': XOpenDisplay.argtypes = [c_char_p] XOpenDisplay.restype = POINTER(Display) XQueryTree = xlib.XQueryTree - XQueryTree.argtypes = [POINTER(Display), Window, POINTER(Window), POINTER(Window), POINTER(Window), POINTER(c_uint)] + XQueryTree.argtypes = [POINTER(Display), Window, POINTER( + Window), POINTER(Window), POINTER(Window), POINTER(c_uint)] XQueryTree.restype = c_int dpy = xlib.XOpenDisplay(None) if not dpy: raise Exception("Can't find your display, can't continue") - + motif_wm_hints_property = XInternAtom(dpy, b'_MOTIF_WM_HINTS', False) motif_wm_hints_normal = MotifWmHints(MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS, - MWM_FUNC_RESIZE | MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE | MWM_FUNC_CLOSE, - MWM_DECOR_BORDER | MWM_DECOR_RESIZEH | MWM_DECOR_TITLE | MWM_DECOR_MENU | MWM_DECOR_MINIMIZE, - 0, 0) - motif_wm_hints_dark = MotifWmHints(MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS, - MWM_FUNC_RESIZE | MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE | MWM_FUNC_CLOSE, - 0, 0, 0) + MWM_FUNC_RESIZE | MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE | MWM_FUNC_CLOSE, + MWM_DECOR_BORDER | MWM_DECOR_RESIZEH | MWM_DECOR_TITLE | MWM_DECOR_MENU | MWM_DECOR_MINIMIZE, + 0, 0) + motif_wm_hints_dark = MotifWmHints(MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS, + MWM_FUNC_RESIZE | MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE | MWM_FUNC_CLOSE, + 0, 0, 0) except: - if __debug__: print_exc() + if __debug__: + print_exc() dpy = None class _Theme(object): def __init__(self): - self.active = None # Starts out with no theme + self.active = None # Starts out with no theme self.minwidth = None self.widgets = {} self.widgets_pair = [] @@ -124,18 +127,18 @@ class _Theme(object): if not self.defaults: # Can't initialise this til window is created # Windows, MacOS self.defaults = { - 'fg' : tk.Label()['foreground'], # SystemButtonText, systemButtonText - 'bg' : tk.Label()['background'], # SystemButtonFace, White - 'font' : tk.Label()['font'], # TkDefaultFont - 'bitmapfg' : tk.BitmapImage()['foreground'], # '-foreground {} {} #000000 #000000' - 'bitmapbg' : tk.BitmapImage()['background'], # '-background {} {} {} {}' - 'entryfg' : tk.Entry()['foreground'], # SystemWindowText, Black - 'entrybg' : tk.Entry()['background'], # SystemWindow, systemWindowBody - 'entryfont' : tk.Entry()['font'], # TkTextFont - 'frame' : tk.Frame()['background'], # SystemButtonFace, systemWindowBody - 'menufg' : tk.Menu()['foreground'], # SystemMenuText, - 'menubg' : tk.Menu()['background'], # SystemMenu, - 'menufont' : tk.Menu()['font'], # TkTextFont + 'fg': tk.Label()['foreground'], # SystemButtonText, systemButtonText + 'bg': tk.Label()['background'], # SystemButtonFace, White + 'font': tk.Label()['font'], # TkDefaultFont + 'bitmapfg': tk.BitmapImage()['foreground'], # '-foreground {} {} #000000 #000000' + 'bitmapbg': tk.BitmapImage()['background'], # '-background {} {} {} {}' + 'entryfg': tk.Entry()['foreground'], # SystemWindowText, Black + 'entrybg': tk.Entry()['background'], # SystemWindow, systemWindowBody + 'entryfont': tk.Entry()['font'], # TkTextFont + 'frame': tk.Frame()['background'], # SystemButtonFace, systemWindowBody + 'menufg': tk.Menu()['foreground'], # SystemMenuText, + 'menubg': tk.Menu()['background'], # SystemMenu, + 'menufont': tk.Menu()['font'], # TkTextFont } if widget not in self.widgets: @@ -189,26 +192,27 @@ class _Theme(object): def _enter(self, event, image): widget = event.widget if widget and widget['state'] != tk.DISABLED: - widget.configure(state = tk.ACTIVE) + widget.configure(state=tk.ACTIVE) if image: - image.configure(foreground = self.current['activeforeground'], background = self.current['activebackground']) + image.configure(foreground=self.current['activeforeground'], + background=self.current['activebackground']) def _leave(self, event, image): widget = event.widget if widget and widget['state'] != tk.DISABLED: - widget.configure(state = tk.NORMAL) + widget.configure(state=tk.NORMAL) if image: - image.configure(foreground = self.current['foreground'], background = self.current['background']) + image.configure(foreground=self.current['foreground'], background=self.current['background']) # Set up colors def _colors(self, root, theme): style = ttk.Style() - if platform == 'linux': + if sys.platform == 'linux': style.theme_use('clam') # Default dark theme colors if not config.get_str('dark_text'): - config.set('dark_text', '#ff8000') # "Tangerine" in OSX color picker + config.set('dark_text', '#ff8000') # "Tangerine" in OSX color picker if not config.get_str('dark_highlight'): config.set('dark_highlight', 'white') @@ -216,40 +220,40 @@ class _Theme(object): # Dark (r, g, b) = root.winfo_rgb(config.get_str('dark_text')) self.current = { - 'background' : 'grey4', # OSX inactive dark titlebar color - 'foreground' : config.get_str('dark_text'), - 'activebackground' : config.get_str('dark_text'), - 'activeforeground' : 'grey4', - 'disabledforeground' : '#%02x%02x%02x' % (int(r/384), int(g/384), int(b/384)), - 'highlight' : config.get_str('dark_highlight'), + 'background': 'grey4', # OSX inactive dark titlebar color + 'foreground': config.get_str('dark_text'), + 'activebackground': config.get_str('dark_text'), + 'activeforeground': 'grey4', + 'disabledforeground': '#%02x%02x%02x' % (int(r/384), int(g/384), int(b/384)), + 'highlight': config.get_str('dark_highlight'), # Font only supports Latin 1 / Supplement / Extended, and a few General Punctuation and Mathematical Operators # LANG: Label for commander name in main window - 'font' : (theme > 1 and not 0x250 < ord(_('Cmdr')[0]) < 0x3000 and - tkFont.Font(family='Euro Caps', size=10, weight=tkFont.NORMAL) or - 'TkDefaultFont'), + 'font': (theme > 1 and not 0x250 < ord(_('Cmdr')[0]) < 0x3000 and + tkFont.Font(family='Euro Caps', size=10, weight=tkFont.NORMAL) or + 'TkDefaultFont'), } else: # (Mostly) system colors style = ttk.Style() self.current = { - 'background' : (platform == 'darwin' and 'systemMovableModalBackground' or - style.lookup('TLabel', 'background')), - 'foreground' : style.lookup('TLabel', 'foreground'), - 'activebackground' : (platform == 'win32' and 'SystemHighlight' or - style.lookup('TLabel', 'background', ['active'])), - 'activeforeground' : (platform == 'win32' and 'SystemHighlightText' or - style.lookup('TLabel', 'foreground', ['active'])), - 'disabledforeground' : style.lookup('TLabel', 'foreground', ['disabled']), - 'highlight' : 'blue', - 'font' : 'TkDefaultFont', + 'background': (sys.platform == 'darwin' and 'systemMovableModalBackground' or + style.lookup('TLabel', 'background')), + 'foreground': style.lookup('TLabel', 'foreground'), + 'activebackground': (sys.platform == 'win32' and 'SystemHighlight' or + style.lookup('TLabel', 'background', ['active'])), + 'activeforeground': (sys.platform == 'win32' and 'SystemHighlightText' or + style.lookup('TLabel', 'foreground', ['active'])), + 'disabledforeground': style.lookup('TLabel', 'foreground', ['disabled']), + 'highlight': 'blue', + 'font': 'TkDefaultFont', } - # Apply current theme to a widget and its children, and register it for future updates + def update(self, widget): assert isinstance(widget, tk.Widget) or isinstance(widget, tk.BitmapImage), widget if not self.current: - return # No need to call this for widgets created in plugin_app() + return # No need to call this for widgets created in plugin_app() self.register(widget) self._update_widget(widget) if isinstance(widget, tk.Frame) or isinstance(widget, ttk.Frame): @@ -258,56 +262,57 @@ class _Theme(object): # Apply current theme to a single widget def _update_widget(self, widget): - assert widget in self.widgets, '%s %s "%s"' %(widget.winfo_class(), widget, 'text' in widget.keys() and widget['text']) + assert widget in self.widgets, '%s %s "%s"' % ( + widget.winfo_class(), widget, 'text' in widget.keys() and widget['text']) attribs = self.widgets.get(widget, []) if isinstance(widget, tk.BitmapImage): # not a widget if 'fg' not in attribs: - widget.configure(foreground = self.current['foreground']), + widget.configure(foreground=self.current['foreground']), if 'bg' not in attribs: - widget.configure(background = self.current['background']) + widget.configure(background=self.current['background']) elif 'cursor' in widget.keys() and str(widget['cursor']) not in ['', 'arrow']: # Hack - highlight widgets like HyperlinkLabel with a non-default cursor if 'fg' not in attribs: - widget.configure(foreground = self.current['highlight']), - if 'insertbackground' in widget.keys(): # tk.Entry - widget.configure(insertbackground = self.current['foreground']), + widget.configure(foreground=self.current['highlight']), + if 'insertbackground' in widget.keys(): # tk.Entry + widget.configure(insertbackground=self.current['foreground']), if 'bg' not in attribs: - widget.configure(background = self.current['background']) - if 'highlightbackground' in widget.keys(): # tk.Entry - widget.configure(highlightbackground = self.current['background']) + widget.configure(background=self.current['background']) + if 'highlightbackground' in widget.keys(): # tk.Entry + widget.configure(highlightbackground=self.current['background']) if 'font' not in attribs: - widget.configure(font = self.current['font']) + widget.configure(font=self.current['font']) elif 'activeforeground' in widget.keys(): # e.g. tk.Button, tk.Label, tk.Menu if 'fg' not in attribs: - widget.configure(foreground = self.current['foreground'], - activeforeground = self.current['activeforeground'], - disabledforeground = self.current['disabledforeground']) + widget.configure(foreground=self.current['foreground'], + activeforeground=self.current['activeforeground'], + disabledforeground=self.current['disabledforeground']) if 'bg' not in attribs: - widget.configure(background = self.current['background'], - activebackground = self.current['activebackground']) - if platform == 'darwin' and isinstance(widget, tk.Button): - widget.configure(highlightbackground = self.current['background']) + widget.configure(background=self.current['background'], + activebackground=self.current['activebackground']) + if sys.platform == 'darwin' and isinstance(widget, tk.Button): + widget.configure(highlightbackground=self.current['background']) if 'font' not in attribs: - widget.configure(font = self.current['font']) + widget.configure(font=self.current['font']) elif 'foreground' in widget.keys(): # e.g. ttk.Label if 'fg' not in attribs: - widget.configure(foreground = self.current['foreground']), + widget.configure(foreground=self.current['foreground']), if 'bg' not in attribs: - widget.configure(background = self.current['background']) + widget.configure(background=self.current['background']) if 'font' not in attribs: - widget.configure(font = self.current['font']) + widget.configure(font=self.current['font']) elif 'background' in widget.keys() or isinstance(widget, tk.Canvas): # e.g. Frame, Canvas if 'bg' not in attribs: - widget.configure(background = self.current['background'], - highlightbackground = self.current['disabledforeground']) - + widget.configure(background=self.current['background'], + highlightbackground=self.current['disabledforeground']) # Apply configured theme + def apply(self, root): theme = config.get_int('theme') @@ -316,7 +321,7 @@ class _Theme(object): # Apply colors for widget in set(self.widgets): if isinstance(widget, tk.Widget) and not widget.winfo_exists(): - self.widgets.pop(widget) # has been destroyed + self.widgets.pop(widget) # has been destroyed else: self._update_widget(widget) @@ -334,58 +339,61 @@ class _Theme(object): pair[theme].grid(**gridopts) if self.active == theme: - return # Don't need to mess with the window manager + return # Don't need to mess with the window manager else: self.active = theme - if platform == 'darwin': + if sys.platform == 'darwin': from AppKit import NSAppearance, NSApplication, NSMiniaturizableWindowMask, NSResizableWindowMask - root.update_idletasks() # need main window to be created + root.update_idletasks() # need main window to be created appearance = NSAppearance.appearanceNamed_(theme and 'NSAppearanceNameDarkAqua' or 'NSAppearanceNameAqua') for window in NSApplication.sharedApplication().windows(): - window.setStyleMask_(window.styleMask() & ~(NSMiniaturizableWindowMask | NSResizableWindowMask)) # disable zoom + window.setStyleMask_(window.styleMask() & ~( + NSMiniaturizableWindowMask | NSResizableWindowMask)) # disable zoom window.setAppearance_(appearance) - elif platform == 'win32': + elif sys.platform == 'win32': GWL_STYLE = -16 - WS_MAXIMIZEBOX = 0x00010000 + WS_MAXIMIZEBOX = 0x00010000 # tk8.5.9/win/tkWinWm.c:342 GWL_EXSTYLE = -20 - WS_EX_APPWINDOW = 0x00040000 - WS_EX_LAYERED = 0x00080000 + WS_EX_APPWINDOW = 0x00040000 + WS_EX_LAYERED = 0x00080000 GetWindowLongW = ctypes.windll.user32.GetWindowLongW SetWindowLongW = ctypes.windll.user32.SetWindowLongW root.overrideredirect(theme and 1 or 0) root.attributes("-transparentcolor", theme > 1 and 'grey4' or '') root.withdraw() - root.update_idletasks() # Size and windows styles get recalculated here + root.update_idletasks() # Size and windows styles get recalculated here hwnd = ctypes.windll.user32.GetParent(root.winfo_id()) - SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX) # disable maximize - SetWindowLongW(hwnd, GWL_EXSTYLE, theme > 1 and WS_EX_APPWINDOW|WS_EX_LAYERED or WS_EX_APPWINDOW) # Add to taskbar + SetWindowLongW(hwnd, GWL_STYLE, GetWindowLongW(hwnd, GWL_STYLE) & ~WS_MAXIMIZEBOX) # disable maximize + SetWindowLongW(hwnd, GWL_EXSTYLE, theme > 1 and WS_EX_APPWINDOW | + WS_EX_LAYERED or WS_EX_APPWINDOW) # Add to taskbar root.deiconify() - root.wait_visibility() # need main window to be displayed before returning + root.wait_visibility() # need main window to be displayed before returning else: root.withdraw() - root.update_idletasks() # Size gets recalculated here + root.update_idletasks() # Size gets recalculated here if dpy: xroot = Window() parent = Window() children = Window() nchildren = c_uint() XQueryTree(dpy, root.winfo_id(), byref(xroot), byref(parent), byref(children), byref(nchildren)) - XChangeProperty(dpy, parent, motif_wm_hints_property, motif_wm_hints_property, 32, PropModeReplace, theme and motif_wm_hints_dark or motif_wm_hints_normal, 5) + XChangeProperty(dpy, parent, motif_wm_hints_property, motif_wm_hints_property, 32, + PropModeReplace, theme and motif_wm_hints_dark or motif_wm_hints_normal, 5) XFlush(dpy) else: root.overrideredirect(theme and 1 or 0) root.deiconify() - root.wait_visibility() # need main window to be displayed before returning + root.wait_visibility() # need main window to be displayed before returning if not self.minwidth: - self.minwidth = root.winfo_width() # Minimum width = width on first creation + self.minwidth = root.winfo_width() # Minimum width = width on first creation root.minsize(self.minwidth, -1) diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 2f64ab74..8dd08644 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -1,14 +1,12 @@ +import sys import tkinter as tk import webbrowser -from sys import platform from tkinter import font as tkFont from tkinter import ttk -if platform == 'win32': +if sys.platform == 'win32': import subprocess - from winreg import ( - HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, CloseKey, OpenKeyEx, QueryValueEx - ) + from winreg import HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, CloseKey, OpenKeyEx, QueryValueEx # A clickable ttk Label # @@ -18,19 +16,22 @@ if platform == 'win32': # popup_copy: Whether right-click on non-empty label text pops up a context menu with a 'Copy' option. Defaults to no context menu. If popup_copy is a function it will be called with the current label text and should return a boolean. # # May be imported by plugins -class HyperlinkLabel(platform == 'darwin' and tk.Label or ttk.Label, object): + + +class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label, object): def __init__(self, master=None, **kw): self.url = 'url' in kw and kw.pop('url') or None self.popup_copy = kw.pop('popup_copy', False) - self.underline = kw.pop('underline', None) # override ttk.Label's underline + self.underline = kw.pop('underline', None) # override ttk.Label's underline self.foreground = kw.get('foreground') or 'blue' - self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup('TLabel', 'foreground', ('disabled',))) # ttk.Label doesn't support disabledforeground option + self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup( + 'TLabel', 'foreground', ('disabled',))) # ttk.Label doesn't support disabledforeground option - if platform == 'darwin': + if sys.platform == 'darwin': # Use tk.Label 'cos can't set ttk.Label background - http://www.tkdocs.com/tutorial/styles.html#whydifficult kw['background'] = kw.pop('background', 'systemDialogBackgroundActive') - kw['anchor'] = kw.pop('anchor', tk.W) # like ttk.Label + kw['anchor'] = kw.pop('anchor', tk.W) # like ttk.Label tk.Label.__init__(self, master, **kw) else: ttk.Label.__init__(self, master, **kw) @@ -39,16 +40,16 @@ class HyperlinkLabel(platform == 'darwin' and tk.Label or ttk.Label, object): self.menu = tk.Menu(None, tearoff=tk.FALSE) # LANG: Label for 'Copy' as in 'Copy and Paste' - self.menu.add_command(label=_('Copy'), command = self.copy) # As in Copy and Paste - self.bind(platform == 'darwin' and '' or '', self._contextmenu) + self.menu.add_command(label=_('Copy'), command=self.copy) # As in Copy and Paste + self.bind(sys.platform == 'darwin' and '' or '', self._contextmenu) self.bind('', self._enter) self.bind('', self._leave) # set up initial appearance - self.configure(state = kw.get('state', tk.NORMAL), - text = kw.get('text'), - font = kw.get('font', ttk.Style().lookup('TLabel', 'font'))) + self.configure(state=kw.get('state', tk.NORMAL), + text=kw.get('text'), + font=kw.get('font', ttk.Style().lookup('TLabel', 'font'))) # Change cursor and appearance depending on state and text def configure(self, cnf=None, **kw): @@ -70,17 +71,18 @@ class HyperlinkLabel(platform == 'darwin' and tk.Label or ttk.Label, object): if 'font' in kw: self.font_n = kw['font'] - self.font_u = tkFont.Font(font = self.font_n) - self.font_u.configure(underline = True) + self.font_u = tkFont.Font(font=self.font_n) + self.font_u.configure(underline=True) kw['font'] = self.underline is True and self.font_u or self.font_n if 'cursor' not in kw: if (kw['state'] if 'state' in kw else str(self['state'])) == tk.DISABLED: - kw['cursor'] = 'arrow' # System default + kw['cursor'] = 'arrow' # System default elif self.url and (kw['text'] if 'text' in kw else self['text']): - kw['cursor'] = platform=='darwin' and 'pointinghand' or 'hand2' + kw['cursor'] = sys.platform == 'darwin' and 'pointinghand' or 'hand2' else: - kw['cursor'] = (platform=='darwin' and 'notallowed') or (platform=='win32' and 'no') or 'circle' + kw['cursor'] = (sys.platform == 'darwin' and 'notallowed') or ( + sys.platform == 'win32' and 'no') or 'circle' super(HyperlinkLabel, self).configure(cnf, **kw) @@ -89,22 +91,22 @@ class HyperlinkLabel(platform == 'darwin' and tk.Label or ttk.Label, object): def _enter(self, event): if self.url and self.underline is not False and str(self['state']) != tk.DISABLED: - super(HyperlinkLabel, self).configure(font = self.font_u) + super(HyperlinkLabel, self).configure(font=self.font_u) def _leave(self, event): if not self.underline: - super(HyperlinkLabel, self).configure(font = self.font_n) + super(HyperlinkLabel, self).configure(font=self.font_n) def _click(self, event): if self.url and self['text'] and str(self['state']) != tk.DISABLED: url = self.url(self['text']) if callable(self.url) else self.url if url: - self._leave(event) # Remove underline before we change window to browser + self._leave(event) # Remove underline before we change window to browser openurl(url) def _contextmenu(self, event): if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy): - self.menu.post(platform == 'darwin' and event.x_root + 1 or event.x_root, event.y_root) + self.menu.post(sys.platform == 'darwin' and event.x_root + 1 or event.x_root, event.y_root) def copy(self): self.clipboard_clear() @@ -112,13 +114,14 @@ class HyperlinkLabel(platform == 'darwin' and tk.Label or ttk.Label, object): def openurl(url): - if platform == 'win32': + if sys.platform == 'win32': # On Windows webbrowser.open calls os.startfile which calls ShellExecute which can't handle long arguments, # so discover and launch the browser directly. # https://blogs.msdn.microsoft.com/oldnewthing/20031210-00/?p=41553 try: - hkey = OpenKeyEx(HKEY_CURRENT_USER, r'Software\Microsoft\Windows\Shell\Associations\UrlAssociations\https\UserChoice') + hkey = OpenKeyEx(HKEY_CURRENT_USER, + r'Software\Microsoft\Windows\Shell\Associations\UrlAssociations\https\UserChoice') (value, typ) = QueryValueEx(hkey, 'ProgId') CloseKey(hkey) if value in ['IE.HTTP', 'AppXq0fevzme2pys62n3e0fbqa7peapykr8v']: @@ -128,7 +131,7 @@ def openurl(url): else: cls = value except: - cls = 'https' + cls = 'https' if cls: try: From f160acfe5721ea014abe93fda8c914363b747e57 Mon Sep 17 00:00:00 2001 From: A_D Date: Mon, 24 Jan 2022 14:19:18 +0200 Subject: [PATCH 085/186] add configs for switching platforms --- .mypy.ini | 1 + pyproject.toml | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.mypy.ini b/.mypy.ini index 03840fd9..f9ec5997 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -2,3 +2,4 @@ follow_imports = skip ignore_missing_imports = True scripts_are_modules = True +; platform = darwin diff --git a/pyproject.toml b/pyproject.toml index 35ab26e4..a7f41ad4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,10 +3,13 @@ max_line_length = 120 [tool.isort] multi_line_output = 5 -line_length = 119 +line_length = 119 [tool.pytest.ini_options] testpaths = ["tests"] # Search for tests in tests/ [tool.coverage.run] -omit = ["venv/*"] # when running pytest --cov, dont report coverage in venv directories +omit = ["venv/*"] # when running pytest --cov, dont report coverage in venv directories + +[tool.pyright] +# pythonPlatform = 'Darwin' From 7bcf3a6f4da7e2f6d7115121caaab35a290eaf85 Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 26 Jan 2022 17:46:30 +0200 Subject: [PATCH 086/186] note sys.platform requirements in contributing --- Contributing.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Contributing.md b/Contributing.md index bdbf4634..0b1f4d58 100644 --- a/Contributing.md +++ b/Contributing.md @@ -228,6 +228,7 @@ handy if you want to step through the testing code to be sure of anything. Otherwise, see the [pytest documentation](https://docs.pytest.org/en/stable/contents.html). --- + ## Debugging network sends Rather than risk sending bad data to a remote service, even if only through @@ -484,6 +485,20 @@ Please be verbose here, more info about weird choices is always prefered over ma Additionally, if your hack is over around 5 lines, please include a `# HACK END` or similar comment to indicate the end of the hack. +# Use `sys.platform` for platform guards + +`mypy` (and `pylance`) understand platform guards and will show unreachable code / resolve imports correctly +for platform specific things. However, this only works if you directly reference `sys.platform`, importantly +the following does not work: + +```py +from sys import platform +if platform == 'darwin': + ... +``` + +It **MUST** be `if sys.platform`. + --- ## Build process From 3814f9ebb9eba2f4607f3674ec8a9bf7936188e9 Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 20 Jan 2022 23:20:10 +0200 Subject: [PATCH 087/186] Quick fix to a type warning --- plugins/eddn.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index 37165ea1..f16c571e 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -168,6 +168,10 @@ class EDDN: def flush(self): """Flush the replay file, clearing any data currently there that is not in the replaylog list.""" + if self.replayfile is None: + logger.warning('replayfile is None!') + return + self.replayfile.seek(0, SEEK_SET) self.replayfile.truncate() for line in self.replaylog: From d65bcbbf0aeb5419f4d1da164849054f7797f9dd Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 21 Jan 2022 00:09:39 +0200 Subject: [PATCH 088/186] Added util directory and http util file HTTP utils are anything generally useful for HTTP things, currently thats just compressing a string if its larger than a given number of bytes. These libraries are intended to be available to plugins --- util/http.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 util/http.py diff --git a/util/http.py b/util/http.py new file mode 100644 index 00000000..441ff386 --- /dev/null +++ b/util/http.py @@ -0,0 +1,24 @@ +"""Utilities for dealing with HTTP.""" +from __future__ import annotations + +from gzip import compress + +__all__ = ['gzip'] + + +def gzip(data: str | bytes, max_size: int = 512, encoding='utf-8') -> tuple[bytes, bool]: + """ + Compress the given data if the max size is greater than specified. + + :param data: The data to compress + :param max_size: The max size of data, in bytes, defaults to 512 + :param encoding: The encoding to use if data is a str, defaults to 'utf-8' + :return: the payload to send, and a bool indicating compression state + """ + if isinstance(data, str): + data = data.encode(encoding=encoding) + + if len(data) <= max_size: + return data, False + + return compress(data), True From b780b1ab8e64d3c4a091014a0b3f5baa57e42575 Mon Sep 17 00:00:00 2001 From: A_D Date: Fri, 21 Jan 2022 00:12:51 +0200 Subject: [PATCH 089/186] Compress outgoing EDDN data if its large This is a sidestep solution to #1390. It doesn't attempt to directly resend data, only compressing with gzip over a given size. If that STILL returns a 413, its dropped, as without introspection of the message we cannot make it any smaller --- plugins/eddn.py | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index f16c571e..6275349f 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -10,6 +10,7 @@ from collections import OrderedDict from os import SEEK_SET from os.path import join from platform import system +from textwrap import dedent from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Mapping, MutableMapping, Optional from typing import OrderedDict as OrderedDictT from typing import TextIO, Tuple, Union @@ -27,6 +28,7 @@ from monitor import monitor from myNotebook import Frame from prefs import prefsVersion from ttkHyperlinkLabel import HyperlinkLabel +from util import http if sys.platform != 'win32': from fcntl import LOCK_EX, LOCK_NB, lockf @@ -217,7 +219,12 @@ class EDDN: ('message', msg['message']), ]) - r = self.session.post(self.eddn_url, data=json.dumps(to_send), timeout=self.TIMEOUT) + encoded, compressed = http.gzip(json.dumps(to_send)) + headers: None | dict[str, str] = None + if compressed: + headers = {'Content-Encoding': 'gzip'} + + r = self.session.post(self.eddn_url, data=encoded, timeout=self.TIMEOUT, headers=headers) if r.status_code != requests.codes.ok: # Check if EDDN is still objecting to an empty commodities list @@ -230,15 +237,31 @@ class EDDN: logger.trace_if('plugin.eddn', "EDDN is still objecting to empty commodities data") return # We want to silence warnings otherwise + from base64 import b64encode # we dont need this to be around until this point, which may never hit + if r.status_code == 413: + logger.debug(dedent( + f"""\ + Got a 413 while POSTing data + URL:\t{r.url} + Headers:\t{r.headers} + Sent Data Len:\t {len(encoded)} + Content:\n{r.text}\n + Msg:\n{msg} + Encoded:\n{b64encode(encoded).decode(errors="replace")} + """ + )) + + return # drop the error + if not self.UNKNOWN_SCHEMA_RE.match(r.text): - logger.debug( + logger.debug(dedent( f'''Status from POST wasn't OK: -Status\t{r.status_code} -URL\t{r.url} -Headers\t{r.headers} -Content:\n{r.text} -Msg:\n{msg}''' - ) + Status\t{r.status_code} + URL\t{r.url} + Headers\t{r.headers} + Content:\n{r.text} + Msg:\n{msg}''' + )) r.raise_for_status() From 2b8fe57bc755a4c20643c95a6798b37ec8f192ca Mon Sep 17 00:00:00 2001 From: A_D Date: Mon, 24 Jan 2022 17:21:02 +0200 Subject: [PATCH 090/186] gzip decompress support for debug_webserver --- debug_webserver.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/debug_webserver.py b/debug_webserver.py index 3d1a671b..9b717ec0 100644 --- a/debug_webserver.py +++ b/debug_webserver.py @@ -1,4 +1,5 @@ """Simple HTTP listener to be used with debugging various EDMC sends.""" +import gzip import json import pathlib import tempfile @@ -30,7 +31,15 @@ class LoggingHandler(server.BaseHTTPRequestHandler): def do_POST(self) -> None: # noqa: N802 # I cant change it """Handle POST.""" logger.info(f"Received a POST for {self.path!r}!") - data = self.rfile.read(int(self.headers['Content-Length'])).decode('utf-8', errors='replace') + data_raw: bytes = self.rfile.read(int(self.headers['Content-Length'])) + data: str | bytes + + if self.headers.get('Content-Encoding') == 'gzip': + data = gzip.decompress(data_raw).decode('utf-8', errors='replace') + + else: + data = data_raw.decode('utf-8', errors='replace') + to_save = data target_path = self.path @@ -64,7 +73,7 @@ class LoggingHandler(server.BaseHTTPRequestHandler): target_file = output_data_path / (safe_file_name(target_path) + '.log') if target_file.parent != output_data_path: logger.warning(f"REFUSING TO WRITE FILE THAT ISN'T IN THE RIGHT PLACE! {target_file=}") - logger.warning(f'DATA FOLLOWS\n{data}') + logger.warning(f'DATA FOLLOWS\n{data}') # type: ignore # mypy thinks data is a byte string here return with output_lock, target_file.open('a') as f: From 73f45e37cde8147d722986b1df555e600a78effa Mon Sep 17 00:00:00 2001 From: A_D Date: Mon, 24 Jan 2022 17:21:31 +0200 Subject: [PATCH 091/186] always compress eddn data, comment rationale --- plugins/eddn.py | 11 ++++++++++- util/http.py | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 6275349f..dd8e6cab 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -219,7 +219,16 @@ class EDDN: ('message', msg['message']), ]) - encoded, compressed = http.gzip(json.dumps(to_send)) + # About the smallest request is going to be (newlines added for brevity): + # {"$schemaRef":"https://eddn.edcd.io/schemas/shipyard/2","header":{"softwareName":"E:D Market + # Connector Windows","softwareVersion":"5.3.0-beta4extra","uploaderID":"abcdefghijklm"},"messa + # ge":{"systemName":"delphi","stationName":"The Oracle","marketID":128782803,"timestamp":"xxxx + # -xx-xxTxx:xx:xxZ","ships":[]}} + # + # Which comes to about around 307 bytes. Lets play it safe and make our minimum 0 bytes. + # Which compresses everything + + encoded, compressed = http.gzip(json.dumps(to_send, separators=(',', ':')), max_size=0) headers: None | dict[str, str] = None if compressed: headers = {'Content-Encoding': 'gzip'} diff --git a/util/http.py b/util/http.py index 441ff386..8a8578b9 100644 --- a/util/http.py +++ b/util/http.py @@ -10,6 +10,9 @@ def gzip(data: str | bytes, max_size: int = 512, encoding='utf-8') -> tuple[byte """ Compress the given data if the max size is greater than specified. + The default was chosen somewhat arbitrarily, see eddn.py for some more careful + work towards keeping the data almost always compressed + :param data: The data to compress :param max_size: The max size of data, in bytes, defaults to 512 :param encoding: The encoding to use if data is a str, defaults to 'utf-8' From 5f7234ce890c927df51065c95272d1fd46be37ea Mon Sep 17 00:00:00 2001 From: A_D Date: Mon, 24 Jan 2022 21:17:50 +0200 Subject: [PATCH 092/186] address PR comments --- plugins/eddn.py | 21 ++++++++++----------- util/{http.py => text.py} | 0 2 files changed, 10 insertions(+), 11 deletions(-) rename util/{http.py => text.py} (100%) diff --git a/plugins/eddn.py b/plugins/eddn.py index dd8e6cab..964aa093 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -28,7 +28,7 @@ from monitor import monitor from myNotebook import Frame from prefs import prefsVersion from ttkHyperlinkLabel import HyperlinkLabel -from util import http +from util import text if sys.platform != 'win32': from fcntl import LOCK_EX, LOCK_NB, lockf @@ -171,7 +171,7 @@ class EDDN: def flush(self): """Flush the replay file, clearing any data currently there that is not in the replaylog list.""" if self.replayfile is None: - logger.warning('replayfile is None!') + logger.error('replayfile is None!') return self.replayfile.seek(0, SEEK_SET) @@ -220,15 +220,14 @@ class EDDN: ]) # About the smallest request is going to be (newlines added for brevity): - # {"$schemaRef":"https://eddn.edcd.io/schemas/shipyard/2","header":{"softwareName":"E:D Market - # Connector Windows","softwareVersion":"5.3.0-beta4extra","uploaderID":"abcdefghijklm"},"messa - # ge":{"systemName":"delphi","stationName":"The Oracle","marketID":128782803,"timestamp":"xxxx - # -xx-xxTxx:xx:xxZ","ships":[]}} + # {"$schemaRef":"https://eddn.edcd.io/schemas/commodity/3","header":{"softwareName":"E:D Market + # Connector Windows","softwareVersion":"5.3.0-beta4extra","uploaderID":"abcdefghijklm"},"messag + # e":{"systemName":"delphi","stationName":"The Oracle","marketId":128782803,"timestamp":"2022-0 + # 1-26T12:00:00Z","commodities":[]}} # - # Which comes to about around 307 bytes. Lets play it safe and make our minimum 0 bytes. - # Which compresses everything + # Which comes to 315 bytes (including \n) and compresses to 244 bytes. So lets just compress everything - encoded, compressed = http.gzip(json.dumps(to_send, separators=(',', ':')), max_size=0) + encoded, compressed = text.gzip(json.dumps(to_send, separators=(',', ':')), max_size=0) headers: None | dict[str, str] = None if compressed: headers = {'Content-Encoding': 'gzip'} @@ -249,7 +248,7 @@ class EDDN: from base64 import b64encode # we dont need this to be around until this point, which may never hit if r.status_code == 413: logger.debug(dedent( - f"""\ + f'''\ Got a 413 while POSTing data URL:\t{r.url} Headers:\t{r.headers} @@ -257,7 +256,7 @@ class EDDN: Content:\n{r.text}\n Msg:\n{msg} Encoded:\n{b64encode(encoded).decode(errors="replace")} - """ + ''' )) return # drop the error diff --git a/util/http.py b/util/text.py similarity index 100% rename from util/http.py rename to util/text.py From 45dba2ba880142ae5e6891569a17dce9e5a7ed41 Mon Sep 17 00:00:00 2001 From: A_D Date: Mon, 24 Jan 2022 23:17:51 +0200 Subject: [PATCH 093/186] change module doc comment for text.py --- util/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/text.py b/util/text.py index 8a8578b9..0755f0f3 100644 --- a/util/text.py +++ b/util/text.py @@ -1,4 +1,4 @@ -"""Utilities for dealing with HTTP.""" +"""Utilities for dealing with text (and byte representations thereof).""" from __future__ import annotations from gzip import compress From 10824250e179aaad5bc2c9fc9a3cf6c146c65a24 Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 25 Jan 2022 15:09:30 +0200 Subject: [PATCH 094/186] add deflate support --- debug_webserver.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/debug_webserver.py b/debug_webserver.py index 9b717ec0..1defdc5b 100644 --- a/debug_webserver.py +++ b/debug_webserver.py @@ -4,6 +4,7 @@ import json import pathlib import tempfile import threading +import zlib from http import server from typing import Any, Callable, Tuple, Union from urllib.parse import parse_qs @@ -34,6 +35,16 @@ class LoggingHandler(server.BaseHTTPRequestHandler): data_raw: bytes = self.rfile.read(int(self.headers['Content-Length'])) data: str | bytes + match self.headers.get('Content-Encoding'): + case 'gzip': + data = gzip.decompress(data_raw).decode('utf-8', errors='replace') + + case 'deflate': + zlib.decompress(data_raw).decode('utf-8', errors='replace') + + case _: + data = data_raw.decode('utf-8', errors='replace') + if self.headers.get('Content-Encoding') == 'gzip': data = gzip.decompress(data_raw).decode('utf-8', errors='replace') From ec6f333cfa2bd51b95c6aeac767dee2eeb16ac09 Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 25 Jan 2022 15:18:23 +0200 Subject: [PATCH 095/186] removed old code --- debug_webserver.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/debug_webserver.py b/debug_webserver.py index 1defdc5b..f660ea05 100644 --- a/debug_webserver.py +++ b/debug_webserver.py @@ -45,12 +45,6 @@ class LoggingHandler(server.BaseHTTPRequestHandler): case _: data = data_raw.decode('utf-8', errors='replace') - if self.headers.get('Content-Encoding') == 'gzip': - data = gzip.decompress(data_raw).decode('utf-8', errors='replace') - - else: - data = data_raw.decode('utf-8', errors='replace') - to_save = data target_path = self.path From 1c190dd76fb2ef5b010ff4ffc944f2d6baf74718 Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 25 Jan 2022 17:11:23 +0200 Subject: [PATCH 096/186] drop match statement --- debug_webserver.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/debug_webserver.py b/debug_webserver.py index f660ea05..024f39b9 100644 --- a/debug_webserver.py +++ b/debug_webserver.py @@ -35,15 +35,13 @@ class LoggingHandler(server.BaseHTTPRequestHandler): data_raw: bytes = self.rfile.read(int(self.headers['Content-Length'])) data: str | bytes - match self.headers.get('Content-Encoding'): - case 'gzip': - data = gzip.decompress(data_raw).decode('utf-8', errors='replace') + encoding = self.headers.get('Content-Encoding') - case 'deflate': - zlib.decompress(data_raw).decode('utf-8', errors='replace') + if encoding in ('gzip', 'deflate'): + data = zlib.decompress(data_raw).decode('utf-8', errors='replace') - case _: - data = data_raw.decode('utf-8', errors='replace') + else: + data = data_raw.decode('utf-8', errors='replace') to_save = data From e6ffe7e5208615e79fa2b2ee4c080e2d21d03116 Mon Sep 17 00:00:00 2001 From: A_D Date: Tue, 25 Jan 2022 17:11:35 +0200 Subject: [PATCH 097/186] refactor error logging --- plugins/eddn.py | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 964aa093..74bab79d 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -245,34 +245,40 @@ class EDDN: logger.trace_if('plugin.eddn', "EDDN is still objecting to empty commodities data") return # We want to silence warnings otherwise - from base64 import b64encode # we dont need this to be around until this point, which may never hit if r.status_code == 413: - logger.debug(dedent( - f'''\ - Got a 413 while POSTing data - URL:\t{r.url} - Headers:\t{r.headers} - Sent Data Len:\t {len(encoded)} - Content:\n{r.text}\n - Msg:\n{msg} - Encoded:\n{b64encode(encoded).decode(errors="replace")} - ''' - )) - + self._log_response(r, header_msg='Got a 413 while POSTing data', sent_data_len=str(len(encoded))) return # drop the error if not self.UNKNOWN_SCHEMA_RE.match(r.text): - logger.debug(dedent( - f'''Status from POST wasn't OK: - Status\t{r.status_code} - URL\t{r.url} - Headers\t{r.headers} - Content:\n{r.text} - Msg:\n{msg}''' - )) + self._log_response(r, header_msg='Status from POST wasn\'t 200 (OK)') r.raise_for_status() + def _log_response( + self, + response: requests.Response, + header_msg='Failed to POST to EDDN', + **kwargs + ) -> None: + """ + Log a response object with optional additional data. + + :param response: The response to log + :param header_msg: A header message to add to the log, defaults to 'Failed to POST to EDDN' + :param kwargs: Any other notes to add, will be added below the main data in the same format. + """ + additional_data = "\n".join( + f'''{name.replace('_', ' ').title():<8}:\t{value}''' for name, value in kwargs.items() + ) + + logger.debug(dedent(f''' + {header_msg}: + Status :\t{response.status_code} + URL :\t{response.url} + Headers :\t{response.headers} + Content :\t{response.text} + '''+additional_data)) + def sendreplay(self) -> None: # noqa: CCR001 """Send cached Journal lines to EDDN.""" if not self.replayfile: From f753ce4308d5cb4753152ec48c15ee8bdb3b259c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jan 2022 17:03:23 +0000 Subject: [PATCH 098/186] build(deps-dev): bump coverage[toml] from 6.2 to 6.3 Bumps [coverage[toml]](https://github.com/nedbat/coveragepy) from 6.2 to 6.3. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/6.2...6.3) --- updated-dependencies: - dependency-name: coverage[toml] dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d746f046..a1911c5f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -35,7 +35,7 @@ py2exe==0.11.0.1; sys_platform == 'win32' # Testing pytest==6.2.5 pytest-cov==3.0.0 # Pytest code coverage support -coverage[toml]==6.2 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs +coverage[toml]==6.3 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs # For manipulating folder permissions and the like. pywin32==303; sys_platform == 'win32' From e891ee4da026816951532f59d86c3189795a65c6 Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 26 Jan 2022 19:05:00 +0200 Subject: [PATCH 099/186] log more contextual data if possible on a 413 --- plugins/eddn.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 74bab79d..76e2f227 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -6,6 +6,7 @@ import pathlib import re import sys import tkinter as tk +from base64 import encode from collections import OrderedDict from os import SEEK_SET from os.path import join @@ -246,7 +247,15 @@ class EDDN: return # We want to silence warnings otherwise if r.status_code == 413: - self._log_response(r, header_msg='Got a 413 while POSTing data', sent_data_len=str(len(encoded))) + extra_data = { + 'schema_ref': msg.get('$schemaRef', 'Unset $schemaRef!'), + 'sent_data_len': str(len(encoded)), + } + + if '/journal/' in extra_data['schema_ref']: + extra_data['event'] = msg.get('message', {}).get('event', 'No Event Set') + + self._log_response(r, header_msg='Got a 413 while POSTing data', **extra_data) return # drop the error if not self.UNKNOWN_SCHEMA_RE.match(r.text): From 3a4690d292807d0a35d8988b09f3b77891e6c6ff Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 26 Jan 2022 17:28:45 +0000 Subject: [PATCH 100/186] monitor: Gate navroute on *not* being in journal catch up mode We can't just use `EDLogs.live` here as it'll get set `true` when the `Commander` event is seen *during the catch up*. We need that catchup to have finished before we'll try processing a `NavRoute` event and file. --- monitor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index 42d282b1..40259ece 100644 --- a/monitor.py +++ b/monitor.py @@ -95,6 +95,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below # If 1 or 2 a LoadGame event will happen when the game goes live. # If 3 we need to inject a special 'StartUp' event since consumers won't see the LoadGame event. self.live = False + # And whilst we're parsing *only to catch up on state*, we might not want to fully process some things + self.catching_up = False self.game_was_running = False # For generation of the "ShutDown" event @@ -342,6 +344,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below if platform == 'darwin': fcntl(loghandle, F_GLOBAL_NOCACHE, -1) # required to avoid corruption on macOS over SMB + self.catching_up = True for line in loghandle: try: if b'"event":"Location"' in line: @@ -352,6 +355,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below except Exception as ex: logger.debug(f'Invalid journal entry:\n{line!r}\n', exc_info=ex) + self.catching_up = False log_pos = loghandle.tell() else: @@ -1314,7 +1318,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.state['Credits'] += entry.get('Refund', 0) self.state['Taxi'] = False - elif event_type == 'navroute': + elif event_type == 'navroute' and not self.catching_up: # assume we've failed out the gate, then pull it back if things are fine self._last_navroute_journal_timestamp = mktime(strptime(entry['timestamp'], '%Y-%m-%dT%H:%M:%SZ')) self._navroute_retries_remaining = 11 From 8c2a0ae95afb1c4a54d6cde5b2d6ae8f2fbe5f9b Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 26 Jan 2022 20:36:29 +0200 Subject: [PATCH 101/186] make tests not explode on windows --- config/linux.py | 2 +- tests/config.py/test_config.py | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/config/linux.py b/config/linux.py index 04087b32..58c0f7cd 100644 --- a/config/linux.py +++ b/config/linux.py @@ -3,7 +3,7 @@ import os import pathlib import sys from configparser import ConfigParser -from typing import List, Optional, Union +from typing import TYPE_CHECKING, List, Optional, Union from config import AbstractConfig, appname, logger diff --git a/tests/config.py/test_config.py b/tests/config.py/test_config.py index 0d10a3c2..4f72d437 100644 --- a/tests/config.py/test_config.py +++ b/tests/config.py/test_config.py @@ -1,4 +1,12 @@ -"""Test the config system.""" +""" +Test the config system. + +Note: These tests to arbitrary reads and writes to an existing config, including +key deletions. Said modifications are to keys that are generated internally. + +Most of these tests are parity tests with the "old" config, and likely one day can be +entirely removed. +""" from __future__ import annotations import contextlib @@ -19,7 +27,7 @@ print(sys.path) from _old_config import old_config # noqa: E402 -from config import LinuxConfig, config # noqa: E402 +from config import config # noqa: E402 def _fuzz_list(length: int) -> List[str]: @@ -77,6 +85,11 @@ class TestNewConfig: def __update_linuxconfig(self) -> None: """On linux config uses ConfigParser, which doesn't update from disk changes. Force the update here.""" + if sys.platform != 'linux': + return + + from config.linux import LinuxConfig + if isinstance(config, LinuxConfig) and config.config is not None: config.config.read(config.filename) @@ -163,6 +176,10 @@ class TestOldNewConfig: def __update_linuxconfig(self) -> None: """On linux config uses ConfigParser, which doesn't update from disk changes. Force the update here.""" + if sys.platform != 'linux': + return + + from config.linux import LinuxConfig if isinstance(config, LinuxConfig) and config.config is not None: config.config.read(config.filename) From 6c0cb45e61b9fd1d553bfcc8cab676bdaf1daa19 Mon Sep 17 00:00:00 2001 From: A_D Date: Wed, 26 Jan 2022 22:40:09 +0200 Subject: [PATCH 102/186] removed unused import --- debug_webserver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/debug_webserver.py b/debug_webserver.py index 024f39b9..ccce5f9d 100644 --- a/debug_webserver.py +++ b/debug_webserver.py @@ -1,5 +1,4 @@ """Simple HTTP listener to be used with debugging various EDMC sends.""" -import gzip import json import pathlib import tempfile From d6dcebf545ba13e4d8e25b4369172bcb97b440dc Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 27 Jan 2022 14:59:27 +0200 Subject: [PATCH 103/186] Remove unused import --- plugins/eddn.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 76e2f227..a423876e 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -6,7 +6,6 @@ import pathlib import re import sys import tkinter as tk -from base64 import encode from collections import OrderedDict from os import SEEK_SET from os.path import join From 38f35403c92320254d2d0cab879b0eaa79612c1d Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 27 Jan 2022 16:02:06 +0200 Subject: [PATCH 104/186] fix gzip support in debug_webserver --- debug_webserver.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/debug_webserver.py b/debug_webserver.py index ccce5f9d..0a456dc9 100644 --- a/debug_webserver.py +++ b/debug_webserver.py @@ -1,4 +1,5 @@ """Simple HTTP listener to be used with debugging various EDMC sends.""" +import gzip import json import pathlib import tempfile @@ -36,7 +37,10 @@ class LoggingHandler(server.BaseHTTPRequestHandler): encoding = self.headers.get('Content-Encoding') - if encoding in ('gzip', 'deflate'): + if encoding == 'gzip': + data = gzip.decompress(data_raw).decode('utf-8', errors='replace') + + elif encoding == 'deflate': data = zlib.decompress(data_raw).decode('utf-8', errors='replace') else: From 17c886af84f7f1c03779ade0c5da989cf3c1d424 Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 27 Jan 2022 16:03:03 +0200 Subject: [PATCH 105/186] fix log --- plugins/eddn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index a423876e..e3e4e198 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -279,13 +279,13 @@ class EDDN: f'''{name.replace('_', ' ').title():<8}:\t{value}''' for name, value in kwargs.items() ) - logger.debug(dedent(f''' + logger.debug(dedent(f'''\ {header_msg}: Status :\t{response.status_code} URL :\t{response.url} Headers :\t{response.headers} Content :\t{response.text} - '''+additional_data)) + ''')+additional_data) def sendreplay(self) -> None: # noqa: CCR001 """Send cached Journal lines to EDDN.""" From 0d68c454a58b83abe85af8caa792e751c23b3683 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 27 Jan 2022 16:28:43 +0000 Subject: [PATCH 106/186] Add .editorconfig * The settings are for all file types, we might have some exceptions, i.e. .xml files. * This is based on what we've been doing all along. Only the max line length has previously been codified in, e.g. pyproject.toml. We've not even codified things like "4-space indentations, expanded from TABs" in Contributing.md. --- .editorconfig | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..7dcb0a42 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# This is the project top-level .editorconfig +root = true + +# Defaults for all file types +[*] +# 4-space indents, no TABs +indent_style = space +tab_width = 4 +indent_size = tab + +# Hard-wrap at 120 columns +max_line_length = 119 + +# Unix EOL +end_of_line = lf + +# UTF-8 is the only sensible option, no BOM +charset = utf-8 + +# All files should have a final newline +insert_final_newline = true From f36d45d4bc70062b89545de5ac47e29146b50f64 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 27 Jan 2022 16:36:41 +0000 Subject: [PATCH 107/186] Contributing: Add section on text formatting / .editorconfig --- Contributing.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Contributing.md b/Contributing.md index 0b1f4d58..e61fe764 100644 --- a/Contributing.md +++ b/Contributing.md @@ -23,6 +23,16 @@ consistent with our vision for EDMC. Fundamental changes in particular need to b --- +## Text formatting + +The project contains an `.editorconfig` file at its root. Please either ensure +your editor is taking note of those settings, or cross-check its contents +with the +[editorconfig documentation](https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties) +, and ensure your editor/IDE's settings match. + +--- + ## General workflow 1. You will need a GitHub account. From cdba879369c3b3818aff1cd05671016439a5fc88 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 27 Jan 2022 16:37:20 +0000 Subject: [PATCH 108/186] .editorconfig: Actually, we use crlf, with git converting --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 7dcb0a42..9f588ce0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,7 +12,7 @@ indent_size = tab max_line_length = 119 # Unix EOL -end_of_line = lf +end_of_line = crlf # UTF-8 is the only sensible option, no BOM charset = utf-8 From 32f98ea6dee5bbc0762e2c115083e390046b4c2b Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 27 Jan 2022 17:01:49 +0000 Subject: [PATCH 109/186] .editorconfig: Fix the EOL comment --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 9f588ce0..adbf367f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,7 @@ indent_size = tab # Hard-wrap at 120 columns max_line_length = 119 -# Unix EOL +# Windows EOL, for historical reasons end_of_line = crlf # UTF-8 is the only sensible option, no BOM From f4ba4775f2424244ed5467d55760bd3a3a819642 Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 27 Jan 2022 19:40:06 +0200 Subject: [PATCH 110/186] pull decode/decompress out into its own function --- debug_webserver.py | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/debug_webserver.py b/debug_webserver.py index 0a456dc9..26e02714 100644 --- a/debug_webserver.py +++ b/debug_webserver.py @@ -6,7 +6,7 @@ import tempfile import threading import zlib from http import server -from typing import Any, Callable, Tuple, Union +from typing import Any, Callable, Literal, Tuple, Union from urllib.parse import parse_qs from config import appname @@ -37,16 +37,7 @@ class LoggingHandler(server.BaseHTTPRequestHandler): encoding = self.headers.get('Content-Encoding') - if encoding == 'gzip': - data = gzip.decompress(data_raw).decode('utf-8', errors='replace') - - elif encoding == 'deflate': - data = zlib.decompress(data_raw).decode('utf-8', errors='replace') - - else: - data = data_raw.decode('utf-8', errors='replace') - - to_save = data + to_save = self.get_printable(data_raw, encoding) target_path = self.path if len(target_path) > 1 and target_path[0] == '/': @@ -57,7 +48,7 @@ class LoggingHandler(server.BaseHTTPRequestHandler): response: Union[Callable[[str], str], str, None] = DEFAULT_RESPONSES.get(target_path) if callable(response): - response = response(data) + response = response(to_save) self.send_response_only(200, "OK") if response is not None: @@ -85,6 +76,31 @@ class LoggingHandler(server.BaseHTTPRequestHandler): with output_lock, target_file.open('a') as f: f.write(to_save + "\n\n") + @staticmethod + def get_printable(data: bytes, compression: Literal['deflate'] | Literal['gzip'] | str | None = None) -> str: + """ + Convert an incoming data stream into a string. + + :param data: The data to convert + :param compression: The compression to remove, defaults to None + :raises ValueError: If compression is unknown + :return: printable strings + """ + ret: bytes = b'' + if compression is None: + ret = data + + elif compression == 'deflate': + ret = zlib.decompress(data) + + elif compression == 'gzip': + ret = gzip.decompress(data) + + else: + raise ValueError(f'Unknown encoding for data {compression!r}') + + return ret.decode('utf-8', errors='replace') + def safe_file_name(name: str): """ From b180f34f167f651c3bd30eda8bc977a2f4cd0271 Mon Sep 17 00:00:00 2001 From: A_D Date: Thu, 27 Jan 2022 19:52:51 +0200 Subject: [PATCH 111/186] fix bugs that were hidden --- debug_webserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debug_webserver.py b/debug_webserver.py index 26e02714..ace82a33 100644 --- a/debug_webserver.py +++ b/debug_webserver.py @@ -33,11 +33,10 @@ class LoggingHandler(server.BaseHTTPRequestHandler): """Handle POST.""" logger.info(f"Received a POST for {self.path!r}!") data_raw: bytes = self.rfile.read(int(self.headers['Content-Length'])) - data: str | bytes encoding = self.headers.get('Content-Encoding') - to_save = self.get_printable(data_raw, encoding) + to_save = data = self.get_printable(data_raw, encoding) target_path = self.path if len(target_path) > 1 and target_path[0] == '/': @@ -134,6 +133,7 @@ def generate_inara_response(raw_data: str) -> str: def extract_edsm_data(data: str) -> dict[str, Any]: + """Extract relevant data from edsm data.""" res = parse_qs(data) return {name: data[0] for name, data in res.items()} From 02dd800c5718d3bddb6439e3e9ef724ed710bb89 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 31 Jan 2022 14:50:12 +0000 Subject: [PATCH 112/186] Inara: Change the way we determine if a credits delta is interesting Now based on the *minimum* of a fractional or absolute change. See for discussion. --- plugins/inara.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index be4f35ac..abc45d5e 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -33,7 +33,10 @@ if TYPE_CHECKING: _TIMEOUT = 20 FAKE = ('CQC', 'Training', 'Destination') # Fake systems that shouldn't be sent to Inara -CREDIT_RATIO = 1.05 # Update credits if they change by 5% over the course of a session +# We only update Credits to Inara if the delta from the last sent value is +# greater than certain thresholds +CREDITS_DELTA_MIN_FRACTION = 0.05 # Fractional difference threshold +CREDITS_DELTA_MIN_ABSOLUTE = 10_000_000 # Absolute difference threshold # These need to be defined above This @@ -1358,7 +1361,10 @@ def cmdr_data(data: CAPIData, is_beta): # noqa: CCR001 this.station_link.update_idletasks() if config.get_int('inara_out') and not is_beta and not this.multicrew and credentials(this.cmdr): - if not (CREDIT_RATIO > this.last_credits / data['commander']['credits'] > 1 / CREDIT_RATIO): + if ( + abs(this.last_credits - data['commander']['credits']) >= + min(this.last_credits * CREDITS_DELTA_MIN_FRACTION, CREDITS_DELTA_MIN_ABSOLUTE) + ): this.filter_events( Credentials(this.cmdr, this.FID, str(credentials(this.cmdr))), lambda e: e.name != 'setCommanderCredits' From adb8055f0871b17eeec912e4f319f9288e8aae9b Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 31 Jan 2022 15:22:36 +0000 Subject: [PATCH 113/186] Inara: Be paranoid in case of -ve credits balance --- plugins/inara.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inara.py b/plugins/inara.py index abc45d5e..08b3b86d 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1363,7 +1363,7 @@ def cmdr_data(data: CAPIData, is_beta): # noqa: CCR001 if config.get_int('inara_out') and not is_beta and not this.multicrew and credentials(this.cmdr): if ( abs(this.last_credits - data['commander']['credits']) >= - min(this.last_credits * CREDITS_DELTA_MIN_FRACTION, CREDITS_DELTA_MIN_ABSOLUTE) + min(abs(this.last_credits * CREDITS_DELTA_MIN_FRACTION), CREDITS_DELTA_MIN_ABSOLUTE) ): this.filter_events( Credentials(this.cmdr, this.FID, str(credentials(this.cmdr))), From 3e0c69be5e2f48ddbc1b4c786be353a4ed90e869 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 31 Jan 2022 15:41:25 +0000 Subject: [PATCH 114/186] docs: Pass for config.py -> config/__init__.py and related changes --- Contributing.md | 11 ++++++----- docs/Releasing.md | 21 +++++++++++---------- tests/{config.py => config}/_old_config.py | 0 tests/{config.py => config}/test_config.py | 0 4 files changed, 17 insertions(+), 15 deletions(-) rename tests/{config.py => config}/_old_config.py (100%) rename tests/{config.py => config}/test_config.py (100%) diff --git a/Contributing.md b/Contributing.md index e61fe764..02919c2f 100644 --- a/Contributing.md +++ b/Contributing.md @@ -191,9 +191,10 @@ only the 'C' (Patch) component. Going forwards we will always use the full [Semantic Version](https://semver.org/#semantic-versioning-specification-semver) and 'folder style' tag names, e.g. `Release/Major.Minor.Patch`. -Currently the only file that defines the version code-wise is `config.py`. -`Changelog.md` and `edmarketconnector.xml` are another matter handled as part -of [the release process](docs/Releasing.md#distribution). +Currently, the only file that defines the version code-wise is +`config/__init__.py`. `Changelog.md` and `edmarketconnector.xml` are another +matter handled as part of +[the release process](docs/Releasing.md#distribution). --- @@ -216,12 +217,12 @@ re-introduce a bug down the line. We use the [`pytest`](https://docs.pytest.org/en/stable/) for unit testing. The files for a test should go in a sub-directory of `tests/` named after the -(principal) file that contains the code they are testing. e.g. for +(principal) file or directory that contains the code they are testing. e.g. for journal_lock.py the tests are in `tests/journal_lock.py/test_journal_lock.py`. The `test_` prefix on `test_journal_lock.py` is necessary in order for `pytest` to recognise the file as containing tests to be run. The sub-directory avoids having a mess of files in `tests`, particularly when -there might be supporting files, e.g. `tests/config.py/_old_config.py` or files +there might be supporting files, e.g. `tests/config/_old_config.py` or files containing test data. Invoking just a bare `pytest` command will run all tests. diff --git a/docs/Releasing.md b/docs/Releasing.md index 50f5d0d6..73114b4c 100644 --- a/docs/Releasing.md +++ b/docs/Releasing.md @@ -17,7 +17,8 @@ this document aims to enable anyone to quickly get up to speed on how to: available versions and asks the user to upgrade. Note that for Windows only a 32-bit application is supported at this time. -This is principally due to the Windows Registry handling in config.py. +This is principally due to the Windows Registry handling in +`config/windows.py`. # Environment @@ -103,7 +104,7 @@ that. registry entries on Windows. 1. Application names, version and URL of the file with latest release - information. These are all in the `config.py` file. See the + information. These are all in the `config/__init__.py` file. See the `from config import ...` lines in setup.py. 1. `appname`: The short appname, e.g. 'EDMarketConnector' 2. `applongname`: The long appname, e.g. 'E:D Market Connector' @@ -206,24 +207,24 @@ following. 1. You should by this time know what changes are going into the release, and which branch (stable or beta) you'll be ultimately updating. -1. So as to make backing out any mistakes easier create a new branch for this +2. So as to make backing out any mistakes easier create a new branch for this release, using a name like `release-4.0.2`. Do not use the tag `Release/4.0.2` form, that could cause confusion. 1. `git checkout stable` # Or whichever other branch is appropriate. 1. `git pull origin` # Ensures local branch is up to date. 1. `git checkout -b release-4.0.2` -1. Get all the relevant code changes into this branch. This might mean +3. Get all the relevant code changes into this branch. This might mean merging from another branch, such as an issue-specific one, or possibly cherry-picking commits. See [Contributing Guidelines](../Contributing.md) for how such branches should be named. -1. You should have already decided on the new -[Version String](#Version-Strings), as it's specified in `config.py`. You'll -need to redo the `.msi` build if you forgot. **Remember to do a fresh git -commit for this change.** +4. You should have already decided on the new +[Version String](#Version-Strings), as it's specified in `config/__init__.py`. +You'll need to redo the `.msi` build if you forgot. **Remember to do a fresh +git commit for this change.** -1. Prepare a changelog text for the release. You'll need this both for the +5. Prepare a changelog text for the release. You'll need this both for the GitHub release and the contents of the `edmarketconnector.xml` file if making a `stable` release, as well as any social media posts you make. 1. The primary location of the changelog is [Changelog.md](../Changelog.md) - @@ -408,7 +409,7 @@ changelog text to the correct section(s): `https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml` - as per `config.py` `update_feed`. + as per `config/__init__.py` `update_feed`. NB: It can take some time for GitHub to show the changed edmarketconnector.xml contents to all users. diff --git a/tests/config.py/_old_config.py b/tests/config/_old_config.py similarity index 100% rename from tests/config.py/_old_config.py rename to tests/config/_old_config.py diff --git a/tests/config.py/test_config.py b/tests/config/test_config.py similarity index 100% rename from tests/config.py/test_config.py rename to tests/config/test_config.py From 9af6474d3ca2761e3ec676292f6d3d859418642a Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 31 Jan 2022 15:45:40 +0000 Subject: [PATCH 115/186] docs: Further config.py -> config tweaks --- Contributing.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Contributing.md b/Contributing.md index 02919c2f..100b43ff 100644 --- a/Contributing.md +++ b/Contributing.md @@ -217,10 +217,16 @@ re-introduce a bug down the line. We use the [`pytest`](https://docs.pytest.org/en/stable/) for unit testing. The files for a test should go in a sub-directory of `tests/` named after the -(principal) file or directory that contains the code they are testing. e.g. for -journal_lock.py the tests are in `tests/journal_lock.py/test_journal_lock.py`. -The `test_` prefix on `test_journal_lock.py` is necessary in order for `pytest` -to recognise the file as containing tests to be run. +(principal) file or directory that contains the code they are testing. +For example: + +- Tests for `journal_lock.py` are in + `tests/journal_lock.py/test_journal_lock.py`. The `test_` prefix on + `test_journal_lock.py` is necessary in order for `pytest` to recognise the + file as containing tests to be run. +- Tests for `config/` code are located in `tests/config/test_config.py`, not + `tests/config.py/test_config.py` + The sub-directory avoids having a mess of files in `tests`, particularly when there might be supporting files, e.g. `tests/config/_old_config.py` or files containing test data. From d03c06b47f466398bb8cd3331948351a241bc8f0 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 31 Jan 2022 16:35:41 +0000 Subject: [PATCH 116/186] appversion: Bump to 3.5.0-beta6 in develop for uniqueness --- config/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/__init__.py b/config/__init__.py index eff2859b..0ad38b97 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -52,7 +52,7 @@ appcmdname = 'EDMC' # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.3.0-beta4' +_static_appversion = '5.3.0-beta6' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD' From c13d19d5457d83d6e76128186fd8809dc22535e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jan 2022 17:04:08 +0000 Subject: [PATCH 117/186] build(deps-dev): bump types-requests from 2.27.7 to 2.27.8 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.7 to 2.27.8. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index a1911c5f..3d9976a9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.3 mypy==0.931 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.27.7 +types-requests==2.27.8 # Code formatting tools autopep8==1.6.0 From 1bca798fc6f66e6e060a41576fa676baa32c165f Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 31 Jan 2022 18:29:08 +0000 Subject: [PATCH 118/186] python version: Some docs updates --- ChangeLog.md | 2 +- docs/Releasing.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 5c2f7e73..81a9a609 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -9,7 +9,7 @@ produce the Windows executables and installer. --- -* We now test against, and package with, Python 3.10.1. +* We now test against, and package with, Python 3.10.2. **As a consequence of this we no longer support Windows 7. This is due to diff --git a/docs/Releasing.md b/docs/Releasing.md index 73114b4c..d311c85a 100644 --- a/docs/Releasing.md +++ b/docs/Releasing.md @@ -439,6 +439,8 @@ When changing the Python version (Major.Minor.Patch) used: 1. `.github/workflows/windows-build.yml` needs updating to have the GitHub based build use the correct version. + 2. `ChangeLog.md` - The `We now test against, and package with, Python + M.m.P.` line. 1. Major or Minor level changes: From 5aa7b98cf6324b1ff2fe9c4f4c3f42914a634b25 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 1 Feb 2022 14:14:43 +0000 Subject: [PATCH 119/186] setup.py: Add `util/` to win32 packages Currently only `plugins/eddn.py` imports anything from `util/`, and as the core plugins are dynamically loaded py2exe can't possibly know about this. So we need to specify it explictly. Tested with build and install, and also manually checking the resulting library.zip: ``` 0$ unzip -l library.zip | grep 'util/' 10582 2022-02-01 14:12 urllib3/util/url.pyc 11221 2022-02-01 14:12 urllib3/util/ssl_.pyc 3367 2022-02-01 14:12 urllib3/util/connection.pyc 1031 2022-02-01 14:12 urllib3/util/__init__.pyc 1266 2022-02-01 14:12 urllib3/util/proxy.pyc 3394 2022-02-01 14:12 urllib3/util/request.pyc 8863 2022-02-01 14:12 urllib3/util/timeout.pyc 986 2022-02-01 14:12 urllib3/util/queue.pyc 7332 2022-02-01 14:12 urllib3/util/ssltransport.pyc 1105 2022-02-01 14:12 util/text.pyc 3015 2022-02-01 14:12 urllib3/util/wait.pyc 110 2022-02-01 14:12 util/__init__.pyc 15704 2022-02-01 14:12 urllib3/util/retry.pyc 2279 2022-02-01 14:12 urllib3/util/response.pyc ``` --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index f73dc370..57383fe7 100755 --- a/setup.py +++ b/setup.py @@ -192,6 +192,7 @@ elif sys.platform == 'win32': 'optimize': 2, 'packages': [ 'sqlite3', # Included for plugins + 'util', # 2022-02-01 only imported in plugins/eddn.py ], 'includes': [ 'dataclasses', From ac7cfb9b147fd0e90aa7ffeff81ef133f5d74399 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 1 Feb 2022 14:45:30 +0000 Subject: [PATCH 120/186] core plugins: Add big obvious comment about imports and windows installer --- plugins/coriolis.py | 22 ++++++++++++++++++++++ plugins/eddb.py | 22 ++++++++++++++++++++++ plugins/eddn.py | 22 ++++++++++++++++++++++ plugins/edsm.py | 22 ++++++++++++++++++++++ plugins/edsy.py | 22 ++++++++++++++++++++++ plugins/inara.py | 22 ++++++++++++++++++++++ 6 files changed, 132 insertions(+) diff --git a/plugins/coriolis.py b/plugins/coriolis.py index c05ba089..78ae1b41 100644 --- a/plugins/coriolis.py +++ b/plugins/coriolis.py @@ -1,5 +1,27 @@ """Coriolis ship export.""" +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# +# This is an EDMC 'core' plugin. +# +# All EDMC plugins are *dynamically* loaded at run-time. +# +# We build for Windows using `py2exe`. +# +# `py2exe` can't possibly know about anything in the dynamically loaded +# core plugins. +# +# Thus you **MUST** check if any imports you add in this file are only +# referenced in this file (or only in any other core plugin), and if so... +# +# YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN `setup.py` +# SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT IN AN END-USER +# INSTALLATION ON WINDOWS. +# +# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# import base64 import gzip import io diff --git a/plugins/eddb.py b/plugins/eddb.py index 220046f0..111d768c 100644 --- a/plugins/eddb.py +++ b/plugins/eddb.py @@ -23,6 +23,28 @@ # +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# +# This is an EDMC 'core' plugin. +# +# All EDMC plugins are *dynamically* loaded at run-time. +# +# We build for Windows using `py2exe`. +# +# `py2exe` can't possibly know about anything in the dynamically loaded +# core plugins. +# +# Thus you **MUST** check if any imports you add in this file are only +# referenced in this file (or only in any other core plugin), and if so... +# +# YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN `setup.py` +# SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT IN AN END-USER +# INSTALLATION ON WINDOWS. +# +# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# import sys from typing import TYPE_CHECKING, Any, Optional diff --git a/plugins/eddn.py b/plugins/eddn.py index e3e4e198..d58eac26 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1,5 +1,27 @@ """Handle exporting data to EDDN.""" +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# +# This is an EDMC 'core' plugin. +# +# All EDMC plugins are *dynamically* loaded at run-time. +# +# We build for Windows using `py2exe`. +# +# `py2exe` can't possibly know about anything in the dynamically loaded +# core plugins. +# +# Thus you **MUST** check if any imports you add in this file are only +# referenced in this file (or only in any other core plugin), and if so... +# +# YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN `setup.py` +# SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT IN AN END-USER +# INSTALLATION ON WINDOWS. +# +# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# import itertools import json import pathlib diff --git a/plugins/edsm.py b/plugins/edsm.py index 31f5a317..6bbf0223 100644 --- a/plugins/edsm.py +++ b/plugins/edsm.py @@ -9,6 +9,28 @@ # 4) Ensure the EDSM API call(back) for setting the image at end of system # text is always fired. i.e. CAPI cmdr_data() processing. +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# +# This is an EDMC 'core' plugin. +# +# All EDMC plugins are *dynamically* loaded at run-time. +# +# We build for Windows using `py2exe`. +# +# `py2exe` can't possibly know about anything in the dynamically loaded +# core plugins. +# +# Thus you **MUST** check if any imports you add in this file are only +# referenced in this file (or only in any other core plugin), and if so... +# +# YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN `setup.py` +# SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT IN AN END-USER +# INSTALLATION ON WINDOWS. +# +# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# import json import threading import tkinter as tk diff --git a/plugins/edsy.py b/plugins/edsy.py index 7da92fa4..df61683e 100644 --- a/plugins/edsy.py +++ b/plugins/edsy.py @@ -1,5 +1,27 @@ # EDShipyard ship export +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# +# This is an EDMC 'core' plugin. +# +# All EDMC plugins are *dynamically* loaded at run-time. +# +# We build for Windows using `py2exe`. +# +# `py2exe` can't possibly know about anything in the dynamically loaded +# core plugins. +# +# Thus you **MUST** check if any imports you add in this file are only +# referenced in this file (or only in any other core plugin), and if so... +# +# YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN `setup.py` +# SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT IN AN END-USER +# INSTALLATION ON WINDOWS. +# +# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# import base64 import gzip import json diff --git a/plugins/inara.py b/plugins/inara.py index 08b3b86d..2446693a 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1,5 +1,27 @@ """Inara Sync.""" +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# +# This is an EDMC 'core' plugin. +# +# All EDMC plugins are *dynamically* loaded at run-time. +# +# We build for Windows using `py2exe`. +# +# `py2exe` can't possibly know about anything in the dynamically loaded +# core plugins. +# +# Thus you **MUST** check if any imports you add in this file are only +# referenced in this file (or only in any other core plugin), and if so... +# +# YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN `setup.py` +# SO AS TO ENSURE THE FILES ARE ACTUALLY PRESENT IN AN END-USER +# INSTALLATION ON WINDOWS. +# +# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# +# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# ! $# import json import threading import time From b108456ae3e6efe3d16ea71aa25783301502190c Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 1 Feb 2022 15:26:10 +0000 Subject: [PATCH 121/186] Contributing: Add new section about plugin-only imports --- Contributing.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Contributing.md b/Contributing.md index 100b43ff..4d7fc9fc 100644 --- a/Contributing.md +++ b/Contributing.md @@ -246,6 +246,41 @@ Otherwise, see the [pytest documentation](https://docs.pytest.org/en/stable/cont --- +## Imports used only in core plugins + +Because the 'core' plugins, as with any EDMarketConnector plugin, are only ever +loaded dynamically, not through an explicit `import` statement, there is no +way for `py2exe` to know about them when building the contents of the +`dist.win32` directory. See [docs/Releasing.md](docs/Releasing.md) for more +information about this build process. + +Thus, you **MUST** check if any imports you add in `plugins/*.py` files are only +referenced in that file (or also only in any other core plugin), and if so +**YOU MUST ENSURE THAT PERTINENT ADJUSTMENTS ARE MADE IN `setup.py` +IN ORDER TO ENSURE THE FILES ARE ACTUALLY PRESENT IN AN END-USER +INSTALLATION ON WINDOWS.** + +An exmaple is that as of 2022-02-01 it was noticed that `plugins/eddn.py` now +uses `util/text.py`, and is the only code to do so. `py2exe` does not detect +this and thus the resulting `dist.win32/library.zip` does not contain the +`util/` directory, let alone the `util/text.py` file. The fix was to update +the appropriate `packages` definition to: + +```python + 'packages': [ + 'sqlite3', # Included for plugins + 'util', # 2022-02-01 only imported in plugins/eddn.py + ], +``` + +Note that in this case it's in `packages` because we want the whole directory +adding. For a single file an extra item in `includes` would suffice. + +Such additions to `setup.py` should not cause any issues if subsequent project +changes cause `py2exe` to automatically pick up the same file(s). + +--- + ## Debugging network sends Rather than risk sending bad data to a remote service, even if only through From 87193b4a510c4f8f4c11012dea74c4b294506a23 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 1 Feb 2022 15:43:42 +0000 Subject: [PATCH 122/186] eddn: Be more paranoid about system name augmentation --- plugins/eddn.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index d58eac26..ee8a52dd 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -802,7 +802,12 @@ class EDDN: # If 'SystemName' or 'System' is there, it's directly from a journal event. # If they're not there *and* 'StarSystem' isn't either, then we add the latter. if 'SystemName' not in entry and 'System' not in entry and 'StarSystem' not in entry: - entry['StarSystem'] = system_name + if system_name is not None and system_name != '': + entry['StarSystem'] = system_name + + else: + # Bad assumptions if this is the case + logger.error(f'No system name in entry, and system_name was not set either! entry:\n{entry!r}\n') if 'SystemAddress' not in entry: if this.systemaddress is None: From 67019e91b258c0342573f77a23cfdeeeb81f885a Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 1 Feb 2022 16:11:35 +0000 Subject: [PATCH 123/186] eddn: Extra System (name) augmentation paranoia --- plugins/eddn.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index ee8a52dd..5eefc46a 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -802,12 +802,13 @@ class EDDN: # If 'SystemName' or 'System' is there, it's directly from a journal event. # If they're not there *and* 'StarSystem' isn't either, then we add the latter. if 'SystemName' not in entry and 'System' not in entry and 'StarSystem' not in entry: - if system_name is not None and system_name != '': - entry['StarSystem'] = system_name + if system_name is None or not isinstance(system_name, str) or system_name == '': + # Bad assumptions if this is the case + logger.warning(f'No system name in entry, and system_name was not set either! entry:\n{entry!r}\n') + return "passed-in system_name is empty, can't add System" else: - # Bad assumptions if this is the case - logger.error(f'No system name in entry, and system_name was not set either! entry:\n{entry!r}\n') + entry['StarSystem'] = system_name if 'SystemAddress' not in entry: if this.systemaddress is None: From f144c199ae4e8f2088023b878860d1f6bc08b56c Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 1 Feb 2022 16:12:05 +0000 Subject: [PATCH 124/186] eddn: Be paranoid about Status.json BodyName value --- plugins/eddn.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 5eefc46a..95317c3c 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1596,10 +1596,12 @@ def dashboard_entry(cmdr: str, is_beta: bool, entry: Dict[str, Any]) -> None: :param is_beta: Whether non-live game version was detected. :param entry: The latest Status.json data. """ + this.status_body_name = None if 'BodyName' in entry: - this.status_body_name = entry['BodyName'] + if not isinstance(entry['BodyName'], str): + logger.warning(f'BodyName was present but not a string! "{entry["BodyName"]}"') - else: - this.status_body_name = None + else: + this.status_body_name = entry['BodyName'] tracking_ui_update() From e2e34674c26dc6c0b88e997ac849f5b88d8df5d4 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 1 Feb 2022 16:22:12 +0000 Subject: [PATCH 125/186] eddn: codexentry - add paranoia post-processing check of values --- plugins/eddn.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index 95317c3c..7cfda84b 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -951,6 +951,10 @@ class EDDN: entry['BodyID'] = this.body_id ####################################################################### + for k, v in entry.items(): + if v is None or isinstance(v, str) and v == '': + logger.warning(f'post-processing entry contains entry["{k}"] = {v}]') + msg = { '$schemaRef': f'https://eddn.edcd.io/schemas/codexentry/1{"/test" if is_beta else ""}', 'message': entry From 4fa64eec40b17a3e356ab091ee1ce40e6f04658f Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 1 Feb 2022 16:43:19 +0000 Subject: [PATCH 126/186] eddn: warnings improvements * Log the type of unexpected values. * Say 'falsey' not 'None' when we only `if not ...` --- plugins/eddn.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 7cfda84b..ea80404f 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -895,7 +895,7 @@ class EDDN: this.eddn.export_journal_entry(cmdr, entry, msg) return None - def export_journal_codexentry( + def export_journal_codexentry( # noqa: CCR001 self, cmdr: str, is_beta: bool, entry: MutableMapping[str, Any] ) -> Optional[str]: """ @@ -948,12 +948,16 @@ class EDDN: # Only set BodyID if journal BodyName matches the Status.json one. # This avoids binary body issues. if this.status_body_name == this.body_name: - entry['BodyID'] = this.body_id + if this.body_id is not None and isinstance(this.body_id, int): + entry['BodyID'] = this.body_id + + else: + logger.warning(f'this.body_id was not set properly: "{this.body_id}" ({type(this.body_id)})') ####################################################################### for k, v in entry.items(): if v is None or isinstance(v, str) and v == '': - logger.warning(f'post-processing entry contains entry["{k}"] = {v}]') + logger.warning(f'post-processing entry contains entry["{k}"] = {v} {(type(v))}') msg = { '$schemaRef': f'https://eddn.edcd.io/schemas/codexentry/1{"/test" if is_beta else ""}', @@ -1423,14 +1427,14 @@ def journal_entry( # noqa: C901, CCR001 # add mandatory StarSystem, StarPos and SystemAddress properties to Scan events if 'StarSystem' not in entry: if not system: - logger.warning("system is None, can't add StarSystem") + logger.warning("system is falsey, can't add StarSystem") return "system is None, can't add StarSystem" entry['StarSystem'] = system if 'StarPos' not in entry: if not this.coordinates: - logger.warning("this.coordinates is None, can't add StarPos") + logger.warning("this.coordinates is falsey, can't add StarPos") return "this.coordinates is None, can't add StarPos" # Gazelle[TD] reported seeing a lagged Scan event with incorrect @@ -1443,7 +1447,7 @@ def journal_entry( # noqa: C901, CCR001 if 'SystemAddress' not in entry: if not this.systemaddress: - logger.warning("this.systemaddress is None, can't add SystemAddress") + logger.warning("this.systemaddress is falsey, can't add SystemAddress") return "this.systemaddress is None, can't add SystemAddress" entry['SystemAddress'] = this.systemaddress @@ -1603,7 +1607,7 @@ def dashboard_entry(cmdr: str, is_beta: bool, entry: Dict[str, Any]) -> None: this.status_body_name = None if 'BodyName' in entry: if not isinstance(entry['BodyName'], str): - logger.warning(f'BodyName was present but not a string! "{entry["BodyName"]}"') + logger.warning(f'BodyName was present but not a string! "{entry["BodyName"]}" ({type(entry["BodyName"])})') else: this.status_body_name = entry['BodyName'] From 2bf70aae14bbea7ff2876ad6c474e9e2091f59fe Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 1 Feb 2022 16:59:44 +0000 Subject: [PATCH 127/186] eddn: Be paranoid about this.status_body_name type and value As it was we'd blindly set codexentry['BodyName'] from it if not None. --- plugins/eddn.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index ea80404f..39c28eb5 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -943,7 +943,11 @@ class EDDN: entry = ret # Set BodyName if it's available from Status.json - if this.status_body_name is not None: + if this.status_body_name is None or not isinstance(this.status_body_name, str): + logger.warning(f'this.status_body_name was not set properly:' + f' "{this.status_body_name}" ({type(this.status_body_name)})') + + else: entry['BodyName'] = this.status_body_name # Only set BodyID if journal BodyName matches the Status.json one. # This avoids binary body issues. From 45b78f55c4e888fef1e4562f1880f3ce5cefdb74 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 1 Feb 2022 18:05:28 +0000 Subject: [PATCH 128/186] eddn: say 'is falsey' in error messages as well --- plugins/eddn.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 39c28eb5..5e4f5749 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1432,14 +1432,14 @@ def journal_entry( # noqa: C901, CCR001 if 'StarSystem' not in entry: if not system: logger.warning("system is falsey, can't add StarSystem") - return "system is None, can't add StarSystem" + return "system is falsey, can't add StarSystem" entry['StarSystem'] = system if 'StarPos' not in entry: if not this.coordinates: logger.warning("this.coordinates is falsey, can't add StarPos") - return "this.coordinates is None, can't add StarPos" + return "this.coordinates is falsey, can't add StarPos" # Gazelle[TD] reported seeing a lagged Scan event with incorrect # augmented StarPos: @@ -1452,7 +1452,7 @@ def journal_entry( # noqa: C901, CCR001 if 'SystemAddress' not in entry: if not this.systemaddress: logger.warning("this.systemaddress is falsey, can't add SystemAddress") - return "this.systemaddress is None, can't add SystemAddress" + return "this.systemaddress is falsey, can't add SystemAddress" entry['SystemAddress'] = this.systemaddress From ed4437b20b23744f6c80832e2ce3cf86651d4de1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 17:06:14 +0000 Subject: [PATCH 129/186] build(deps-dev): bump coverage[toml] from 6.3 to 6.3.1 Bumps [coverage[toml]](https://github.com/nedbat/coveragepy) from 6.3 to 6.3.1. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/6.3...6.3.1) --- updated-dependencies: - dependency-name: coverage[toml] dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3d9976a9..d40a05c7 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -35,7 +35,7 @@ py2exe==0.11.0.1; sys_platform == 'win32' # Testing pytest==6.2.5 pytest-cov==3.0.0 # Pytest code coverage support -coverage[toml]==6.3 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs +coverage[toml]==6.3.1 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs # For manipulating folder permissions and the like. pywin32==303; sys_platform == 'win32' From 19a7bf0bd1877deed465d51bbe7a7a86b0405c18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 17:06:17 +0000 Subject: [PATCH 130/186] build(deps-dev): bump grip from 4.5.2 to 4.6.0 Bumps [grip](https://github.com/joeyespo/grip) from 4.5.2 to 4.6.0. - [Release notes](https://github.com/joeyespo/grip/releases) - [Changelog](https://github.com/joeyespo/grip/blob/master/CHANGES.md) - [Commits](https://github.com/joeyespo/grip/compare/v4.5.2...v4.6.0) --- updated-dependencies: - dependency-name: grip dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3d9976a9..25d11e40 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -24,7 +24,7 @@ types-requests==2.27.8 autopep8==1.6.0 # HTML changelogs -grip==4.5.2 +grip==4.6.0 # Packaging # Used to put together a WiX configuration from template/auto-gen From 1bb48ed5960aef0951df0291b0cfeea9f7cd519e Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 3 Feb 2022 13:59:52 +0000 Subject: [PATCH 131/186] eddn/codexentry: Bail and show error if empty string in message --- plugins/eddn.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index 5e4f5749..afab0458 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -962,6 +962,10 @@ class EDDN: for k, v in entry.items(): if v is None or isinstance(v, str) and v == '': logger.warning(f'post-processing entry contains entry["{k}"] = {v} {(type(v))}') + # We should drop this message and VERY LOUDLY inform the + # user, in the hopes they'll open a bug report with the + # raw Journal event that caused this. + return 'CodexEntry had empty string, PLEASE ALERT THE EDMC DEVELOPERS' msg = { '$schemaRef': f'https://eddn.edcd.io/schemas/codexentry/1{"/test" if is_beta else ""}', From d83a1c514d15899f0752abcbd55a45c38aa2f133 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Thu, 3 Feb 2022 15:33:34 +0000 Subject: [PATCH 132/186] eddn: Log if a 'location' event doesn't have SystemAddress It *really* shouldn't ever happen, and we'll want to see the full event if it does somehow. --- plugins/eddn.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index d58eac26..2f54d609 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1337,7 +1337,14 @@ def journal_entry( # noqa: C901, CCR001 elif this.systemaddress != entry.get('SystemAddress'): this.coordinates = None # Docked event doesn't include coordinates - this.systemaddress = entry.get('SystemAddress') # type: ignore + if 'SystemAddress' not in entry: + logger.warning(f'"location" event without SystemAddress !!!:\n{entry}\n') + + # But we'll still *use* the value, because if a 'location' event doesn't + # have this we've still moved and now don't know where and MUST NOT + # continue to use any old value. + # Yes, explicitly state `None` here, so it's crystal clear. + this.systemaddress = entry.get('SystemAddress', None) # type: ignore elif entry['event'] == 'ApproachBody': this.body_name = entry['Body'] From f8e0fb3658733445e8a98016de639d85c5cf4106 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 4 Feb 2022 12:22:47 +0000 Subject: [PATCH 133/186] monitor: Fold 'EngineerCraft' to lower case in event_type comparison --- monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitor.py b/monitor.py index 972b2b89..a90397e4 100644 --- a/monitor.py +++ b/monitor.py @@ -1422,7 +1422,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below category = self.category(received['Category']) state_category[received['Material']] += received['Quantity'] - elif event_type == 'EngineerCraft' or ( + elif event_type == 'engineercraft' or ( event_type == 'engineerlegacyconvert' and not entry.get('IsPreview') ): From 8af0f5598b02c1e07af19a46cb17823629ef86d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Feb 2022 17:06:20 +0000 Subject: [PATCH 134/186] build(deps): bump actions/setup-python from 2.3.1 to 2.3.2 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.3.1 to 2.3.2. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2.3.1...v2.3.2) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/pr-checks.yml | 2 +- .github/workflows/push-checks.yml | 2 +- .github/workflows/windows-build.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 7b0a61c2..cbe187a3 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -53,7 +53,7 @@ jobs: # Get Python set up #################################################################### - name: Set up Python 3.10 - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: "3.10" - name: Install dependencies diff --git a/.github/workflows/push-checks.yml b/.github/workflows/push-checks.yml index e8cf5f5c..100e2363 100644 --- a/.github/workflows/push-checks.yml +++ b/.github/workflows/push-checks.yml @@ -21,7 +21,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python 3.10 - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: "3.10" - name: Install dependencies diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 5d12da92..f76bf1b6 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - - uses: actions/setup-python@v2.3.1 + - uses: actions/setup-python@v2.3.2 with: python-version: "3.10.2" architecture: "x86" From ef963fc949acae5d40303d39ef25767d97000ec6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Feb 2022 17:06:31 +0000 Subject: [PATCH 135/186] build(deps-dev): bump pytest from 6.2.5 to 7.0.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.5 to 7.0.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/6.2.5...7.0.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index f1974f58..ba5a4cd2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -33,7 +33,7 @@ lxml==4.7.1 py2exe==0.11.0.1; sys_platform == 'win32' # Testing -pytest==6.2.5 +pytest==7.0.0 pytest-cov==3.0.0 # Pytest code coverage support coverage[toml]==6.3.1 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs # For manipulating folder permissions and the like. From 5a31e5be55252096844a4a3fb4241b7b24dfedf9 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 4 Feb 2022 15:04:37 +0000 Subject: [PATCH 136/186] Pre-Release 5.3.0-beta7: appversion and *first pass* ChangeLog --- ChangeLog.md | 156 +++++++++++++++++++++++++++++++++++++++++++++ config/__init__.py | 2 +- 2 files changed, 157 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 81a9a609..b48ab7e0 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -26,6 +26,162 @@ produce the Windows executables and installer. currently used version in a given branch. --- + +Pre-Release 5.3.0-beta7 +=== + +* We now test and build using Python 3.10.2. We do not *yet* make use of any + features specific to Python 3.10 (or 3.9). Let us restate that we + absolutely reserve the right to commence doing so. + +* All communication with remote web servers and APIs now uses the same custom + "User-Agent" header to make attribution to this application clear. +* The process used to build the Windows installers should now always pick up + all the necessary files automatically. Prior to this we used a manual + process to update the installer configuration which was prone to both user + error and neglecting to update it as necessary. +* We now include [FDevIDS](https://github.com/EDCD/FDevIDs) as a + sub-repository, and use its files directly for keeping some game data up to + date. This should hopefully mean we include, e.g. new ships and modules + for loadout exports in a more timely manner. +* We now use UTC-based timestamps in the application's log files. Prior to + this change it was the "local time", but without any indication of the + applied timezone. Each line's timestamp has ` UTC` as a suffix now, and we + are assuming that your local clock is correct *and* the timezone is set + correctly such that Python's `time.gmtime()` yields UTC times. + + This should make it easier to correlate application logfiles with in-game + time and/or third-party service timestamps. +* Running `EDMarketConnector.exe --reset-ui` will now also reset any changes to + the application "UI Scale" or geometry (position and size). +* Inara: Use the `->Statistics->Bank_Account->Current_Wealth` + value when sending a `setCommanderCredits` message to Inara to set + `commanderAssets`. + + This should cause Inara and EDMC to agree on the Commander's current total + assets credits figure, and thus prevent it bouncing between two values. +* Inara: Send a `setCommanderRankPilot` message when the player logs in to the + game on-foot. Previously you would *HAVE* to be in a ship at login time + for this to be sent. + + Thus, you can now relog on-foot in order to update Inara with any Rank up + or progress since the session started. +* "File" -> "Status" will now show the new Odyssey ranks, both the new + categories and the new 'prestige' ranks, e.g. 'Elite I'. +* If the application fails to load valid data from the `NavRoute.json` file + when processing a Journal `NavRoute` event, it will attempt to retry this + operation a number of times as it processes subsequent Journal events. + + This should hopefully work around a race condition where the game might + not have yet updated `NavRoute.json` at all, or has truncated it to empty, + when we first attempt this. + + We will also now *NOT* attempt to load `NavRoute.json` during the startup + 'Journal catch-up' mode, which only sets internal state. + +* EDDN: We now compress all outgoing messages. This might help get some + particularly large `navroute` messages go through. + + If any message is now rejected as 'too large' we will drop it, and thus + not retry it later. The application logs will reflect this. + + NB: The EDDN Gateway was updated to allow messages up to 1 MiB in size + anyway. The old limit was 100 KiB. + +* Inara: We will now send the new idea of "credits in the Commander's wallet" + when the delta from the last sent value is either 5% *or* at least + 10 million. It used to require a strict 5% difference, which meant needing + very large deltas if you had a large credit balance. + + The intention of the 5% is so that newer/poorer commanders still get + updates at an acceptable frequency, with the 10 million alternative trigger + allowing richer commanders to also experience more frequent updates than + was previously the case. + +* EDDN: In an attempt to diagnose some errors observed on the EDDN Gateway + with respect to messages sent from this application some additional checks + and logging have been added. + + **NB: After some thorough investigation it was concluded that these EDDN + errors were likely the result of long-delayed messages due to use of + the "Delay sending until docked" option.** + + There should be no functional changes for users. But if you see any of + the following in this application's log files **PLEASE OPEN + [AN ISSUE ON GITHUB](https://github.com/EDCD/EDMarketConnector/issues/new?assignees=&labels=bug%2C+unconfirmed&template=bug_report.md&title=) + with all the requested information**, so that we can correct the relevant + code: + + - `No system name in entry, and system_name was not set either! entry: ...` + - `BodyName was present but not a string! ...` + - `post-processing entry contains entry ...` + - `this.body_id was not set properly: ...` + - `system is falsey, can't add StarSystem` + - `this.coordinates is falsey, can't add StarPos` + - `this.systemaddress is falsey, can't add SystemAddress` + - `this.status_body_name was not set properly: ...` + + You might also see any of the following in the application status text + (bottom of the window): + + - `passed-in system_name is empty, can't add System` + - `CodexEntry had empty string, PLEASE ALERT THE EDMC DEVELOPERS` + - `system is falsey, can't add StarSystem` + - `this.coordinates is falsey, can't add StarPos` + - `this.systemaddress is falsey, can't add SystemAddress` + +* Inara: You should once more see updates for any materials used in + Engineering. The bug was in our more general Journal event processing + code, such that the state passed to the Inara plugin hadn't been updated. + + Such updates should happen 'immediately', but take into account that there + can be a delay of up to 35 seconds for any data sent to Inara, due to how + we avoid breaking the "2 messages a minute" limit on the Inara API. + + + +Plugin Developers +--- + +* We now have an `.editorconfig` file which will instruct your editor/IDE to + change some settings pertaining to things like indentation and line wrap, + assuming your editor/IDE supports the file. + + See [Contributing.md->Text formatting](Contributing.md#text-formatting). + +* `config.py` has been refactored into a sub-directory, with the per-OS code + split into separate files. There *shouldn't* be any changes necessary to + how you utilise this, e.g. to determine the application version. + +* We will now include in the Windows installer *all* of the files that `py2exe` + places in the build directory. We still do *not* yet include *all* of the + Python 'stdlib', so open an issue on GitHub if something you need is + missing. + +* Due to now using files from the FDevIDs repository the path of, e.g. + `commodity.csv`, in the source has changed. You should never have been + using this application's copy in plugin code anyway as it's not even + packaged in the Windows installer. + +--- +5.3.0-beta2 through 5.3.0-beta6 were used internally, no public pre-releases. + +--- + +Pre-Release 5.3.0-beta2 +=== + +In terms of application functionality this is identical to 5.3.0-beta1. The +only changes are related to the process of building the Windows installer. + +- Ensure we always package all files into the Windows installer. +- Use a different workaround for OneSky (translations website) using "zh-Hans" + for Chinese (Simplified), whereas Windows will call this "zh-CN". This is + in-code and documented with a comment, as opposed to some 'magic' in the + Windows Installer configuration that had no such documentation. It's less + fragile than relying on that, or developers using a script/documented + process to rename the file. + Pre-Release 5.3.0-beta1 === diff --git a/config/__init__.py b/config/__init__.py index 0ad38b97..fa8f1776 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -52,7 +52,7 @@ appcmdname = 'EDMC' # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.3.0-beta6' +_static_appversion = '5.3.0-beta7' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD' From b96019586943887cc196f99a742fee9dff73736d Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 4 Feb 2022 15:21:14 +0000 Subject: [PATCH 137/186] Pre-Release 5.3.0-beta7: ChangeLog pass * Python 3.10 in more places. * Re-ordered entries, both into 'Plugin Developers' section, and grouping for e.g. Inara, EDDN, "obvious users thing", "less obvious user things". * Linked to issues where relevant. --- ChangeLog.md | 114 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 42 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index b48ab7e0..32ccf915 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -13,7 +13,7 @@ produce the Windows executables and installer. **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/). + [Python 3.10.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.** @@ -34,40 +34,31 @@ Pre-Release 5.3.0-beta7 features specific to Python 3.10 (or 3.9). Let us restate that we absolutely reserve the right to commence doing so. +* "File" -> "Status" will now show the new Odyssey ranks, both the new + categories and the new 'prestige' ranks, e.g. 'Elite I'. Closes + [#1369](https://github.com/EDCD/EDMarketConnector/issues/1369). + +* We now use UTC-based timestamps in the application's log files. Prior to + this change it was the "local time", but without any indication of the + applied timezone. Each line's timestamp has ` UTC` as a suffix now, and we + are assuming that your local clock is correct *and* the timezone is set + correctly such that Python's `time.gmtime()` yields UTC times. + + This should make it easier to correlate application logfiles with in-game + time and/or third-party service timestamps. + * All communication with remote web servers and APIs now uses the same custom "User-Agent" header to make attribution to this application clear. + * The process used to build the Windows installers should now always pick up all the necessary files automatically. Prior to this we used a manual process to update the installer configuration which was prone to both user error and neglecting to update it as necessary. -* We now include [FDevIDS](https://github.com/EDCD/FDevIDs) as a - sub-repository, and use its files directly for keeping some game data up to - date. This should hopefully mean we include, e.g. new ships and modules - for loadout exports in a more timely manner. -* We now use UTC-based timestamps in the application's log files. Prior to - this change it was the "local time", but without any indication of the - applied timezone. Each line's timestamp has ` UTC` as a suffix now, and we - are assuming that your local clock is correct *and* the timezone is set - correctly such that Python's `time.gmtime()` yields UTC times. - This should make it easier to correlate application logfiles with in-game - time and/or third-party service timestamps. * Running `EDMarketConnector.exe --reset-ui` will now also reset any changes to - the application "UI Scale" or geometry (position and size). -* Inara: Use the `->Statistics->Bank_Account->Current_Wealth` - value when sending a `setCommanderCredits` message to Inara to set - `commanderAssets`. + the application "UI Scale" or geometry (position and size). Closes + [#1155](https://github.com/EDCD/EDMarketConnector/issues/1155). - This should cause Inara and EDMC to agree on the Commander's current total - assets credits figure, and thus prevent it bouncing between two values. -* Inara: Send a `setCommanderRankPilot` message when the player logs in to the - game on-foot. Previously you would *HAVE* to be in a ship at login time - for this to be sent. - - Thus, you can now relog on-foot in order to update Inara with any Rank up - or progress since the session started. -* "File" -> "Status" will now show the new Odyssey ranks, both the new - categories and the new 'prestige' ranks, e.g. 'Elite I'. * If the application fails to load valid data from the `NavRoute.json` file when processing a Journal `NavRoute` event, it will attempt to retry this operation a number of times as it processes subsequent Journal events. @@ -79,14 +70,24 @@ Pre-Release 5.3.0-beta7 We will also now *NOT* attempt to load `NavRoute.json` during the startup 'Journal catch-up' mode, which only sets internal state. -* EDDN: We now compress all outgoing messages. This might help get some - particularly large `navroute` messages go through. + Closes [#1348](https://github.com/EDCD/EDMarketConnector/issues/1155). - If any message is now rejected as 'too large' we will drop it, and thus - not retry it later. The application logs will reflect this. +* Inara: Use the `->Statistics->Bank_Account->Current_Wealth` + value when sending a `setCommanderCredits` message to Inara to set + `commanderAssets`. - NB: The EDDN Gateway was updated to allow messages up to 1 MiB in size - anyway. The old limit was 100 KiB. + This should cause Inara and EDMC to agree on the Commander's current total + assets credits figure, and thus prevent it bouncing between two values. + + Closes [#1401](https://github.com/EDCD/EDMarketConnector/issues/1401). +* Inara: Send a `setCommanderRankPilot` message when the player logs in to the + game on-foot. Previously you would *HAVE* to be in a ship at login time + for this to be sent. + + Thus, you can now relog on-foot in order to update Inara with any Rank up + or progress since the session started. + + Closes [#1378](https://github.com/EDCD/EDMarketConnector/issues/1378). * Inara: We will now send the new idea of "credits in the Commander's wallet" when the delta from the last sent value is either 5% *or* at least @@ -98,6 +99,30 @@ Pre-Release 5.3.0-beta7 allowing richer commanders to also experience more frequent updates than was previously the case. + Close [#1255](https://github.com/EDCD/EDMarketConnector/issues/1255). + +* Inara: You should once more see updates for any materials used in + Engineering. The bug was in our more general Journal event processing + code pertaining to `EngineerCraft` events, such that the state passed to + the Inara plugin hadn't been updated. + + Such updates should happen 'immediately', but take into account that there + can be a delay of up to 35 seconds for any data sent to Inara, due to how + we avoid breaking the "2 messages a minute" limit on the Inara API. + + Closes [#1395](https://github.com/EDCD/EDMarketConnector/issues/1395). + +* EDDN: We now compress all outgoing messages. This might help get some + particularly large `navroute` messages go through. + + If any message is now rejected as 'too large' we will drop it, and thus + not retry it later. The application logs will reflect this. + + NB: The EDDN Gateway was updated to allow messages up to 1 MiB in size + anyway. The old limit was 100 KiB. + + Closes [#1390](https://github.com/EDCD/EDMarketConnector/issues/1390). + * EDDN: In an attempt to diagnose some errors observed on the EDDN Gateway with respect to messages sent from this application some additional checks and logging have been added. @@ -130,15 +155,8 @@ Pre-Release 5.3.0-beta7 - `this.coordinates is falsey, can't add StarPos` - `this.systemaddress is falsey, can't add SystemAddress` -* Inara: You should once more see updates for any materials used in - Engineering. The bug was in our more general Journal event processing - code, such that the state passed to the Inara plugin hadn't been updated. - - Such updates should happen 'immediately', but take into account that there - can be a delay of up to 35 seconds for any data sent to Inara, due to how - we avoid breaking the "2 messages a minute" limit on the Inara API. - - + Ref: [#1403](https://github.com/EDCD/EDMarketConnector/issues/1403) + [#1393](https://github.com/EDCD/EDMarketConnector/issues/1393). Plugin Developers --- @@ -149,10 +167,22 @@ Plugin Developers See [Contributing.md->Text formatting](Contributing.md#text-formatting). +* As noted above, prior to this version we weren't properly monitoring + `EngineerCraft` events. This caused the `state` passed to plugins to not + contain the correct 'materials' (Raw, Manufactured, Encoded) counts. + * `config.py` has been refactored into a sub-directory, with the per-OS code split into separate files. There *shouldn't* be any changes necessary to how you utilise this, e.g. to determine the application version. + All forms of any `import` statement that worked before should have + unchanged functionality. + +* We now include [FDevIDS](https://github.com/EDCD/FDevIDs) as a + sub-repository, and use its files directly for keeping some game data up to + date. This should hopefully mean we include, e.g. new ships and modules + for loadout exports in a more timely manner. + * We will now include in the Windows installer *all* of the files that `py2exe` places in the build directory. We still do *not* yet include *all* of the Python 'stdlib', so open an issue on GitHub if something you need is @@ -164,7 +194,7 @@ Plugin Developers packaged in the Windows installer. --- -5.3.0-beta2 through 5.3.0-beta6 were used internally, no public pre-releases. +5.3.0-beta3 through 5.3.0-beta6 were used internally, no public pre-releases. --- From edf0393e88062a60e20ea50f6b3d27e768b3c962 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 4 Feb 2022 19:36:52 +0000 Subject: [PATCH 138/186] Pre-Release 5.3.0-beta7: Some ChangeLog review/cleanup --- ChangeLog.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 32ccf915..a752e9b7 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -38,11 +38,15 @@ Pre-Release 5.3.0-beta7 categories and the new 'prestige' ranks, e.g. 'Elite I'. Closes [#1369](https://github.com/EDCD/EDMarketConnector/issues/1369). +* Running `EDMarketConnector.exe --reset-ui` will now also reset any changes to + the application "UI Scale" or geometry (position and size). Closes + [#1155](https://github.com/EDCD/EDMarketConnector/issues/1155). + * We now use UTC-based timestamps in the application's log files. Prior to this change it was the "local time", but without any indication of the - applied timezone. Each line's timestamp has ` UTC` as a suffix now, and we + applied timezone. Each line's timestamp has ` UTC` as a suffix now. We are assuming that your local clock is correct *and* the timezone is set - correctly such that Python's `time.gmtime()` yields UTC times. + correctly, such that Python's `time.gmtime()` yields UTC times. This should make it easier to correlate application logfiles with in-game time and/or third-party service timestamps. @@ -55,10 +59,6 @@ Pre-Release 5.3.0-beta7 process to update the installer configuration which was prone to both user error and neglecting to update it as necessary. -* Running `EDMarketConnector.exe --reset-ui` will now also reset any changes to - the application "UI Scale" or geometry (position and size). Closes - [#1155](https://github.com/EDCD/EDMarketConnector/issues/1155). - * If the application fails to load valid data from the `NavRoute.json` file when processing a Journal `NavRoute` event, it will attempt to retry this operation a number of times as it processes subsequent Journal events. @@ -183,16 +183,14 @@ Plugin Developers date. This should hopefully mean we include, e.g. new ships and modules for loadout exports in a more timely manner. + Developers of third-party plugins should never have been using these files + anyway, so this shouldn't break anything for them. + * We will now include in the Windows installer *all* of the files that `py2exe` places in the build directory. We still do *not* yet include *all* of the Python 'stdlib', so open an issue on GitHub if something you need is missing. -* Due to now using files from the FDevIDs repository the path of, e.g. - `commodity.csv`, in the source has changed. You should never have been - using this application's copy in plugin code anyway as it's not even - packaged in the Windows installer. - --- 5.3.0-beta3 through 5.3.0-beta6 were used internally, no public pre-releases. From 8f9bfe9035e25b1c4f559e41d290cb4badb7ab38 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 5 Feb 2022 09:33:08 +0000 Subject: [PATCH 139/186] Pre-Release 5.3.0-beta7: Built on Ath's machine & maleware false positives --- ChangeLog.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index a752e9b7..caea7f67 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -30,6 +30,18 @@ produce the Windows executables and installer. Pre-Release 5.3.0-beta7 === +**NB: Due to [this issue with GitHub's Actions](https://github.com/actions/virtual-environments/issues/5023) +we were unable to build this version on GitHub. THE INSTALLER FOR THIS +RELEASE WAS BUILT ON A DEVELOPER'S OWN MACHINE. THERE IS NO REASON TO BELIEVE +THAT THIS IN ANY WAY INCREASES THE RISK OF IT ACTUALLY CONTAINING MALWARE.** + +As has sadly become routine now, please read +[our statement about malware false positives](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting#installer-and-or-executables-flagged-as-malicious-viruses) +affecting our installers and/or the files they contain. We are as confident +as we can be, without detailed auditing of python.org's releases and all of +the py2exe source and releases, that there is no malware in the files we make +available. + * We now test and build using Python 3.10.2. We do not *yet* make use of any features specific to Python 3.10 (or 3.9). Let us restate that we absolutely reserve the right to commence doing so. From 33cfd8427494027de24657b7a308beb2aaf50277 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 09:46:35 +0000 Subject: [PATCH 140/186] windows-build: Try checkout out submodules recursively --- .github/workflows/windows-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index f76bf1b6..09dcbf52 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -21,6 +21,7 @@ jobs: with: python-version: "3.10.2" architecture: "x86" + submodules: recursive - name: Install python tools run: | From f93b4b57d1586970031678d40a711699e56ab28e Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 09:48:28 +0000 Subject: [PATCH 141/186] Pre-Release 5.3.0-beta8: appversion and ChangeLog stub --- ChangeLog.md | 8 ++++++++ config/__init__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index caea7f67..5571c852 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -27,6 +27,14 @@ produce the Windows executables and installer. --- +Pre-Release 5.3.0-beta8 +=== + +This release is simply to check we've correctly updated the windows-build +configuration to pull in `FDevIDs` when the build is performed on GitHub. + +--- + Pre-Release 5.3.0-beta7 === diff --git a/config/__init__.py b/config/__init__.py index fa8f1776..5221aa61 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -52,7 +52,7 @@ appcmdname = 'EDMC' # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.3.0-beta7' +_static_appversion = '5.3.0-beta8' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD' From b7cc75723cad6c6f275d820a2f7924b271700653 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 11:08:26 +0000 Subject: [PATCH 142/186] windows-build: move `submodules: recursive` to actions/checkout --- .github/workflows/windows-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 09dcbf52..7830a88f 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -17,11 +17,12 @@ jobs: steps: - uses: actions/checkout@v2.4.0 + submodules: recursive + - uses: actions/setup-python@v2.3.2 with: python-version: "3.10.2" architecture: "x86" - submodules: recursive - name: Install python tools run: | From 49275d46b1571de2c4ae86ee2e09c98e41a0171a Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 11:13:51 +0000 Subject: [PATCH 143/186] windows-build: Prehaps *this* is the syntax for submodule ? --- .github/workflows/windows-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 7830a88f..e4fece7a 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -17,9 +17,9 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - submodules: recursive - - uses: actions/setup-python@v2.3.2 + with: + submodules: true with: python-version: "3.10.2" architecture: "x86" From 4b9d1b4f936e210e85feda97aa5b712dde50970b Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 11:20:09 +0000 Subject: [PATCH 144/186] windows-build: OK, only one `with:`, so try `submodules` first ? If this doesn't work then it's time to try splitting all of this out into separate jobs, rather than steps within a job, with dependencies to ensure it works. --- .github/workflows/windows-build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index e4fece7a..89a086ba 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -20,7 +20,6 @@ jobs: - uses: actions/setup-python@v2.3.2 with: submodules: true - with: python-version: "3.10.2" architecture: "x86" From 9fcb479955c3f45be68431743bdcad39c5499090 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 11:43:21 +0000 Subject: [PATCH 145/186] windows-build: Example of how to use separate jobs Example only because I realised this probably won't preserve, e.g. the checkout, python dependencies, winsparkle unpack etc, across jobs. Even if the jobs are serialised by `needs`. So expect this to get reverted next, it's only for reference. --- .github/workflows/windows-build.yml | 64 +++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 89a086ba..bdfa44c2 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -7,47 +7,79 @@ on: workflow_dispatch: jobs: - test: - name: Build EDMC + checkout_all: + name: Checkout EDMC and submodules runs-on: windows-2019 + steps: + - uses: actions/checkout@v2.4.0 + with: + submodules: true + + python_setup_base: + name: Install python base + runs-on: windows-2019 + needs: checkout_all + + steps: + - uses: actions/setup-python@v2.3.2 + with: + python-version: "3.10.2" + architecture: "x86" + + python_requirements: + name: Install application python requirements + runs-on: windows-2019 + needs: setup_python_base + + steps: + - name: Install python tools + run: | + pip install wheel + pip install -r requirements-dev.txt + + winsparkle_install: + name: Download and unpack WinSparkle files + runs-on: windows-2019 + needs: perform_checkouts + defaults: run: shell: powershell steps: - - uses: actions/checkout@v2.4.0 - - uses: actions/setup-python@v2.3.2 - with: - submodules: true - python-version: "3.10.2" - architecture: "x86" - - - name: Install python tools - run: | - pip install wheel - pip install -r requirements-dev.txt - - name: Download winsparkle run: | Invoke-Webrequest -UseBasicParsing https://github.com/vslavik/winsparkle/releases/download/v0.7.0/WinSparkle-0.7.0.zip -OutFile out.zip Expand-Archive out.zip Move-Item 'out\WinSparkle-0.7.0\Release\*' '.\' + edmc_setup_py2exe: + name: Invoke setup.py py2exe + runs-on: windows-2019 + needs: [ perform_checkouts, setup_python_base, python_requirements, winsparkle_install ] + + steps: - name: Build EDMC run: | python setup.py py2exe + edmc_upload_build: + name: Upload resulting build files + runs-on: windows-2019 + needs: edmc_setup_py2exe + + steps: - name: Upload build files uses: actions/upload-artifact@v2 with: name: Built files path: EDMarketConnector_win*.msi - release: + edmc_release: name: Release new version runs-on: ubuntu-latest - needs: test + needs: edmc_upload_build if: "${{ github.event_name != 'workflow_dispatch' }}" steps: From ba8b1b08f66ed37435c87b7fb5147336a2c7126c Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 11:44:37 +0000 Subject: [PATCH 146/186] Revert "windows-build: Example of how to use separate jobs" This reverts commit 9fcb479955c3f45be68431743bdcad39c5499090. --- .github/workflows/windows-build.yml | 64 ++++++++--------------------- 1 file changed, 16 insertions(+), 48 deletions(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index bdfa44c2..89a086ba 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -7,79 +7,47 @@ on: workflow_dispatch: jobs: - checkout_all: - name: Checkout EDMC and submodules + test: + name: Build EDMC runs-on: windows-2019 - steps: - - uses: actions/checkout@v2.4.0 - with: - submodules: true - - python_setup_base: - name: Install python base - runs-on: windows-2019 - needs: checkout_all - - steps: - - uses: actions/setup-python@v2.3.2 - with: - python-version: "3.10.2" - architecture: "x86" - - python_requirements: - name: Install application python requirements - runs-on: windows-2019 - needs: setup_python_base - - steps: - - name: Install python tools - run: | - pip install wheel - pip install -r requirements-dev.txt - - winsparkle_install: - name: Download and unpack WinSparkle files - runs-on: windows-2019 - needs: perform_checkouts - defaults: run: shell: powershell steps: + - uses: actions/checkout@v2.4.0 + - uses: actions/setup-python@v2.3.2 + with: + submodules: true + python-version: "3.10.2" + architecture: "x86" + + - name: Install python tools + run: | + pip install wheel + pip install -r requirements-dev.txt + - name: Download winsparkle run: | Invoke-Webrequest -UseBasicParsing https://github.com/vslavik/winsparkle/releases/download/v0.7.0/WinSparkle-0.7.0.zip -OutFile out.zip Expand-Archive out.zip Move-Item 'out\WinSparkle-0.7.0\Release\*' '.\' - edmc_setup_py2exe: - name: Invoke setup.py py2exe - runs-on: windows-2019 - needs: [ perform_checkouts, setup_python_base, python_requirements, winsparkle_install ] - - steps: - name: Build EDMC run: | python setup.py py2exe - edmc_upload_build: - name: Upload resulting build files - runs-on: windows-2019 - needs: edmc_setup_py2exe - - steps: - name: Upload build files uses: actions/upload-artifact@v2 with: name: Built files path: EDMarketConnector_win*.msi - edmc_release: + release: name: Release new version runs-on: ubuntu-latest - needs: edmc_upload_build + needs: test if: "${{ github.event_name != 'workflow_dispatch' }}" steps: From 9603eb5561493076e9504621e14d52ccf5207042 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 11:46:24 +0000 Subject: [PATCH 147/186] windows-build: Try the checkout in its own `uses` section --- .github/workflows/windows-build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 89a086ba..9aaea7e6 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -17,9 +17,11 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - - uses: actions/setup-python@v2.3.2 with: submodules: true + + - uses: actions/setup-python@v2.3.2 + with: python-version: "3.10.2" architecture: "x86" From 9f0faad041b69ba4665487b696f0c897af7887f4 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 12:02:47 +0000 Subject: [PATCH 148/186] develop: Bump appversion --- config/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/__init__.py b/config/__init__.py index 5221aa61..f7161cea 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -52,7 +52,7 @@ appcmdname = 'EDMC' # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.3.0-beta8' +_static_appversion = '5.3.0-beta9' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD' From 2bf199f1292b2644425ac9a5c08df07f3b6d6b17 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 14:45:00 +0000 Subject: [PATCH 149/186] github: Add `submodule-updates` workflow It's time to start tracking `master/head` of coriolis-data, and this should trigger a PR making that so. --- .github/workflows/submodule-update.yml | 40 ++++++++++++++++++++++++++ coriolis-data | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/submodule-update.yml diff --git a/.github/workflows/submodule-update.yml b/.github/workflows/submodule-update.yml new file mode 100644 index 00000000..385544cb --- /dev/null +++ b/.github/workflows/submodule-update.yml @@ -0,0 +1,40 @@ +--- +name: Submodule Updates + +on: + # We might want this on a schedule once this is in `main` + push: + branches: [ develop ] + +############### +# Set the Job # +############### +jobs: + check_submodules: + name: Pull Request for updated submodules + runs-on: ubuntu-latest + env: + PARENT_REPOSITORY: 'edcd/edmarketconnector' + CHECKOUT_BRANCH: 'develop' + PR_AGAINST_BRANCH: 'develop' + OWNER: 'edcd' + + steps: + ########################## + # Checkout the code base # + ########################## + - name: Checkout EDMC + uses: actions/checkout@v2 + + #################################### + # Run the action against code base # + #################################### + - name: Check FDevIDs submodule + id: run_action + uses: releasehub-com/github-action-create-pr-parent-submodule@v1 + with: + github_token: ${{ secrets.RELEASE_HUB_SECRET }} + parent_repository: ${{ env.PARENT_REPOSITORY }} + checkout_branch: ${{ env.CHECKOUT_BRANCH}} + pr_against_branch: ${{ env.PR_AGAINST_BRANCH }} + owner: ${{ env.OWNER }} diff --git a/coriolis-data b/coriolis-data index e499f684..5ac8a247 160000 --- a/coriolis-data +++ b/coriolis-data @@ -1 +1 @@ -Subproject commit e499f68469bb4c3d7cecbc91ab67bd4a0bc40993 +Subproject commit 5ac8a2474e931bbf7cae351f7c987bc40af81012 From a77618f76256a023b41ea5ac64126803a8d40001 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 14:46:53 +0000 Subject: [PATCH 150/186] github: Fix org(/repo) case in submodule-updates.yml --- .github/workflows/submodule-update.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/submodule-update.yml b/.github/workflows/submodule-update.yml index 385544cb..071b8239 100644 --- a/.github/workflows/submodule-update.yml +++ b/.github/workflows/submodule-update.yml @@ -14,10 +14,10 @@ jobs: name: Pull Request for updated submodules runs-on: ubuntu-latest env: - PARENT_REPOSITORY: 'edcd/edmarketconnector' + PARENT_REPOSITORY: 'EDCD/EDMarketConnector' CHECKOUT_BRANCH: 'develop' PR_AGAINST_BRANCH: 'develop' - OWNER: 'edcd' + OWNER: 'EDCD' steps: ########################## From 3a362d6a1005c28ba933e6b1eed08b0f5c18acc1 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 15:18:45 +0000 Subject: [PATCH 151/186] github: submodules-check: Use GITHUB_SECRET Perhaps this will be sufficient, if not we'll need to tweak the permissions *for that token* **for this action**. --- .github/workflows/submodule-update.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/submodule-update.yml b/.github/workflows/submodule-update.yml index 071b8239..c757f1f9 100644 --- a/.github/workflows/submodule-update.yml +++ b/.github/workflows/submodule-update.yml @@ -33,7 +33,7 @@ jobs: id: run_action uses: releasehub-com/github-action-create-pr-parent-submodule@v1 with: - github_token: ${{ secrets.RELEASE_HUB_SECRET }} + github_token: ${{ secrets.GITHUB_TOKEN }} parent_repository: ${{ env.PARENT_REPOSITORY }} checkout_branch: ${{ env.CHECKOUT_BRANCH}} pr_against_branch: ${{ env.PR_AGAINST_BRANCH }} From 25123c32caba2dfa104d2e80dd21d0cc9e49d73a Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 7 Feb 2022 15:19:49 +0000 Subject: [PATCH 152/186] updating submodules --- FDevIDs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FDevIDs b/FDevIDs index f4677fff..9ca62008 160000 --- a/FDevIDs +++ b/FDevIDs @@ -1 +1 @@ -Subproject commit f4677fffe618439e4111b97154c4592fe4f360e9 +Subproject commit 9ca62008e93a98dd146bf37cb1e94f2f6c5d2fbf From c93df877bb602b41d3dd7efa5e9b8f9fca13ce5e Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 15:25:16 +0000 Subject: [PATCH 153/186] github: submodules-update: Actually it's all submodules, not only FDevIDs --- .github/workflows/submodule-update.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/submodule-update.yml b/.github/workflows/submodule-update.yml index c757f1f9..3db7ff60 100644 --- a/.github/workflows/submodule-update.yml +++ b/.github/workflows/submodule-update.yml @@ -29,7 +29,7 @@ jobs: #################################### # Run the action against code base # #################################### - - name: Check FDevIDs submodule + - name: Check EDMC submodules id: run_action uses: releasehub-com/github-action-create-pr-parent-submodule@v1 with: From 6963a24728ba3aed0b02eedb5c2e2c332de9679f Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 16:09:45 +0000 Subject: [PATCH 154/186] github: submodules-update: Port in all the releasehub code & tweak We want to *check* if there are any changes and only make a branch and add commits if so. The upstream code has no option for this, so use it as a starting point instead. Specifically this is based on: https://github.com/releasehub-com/github-action-create-pr-parent-submodule/blob/main/action.yml aff9d0978a9bbcbc2961d621d5b108c4b46db5e7 * We need 'success' from the *step* to be when there ARE changes, and that is in the special github output. * In order for the whole job *not* to fail in the 'step check_for_changes says it failed' case we need it to have `continue-on-error: true`. --- .github/workflows/submodule-update.yml | 82 +++++++++++++++++++------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/.github/workflows/submodule-update.yml b/.github/workflows/submodule-update.yml index 3db7ff60..ecd14e27 100644 --- a/.github/workflows/submodule-update.yml +++ b/.github/workflows/submodule-update.yml @@ -6,9 +6,6 @@ on: push: branches: [ develop ] -############### -# Set the Job # -############### jobs: check_submodules: name: Pull Request for updated submodules @@ -20,21 +17,66 @@ jobs: OWNER: 'EDCD' steps: - ########################## - # Checkout the code base # - ########################## - - name: Checkout EDMC - uses: actions/checkout@v2 - - #################################### - # Run the action against code base # - #################################### - - name: Check EDMC submodules - id: run_action - uses: releasehub-com/github-action-create-pr-parent-submodule@v1 + - uses: actions/checkout@v2.4.0 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - parent_repository: ${{ env.PARENT_REPOSITORY }} - checkout_branch: ${{ env.CHECKOUT_BRANCH}} - pr_against_branch: ${{ env.PR_AGAINST_BRANCH }} - owner: ${{ env.OWNER }} + submodules: true + + - name: Update submodules + shell: bash + run: | + git config user.name github-actions + git config user.email github-actions@github.com + git submodule update --remote + + - name: Check for changes + id: check_for_changes + continue-on-error: true + run: | + changes=$(git status --porcelain) + if [ -n "${changes}" ]; + then + echo '::set-output changes=true' + fi + echo '::set-output changes=false' + exit 0 + + - name: Create submodules changes branch + if: steps.check_for_changes.outputs.changes == 'true' + run: | + git checkout -b $GITHUB_RUN_ID + git commit -am "updating submodules" + git push --set-upstream origin $GITHUB_RUN_ID + + - name: Create pull request against target branch + if: steps.check_for_changes.outputs.changes == 'true' + uses: actions/github-script@v5 + with: + github-token: ${{ inputs.github_token }} + script: | + await github.rest.pulls.create({ + owner: '${{ inputs.owner }}', + repo: '${{ inputs.parent_repository }}'.split('/')[1].trim(), + head: process.env.GITHUB_RUN_ID, + base: '${{ inputs.pr_against_branch }}', + title: `[Auto-generated] Submodule Updates ${process.env.GITHUB_RUN_ID}`, + body: `[Auto-generated] Submodule Updates ${process.env.GITHUB_RUN_ID}`, + }); + + - name: Add labels + if: steps.check_for_changes.outputs.changes == 'true' + uses: actions/github-script@v5 + with: + github-token: ${{ inputs.github_token }} + script: | + const res = await github.rest.issues.listForRepo({ + owner: '${{ inputs.owner }}', + repo: '${{ inputs.parent_repository }}'.split('/')[1].trim(), + }); + const pr = res.data.filter(i => i.title.includes(process.env.GITHUB_RUN_ID)); + const prNumber = pr[0].number; + await github.rest.issues.addLabels({ + issue_number: prNumber, + owner: '${{ inputs.owner }}', + repo: '${{ inputs.parent_repository }}'.split('/')[1].trim(), + labels: ['${{ inputs.label }}'] + }); From 0cb9f10bff3631b7bf60c2c8d2d572bd6dd299f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 17:38:38 +0000 Subject: [PATCH 155/186] build(deps): bump semantic-version from 2.8.5 to 2.9.0 Bumps [semantic-version](https://github.com/rbarrois/python-semanticversion) from 2.8.5 to 2.9.0. - [Release notes](https://github.com/rbarrois/python-semanticversion/releases) - [Changelog](https://github.com/rbarrois/python-semanticversion/blob/master/ChangeLog) - [Commits](https://github.com/rbarrois/python-semanticversion/compare/2.8.5...2.9.0) --- updated-dependencies: - dependency-name: semantic-version dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 479425c9..402c5a86 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ watchdog==2.1.6 infi.systray==0.1.12; sys_platform == 'win32' # argh==0.26.2 watchdog dep # pyyaml==5.3.1 watchdog dep -semantic-version==2.8.5 +semantic-version==2.9.0 # Base requirement for MacOS pyobjc; sys_platform == 'darwin' From 5e475023849bc3bada495bc42f7c61c9b9871da1 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 7 Feb 2022 20:41:47 +0000 Subject: [PATCH 156/186] pre-commit: Updated versions & include `check-yaml` now --- .pre-commit-config.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a6effa3b..bc2d8a73 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,12 +2,13 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: 'v3.4.0' + rev: 'v4.1.0' hooks: - id: check-merge-conflict - id: debug-statements - id: end-of-file-fixer - id: mixed-line-ending + - id: check-yaml #- repo: https://github.com/pre-commit/mirrors-autopep8 # rev: '' @@ -30,7 +31,7 @@ repos: types: [ python ] - repo: https://github.com/pre-commit/pygrep-hooks - rev: 'v1.8.0' + rev: 'v1.9.0' hooks: - id: python-no-eval - id: python-no-log-warn @@ -40,7 +41,7 @@ repos: # mypy - static type checking # mypy --follow-imports skip - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.812' + rev: 'v0.931' hooks: - id: mypy args: [ "--follow-imports", "skip", "--ignore-missing-imports", "--scripts-are-modules" ] @@ -59,7 +60,7 @@ repos: # safety.exe check -r requirements.txt - repo: https://github.com/Lucas-C/pre-commit-hooks-safety - rev: 'v1.2.1' + rev: 'v1.2.3' hooks: - id: python-safety-dependencies-check entry: safety From 22f90e23aac7203f9a6a7b9349a163cd68fbc5f7 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 8 Feb 2022 13:30:10 +0000 Subject: [PATCH 157/186] Inara: Trigger off 'Progress' for sending setCommanderRankPilot Doing so from 'Rank' means the 'Progress' event hadn't happened yet, so of course we only ever sent all zeroes for progress. NB: Journal-v32 doc makes *no guarantee* about the order of the events. Is it worth trying to be paranoid about that ? For a 100% new player everything would be "all zeroes", so we would likely need to tweak things to store `None` as default values, and check for such. --- plugins/inara.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inara.py b/plugins/inara.py index 2446693a..e0e0dade 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -519,7 +519,7 @@ def journal_entry( # noqa: C901, CCR001 new_add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout) # Login-time Ranks - elif event_name == 'Rank': + elif event_name == 'Progress': # Send rank info to Inara on startup new_add_event( 'setCommanderRankPilot', From 30e9abb870658f7821e9d7c671cad4f00f033dec Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 8 Feb 2022 13:34:03 +0000 Subject: [PATCH 158/186] Inara: Change the comment for why we're using Progress, not Ranks --- plugins/inara.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/inara.py b/plugins/inara.py index e0e0dade..9388d5da 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -518,7 +518,8 @@ def journal_entry( # noqa: C901, CCR001 this.loadout = make_loadout(state) new_add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout) - # Login-time Ranks + # Trigger off the "only observed as being after Ranks" event so that + # we have both current Ranks *and* current Progress within them. elif event_name == 'Progress': # Send rank info to Inara on startup new_add_event( From dec51c844a1cc07e8cd425e55f3026e95f769d16 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 8 Feb 2022 14:42:31 +0000 Subject: [PATCH 159/186] Pre-Release 5.3.0-beta9: ChangeLog * appversion was already -beta9 in `develop` --- ChangeLog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 5571c852..dc1b4c17 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -27,6 +27,13 @@ produce the Windows executables and installer. --- +Pre-Release 5.3.0-beta9 +=== + +* Inara: Fix for always sending a Rank Progress of 0%. + +--- + Pre-Release 5.3.0-beta8 === From e1ef4f38f885d241cf625cbfb2fd6854dffb6401 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 8 Feb 2022 16:28:50 +0000 Subject: [PATCH 160/186] requirements.txt: Revert semantic_version to 2.8.5 Ref: https://github.com/EDCD/EDMarketConnector/issues/1447 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 402c5a86..479425c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ watchdog==2.1.6 infi.systray==0.1.12; sys_platform == 'win32' # argh==0.26.2 watchdog dep # pyyaml==5.3.1 watchdog dep -semantic-version==2.9.0 +semantic-version==2.8.5 # Base requirement for MacOS pyobjc; sys_platform == 'darwin' From b6b6f995e2b659a433b77ba51da2dfcc59ca65af Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 8 Feb 2022 16:31:31 +0000 Subject: [PATCH 161/186] Pre-Release 5.3.0-beta10: appversion and changelog --- ChangeLog.md | 9 +++++++++ config/__init__.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index dc1b4c17..74766105 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -27,6 +27,15 @@ produce the Windows executables and installer. --- +Pre-Release 5.3.0-beta10 +=== + +* Revert `semantic_version` python module to v2.8.5, as v2.9.0 doesn't get + correctly included by py2exe. This leads to application crash at startup + when using the Windows .exe. + +--- + Pre-Release 5.3.0-beta9 === diff --git a/config/__init__.py b/config/__init__.py index f7161cea..6e97602d 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -52,7 +52,7 @@ appcmdname = 'EDMC' # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.3.0-beta9' +_static_appversion = '5.3.0-beta10' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD' From d59dc18b1b8cdf29227365d1984ad89abcacdd20 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 9 Feb 2022 13:50:42 +0000 Subject: [PATCH 162/186] eddn: make the exclusive nature of 'own schema' events explicit --- plugins/eddn.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index a9f25d57..3ffbbdf7 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1391,16 +1391,16 @@ def journal_entry( # noqa: C901, CCR001 if entry['event'].lower() == 'fssdiscoveryscan': return this.eddn.export_journal_fssdiscoveryscan(cmdr, system, is_beta, entry) - if entry['event'].lower() == 'navbeaconscan': + elif entry['event'].lower() == 'navbeaconscan': return this.eddn.export_journal_navbeaconscan(cmdr, system, is_beta, entry) - if entry['event'].lower() == 'codexentry': + elif entry['event'].lower() == 'codexentry': return this.eddn.export_journal_codexentry(cmdr, is_beta, entry) - if entry['event'].lower() == 'scanbarycentre': + elif entry['event'].lower() == 'scanbarycentre': return this.eddn.export_journal_scanbarycentre(cmdr, is_beta, entry) - if entry['event'].lower() == 'navroute': + elif entry['event'].lower() == 'navroute': return this.eddn.export_journal_navroute(cmdr, is_beta, entry) # Send journal schema events to EDDN, but not when on a crew From 91c36a5b1ab005d542b8ea2da80ecdd739265647 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 9 Feb 2022 14:08:36 +0000 Subject: [PATCH 163/186] eddn: Change to the ".lower() Journal event name once" paradigm Also, whilst the *current* code has the 'augment files' handled last in then if/else tree we shouldn't assume that will always be the case. So, be paranoid and use different variables for the augment-loaded entry and its .lower event_name. 'cos you just know one of us will trip up on it later if there's ever some 'finally' code after that conditional tree. --- plugins/eddn.py | 49 +++++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 3ffbbdf7..9efe2c6a 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1331,6 +1331,7 @@ def journal_entry( # noqa: C901, CCR001 return None entry = new_data + event_name = entry['event'].lower() this.on_foot = state['OnFoot'] @@ -1340,8 +1341,8 @@ def journal_entry( # noqa: C901, CCR001 this.odyssey = entry['odyssey'] = state['Odyssey'] # Track location - if entry['event'] in ('Location', 'FSDJump', 'Docked', 'CarrierJump'): - if entry['event'] in ('Location', 'CarrierJump'): + if event_name in ('location', 'fsdjump', 'docked', 'carrierjump'): + if event_name in ('location', 'carrierjump'): if entry.get('BodyType') == 'Planet': this.body_name = entry.get('Body') this.body_id = entry.get('BodyID') @@ -1349,7 +1350,7 @@ def journal_entry( # noqa: C901, CCR001 else: this.body_name = None - elif entry['event'] == 'FSDJump': + elif event_name == 'fsdjump': this.body_name = None this.body_id = None @@ -1368,11 +1369,11 @@ def journal_entry( # noqa: C901, CCR001 # Yes, explicitly state `None` here, so it's crystal clear. this.systemaddress = entry.get('SystemAddress', None) # type: ignore - elif entry['event'] == 'ApproachBody': + elif event_name == 'approachbody': this.body_name = entry['Body'] this.body_id = entry.get('BodyID') - elif entry['event'] == 'LeaveBody': + elif event_name == 'leavebody': # NB: **NOT** SupercruiseEntry, because we won't get a fresh # ApproachBody if we don't leave Orbital Cruise and land again. # *This* is triggered when you go above Orbital Cruise altitude. @@ -1380,7 +1381,7 @@ def journal_entry( # noqa: C901, CCR001 this.body_name = None this.body_id = None - elif entry['event'] == 'Music': + elif event_name == 'music': if entry['MusicTrack'] == 'MainMenu': this.body_name = None this.body_id = None @@ -1388,24 +1389,25 @@ def journal_entry( # noqa: C901, CCR001 # Events with their own EDDN schema if config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain']: - if entry['event'].lower() == 'fssdiscoveryscan': + + if event_name == 'fssdiscoveryscan': return this.eddn.export_journal_fssdiscoveryscan(cmdr, system, is_beta, entry) - elif entry['event'].lower() == 'navbeaconscan': + elif event_name == 'navbeaconscan': return this.eddn.export_journal_navbeaconscan(cmdr, system, is_beta, entry) - elif entry['event'].lower() == 'codexentry': + elif event_name == 'codexentry': return this.eddn.export_journal_codexentry(cmdr, is_beta, entry) - elif entry['event'].lower() == 'scanbarycentre': + elif event_name == 'scanbarycentre': return this.eddn.export_journal_scanbarycentre(cmdr, is_beta, entry) - elif entry['event'].lower() == 'navroute': + elif event_name == 'navroute': return this.eddn.export_journal_navroute(cmdr, is_beta, entry) # Send journal schema events to EDDN, but not when on a crew if (config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain'] and - (entry['event'] in ('Location', 'FSDJump', 'Docked', 'Scan', 'SAASignalsFound', 'CarrierJump')) and + (event_name in ('location', 'fsdjump', 'docked', 'scan', 'saasignalsfound', 'carrierjump')) and ('StarPos' in entry or this.coordinates)): # strip out properties disallowed by the schema @@ -1435,7 +1437,7 @@ def journal_entry( # noqa: C901, CCR001 ] # add planet to Docked event for planetary stations if known - if entry['event'] == 'Docked' and this.body_name: + if event_name == 'docked' and this.body_name: entry['Body'] = this.body_name entry['BodyType'] = 'Planet' @@ -1479,7 +1481,7 @@ def journal_entry( # noqa: C901, CCR001 return str(e) elif (config.get_int('output') & config.OUT_MKT_EDDN and not state['Captain'] and - entry['event'] in ('Market', 'Outfitting', 'Shipyard')): + event_name in ('market', 'outfitting', 'shipyard')): # Market.json, Outfitting.json or Shipyard.json to process try: @@ -1494,16 +1496,19 @@ def journal_entry( # noqa: C901, CCR001 path = pathlib.Path(journaldir) / f'{entry["event"]}.json' with path.open('rb') as f: - entry = json.load(f) - entry['odyssey'] = this.odyssey - if entry['event'] == 'Market': - this.eddn.export_journal_commodities(cmdr, is_beta, entry) + # Don't assume we can definitely stomp entry & event_name here + entry_augment = json.load(f) + event_name_augment = entry_augment['event'].lower() + entry_augment['odyssey'] = this.odyssey - elif entry['event'] == 'Outfitting': - this.eddn.export_journal_outfitting(cmdr, is_beta, entry) + if event_name_augment == 'market': + this.eddn.export_journal_commodities(cmdr, is_beta, entry_augment) - elif entry['event'] == 'Shipyard': - this.eddn.export_journal_shipyard(cmdr, is_beta, entry) + elif event_name_augment == 'outfitting': + this.eddn.export_journal_outfitting(cmdr, is_beta, entry_augment) + + elif event_name_augment == 'shipyard': + this.eddn.export_journal_shipyard(cmdr, is_beta, entry_augment) except requests.exceptions.RequestException as e: logger.debug(f'Failed exporting {entry["event"]}', exc_info=e) From 4f0ecb0289c7b119647b7e1f9a2c3d27afa1b470 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 9 Feb 2022 14:48:56 +0000 Subject: [PATCH 164/186] .pre-commit-config: .mypy.ini sets these options --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bc2d8a73..e9b0cc92 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,7 @@ repos: rev: 'v0.931' hooks: - id: mypy - args: [ "--follow-imports", "skip", "--ignore-missing-imports", "--scripts-are-modules" ] + # args: [ "--follow-imports", "skip", "--ignore-missing-imports", "--scripts-are-modules" ] ### # pydocstyle.exe ### - repo: https://github.com/FalconSocial/pre-commit-mirrors-pep257 From 3a0dbb906a7f39eba572ffb567f1e5ab8b96a791 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 9 Feb 2022 15:01:56 +0000 Subject: [PATCH 165/186] eddn: export_journal_approachsettlement() implementation --- plugins/eddn.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index 9efe2c6a..f2185156 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1090,6 +1090,46 @@ class EDDN: this.eddn.export_journal_entry(cmdr, entry, msg) return None + def export_journal_approachsettlement( + self, cmdr: str, is_beta: bool, entry: MutableMapping[str, Any] + ) -> Optional[str]: + """ + Send an ApproachSettlement to EDDN on the correct schema. + + :param cmdr: the commander under which this upload is made + :param is_beta: whether or not we are in beta mode + :param entry: the journal entry to send + """ + # { + # "BodyID": 32, + # "BodyName": "Ix 5 a a", + # "Latitude": 17.090912, + # "Longitude": 160.236679, + # "MarketID": 3915738368, + # "Name": "Arnold Defence Base", + # "SystemAddress": 2381282543963, + # "event": "ApproachSettlement", + # "timestamp": "2021-10-14T12:37:54Z" + # } + ####################################################################### + # Augmentations + ####################################################################### + # In this case should add StarSystem and StarPos + ret = this.eddn.entry_augment_system_data(entry, entry['StarSystem']) + if isinstance(ret, str): + return ret + + entry = ret + ####################################################################### + + msg = { + '$schemaRef': f'https://eddn.edcd.io/schemas/approachsettlement/1{"/test" if is_beta else ""}', + 'message': entry + } + + this.eddn.export_journal_entry(cmdr, entry, msg) + return None + def canonicalise(self, item: str) -> str: """ Canonicalise the given commodity name. @@ -1405,6 +1445,9 @@ def journal_entry( # noqa: C901, CCR001 elif event_name == 'navroute': return this.eddn.export_journal_navroute(cmdr, is_beta, entry) + elif event_name == 'approachsettlement': + return this.eddn.export_journal_approachsettlement(cmdr, is_beta, entry) + # Send journal schema events to EDDN, but not when on a crew if (config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain'] and (event_name in ('location', 'fsdjump', 'docked', 'scan', 'saasignalsfound', 'carrierjump')) and From f1ba8ee5e1561c2b1d17546ca40d0fa937c37f92 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 9 Feb 2022 16:07:16 +0000 Subject: [PATCH 166/186] eddn: Fix up approachsettlement & adjust other such function signatures 1. We need StarPos (as well as StarSystem) 2. Adding more state tracking in this plugin is misguided. 3. So added it in monitor instead, putting *copies* of data in the monitor.state dictionary. 4. So we reference those, but only available in journal_entry() itself, else we'd need to pass the whole of `state` in. 5. So instead pass in the bits of `state` only when we need them. --- monitor.py | 14 +++++++++-- plugins/eddn.py | 63 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/monitor.py b/monitor.py index a90397e4..9c049429 100644 --- a/monitor.py +++ b/monitor.py @@ -180,6 +180,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below 'SuitLoadouts': {}, 'Taxi': None, # True whenever we are _in_ a taxi. ie, this is reset on Disembark etc. 'Dropship': None, # Best effort as to whether or not the above taxi is a dropship. + 'StarSystem': None, # Best effort name of current system. + 'StarPos': None, # Best effort current system's galaxy position. 'Body': None, 'BodyType': None, @@ -801,13 +803,21 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.state['BodyType'] = None if 'StarPos' in entry: - self.coordinates = tuple(entry['StarPos']) # type: ignore + # Plugins need this as well, so copy in state + self.state['StarPos'] = self.coordinates = tuple(entry['StarPos']) # type: ignore self.systemaddress = entry.get('SystemAddress') self.systempopulation = entry.get('Population') - self.system = 'CQC' if entry['StarSystem'] == 'ProvingGround' else entry['StarSystem'] + if entry['StarSystem'] == 'ProvingGround': + to_set = 'CQC' + + else: + to_set = entry['StarSystem'] + + # EDDN plugin needs this in `state` + self.state['StarSystem'] = self.system = to_set self.station = entry.get('StationName') # May be None # If on foot in-station 'Docked' is false, but we have a diff --git a/plugins/eddn.py b/plugins/eddn.py index f2185156..a630cfd8 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -789,14 +789,15 @@ class EDDN: def entry_augment_system_data( self, entry: MutableMapping[str, Any], - system_name: str + system_name: str, + system_coordinates: list ) -> Union[str, MutableMapping[str, Any]]: """ Augment a journal entry with necessary system data. :param entry: The journal entry to be augmented. :param system_name: Name of current star system. - :param systemname_field_name: Name of journal key for system name. + :param system_coordinates: Coordinates of current star system. :return: The augmented version of entry. """ # If 'SystemName' or 'System' is there, it's directly from a journal event. @@ -818,21 +819,29 @@ class EDDN: entry['SystemAddress'] = this.systemaddress if 'StarPos' not in entry: - if not this.coordinates: - logger.warning("this.coordinates is None, can't add StarPos") - return "this.coordinates is None, can't add StarPos" + # Prefer the passed-in, probably monitor.state version + if system_coordinates is not None: + entry['StarPos'] = system_coordinates - entry['StarPos'] = list(this.coordinates) + # TODO: Deprecate in-plugin tracking + elif this.coordinates is not None: + entry['StarPos'] = list(this.coordinates) + + else: + logger.warning("Neither this_coordinates or this.coordinates set, can't add StarPos") + return 'No source for adding StarPos to approachsettlement/1 !' return entry def export_journal_fssdiscoveryscan( - self, cmdr: str, system: str, is_beta: bool, entry: Mapping[str, Any] + self, cmdr: str, system: str, system_starpos: list, is_beta: bool, entry: Mapping[str, Any] ) -> Optional[str]: """ Send an FSSDiscoveryScan to EDDN on the correct schema. :param cmdr: the commander under which this upload is made + :param system: Name of current star system + :param system_starpos: Coordinates of current star system :param is_beta: whether or not we are in beta mode :param entry: the journal entry to send """ @@ -845,7 +854,7 @@ class EDDN: ####################################################################### # Augmentations ####################################################################### - ret = this.eddn.entry_augment_system_data(entry, system) + ret = this.eddn.entry_augment_system_data(entry, system, system_starpos) if isinstance(ret, str): return ret @@ -861,13 +870,14 @@ class EDDN: return None def export_journal_navbeaconscan( - self, cmdr: str, system_name: str, is_beta: bool, entry: Mapping[str, Any] + self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: Mapping[str, Any] ) -> Optional[str]: """ Send an NavBeaconScan to EDDN on the correct schema. :param cmdr: the commander under which this upload is made :param system_name: Name of the current system. + :param system_starpos: Coordinates of current star system :param is_beta: whether or not we are in beta mode :param entry: the journal entry to send """ @@ -880,7 +890,7 @@ class EDDN: ####################################################################### # Augmentations ####################################################################### - ret = this.eddn.entry_augment_system_data(entry, system_name) + ret = this.eddn.entry_augment_system_data(entry, system_name, system_starpos) if isinstance(ret, str): return ret @@ -896,12 +906,13 @@ class EDDN: return None def export_journal_codexentry( # noqa: CCR001 - self, cmdr: str, is_beta: bool, entry: MutableMapping[str, Any] + self, cmdr: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any] ) -> Optional[str]: """ Send a CodexEntry to EDDN on the correct schema. :param cmdr: the commander under which this upload is made + :param system_starpos: Coordinates of current star system :param is_beta: whether or not we are in beta mode :param entry: the journal entry to send """ @@ -936,7 +947,7 @@ class EDDN: # Augmentations ####################################################################### # General 'system' augmentations - ret = this.eddn.entry_augment_system_data(entry, entry['System']) + ret = this.eddn.entry_augment_system_data(entry, entry['System'], system_starpos) if isinstance(ret, str): return ret @@ -976,12 +987,13 @@ class EDDN: return None def export_journal_scanbarycentre( - self, cmdr: str, is_beta: bool, entry: Mapping[str, Any] + self, cmdr: str, system_starpos: list, is_beta: bool, entry: Mapping[str, Any] ) -> Optional[str]: """ Send a ScanBaryCentre to EDDN on the correct schema. :param cmdr: the commander under which this upload is made + :param system_starpos: Coordinates of current star system :param is_beta: whether or not we are in beta mode :param entry: the journal entry to send """ @@ -1007,7 +1019,7 @@ class EDDN: ####################################################################### # Augmentations ####################################################################### - ret = this.eddn.entry_augment_system_data(entry, entry['StarSystem']) + ret = this.eddn.entry_augment_system_data(entry, entry['StarSystem'], system_starpos) if isinstance(ret, str): return ret @@ -1029,6 +1041,7 @@ class EDDN: Send a NavRoute to EDDN on the correct schema. :param cmdr: the commander under which this upload is made + :param system_starpos: Coordinates of current star system :param is_beta: whether or not we are in beta mode :param entry: the journal entry to send """ @@ -1091,12 +1104,14 @@ class EDDN: return None def export_journal_approachsettlement( - self, cmdr: str, is_beta: bool, entry: MutableMapping[str, Any] + self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any] ) -> Optional[str]: """ Send an ApproachSettlement to EDDN on the correct schema. :param cmdr: the commander under which this upload is made + :param system_name: Name of current star system + :param system_starpos: Coordinates of current star system :param is_beta: whether or not we are in beta mode :param entry: the journal entry to send """ @@ -1115,7 +1130,7 @@ class EDDN: # Augmentations ####################################################################### # In this case should add StarSystem and StarPos - ret = this.eddn.entry_augment_system_data(entry, entry['StarSystem']) + ret = this.eddn.entry_augment_system_data(entry, system_name, system_starpos) if isinstance(ret, str): return ret @@ -1431,22 +1446,28 @@ def journal_entry( # noqa: C901, CCR001 if config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain']: if event_name == 'fssdiscoveryscan': - return this.eddn.export_journal_fssdiscoveryscan(cmdr, system, is_beta, entry) + return this.eddn.export_journal_fssdiscoveryscan(cmdr, system, state['StarPos'], is_beta, entry) elif event_name == 'navbeaconscan': - return this.eddn.export_journal_navbeaconscan(cmdr, system, is_beta, entry) + return this.eddn.export_journal_navbeaconscan(cmdr, system, state['StarPos'], is_beta, entry) elif event_name == 'codexentry': - return this.eddn.export_journal_codexentry(cmdr, is_beta, entry) + return this.eddn.export_journal_codexentry(cmdr, state['StarPos'], is_beta, entry) elif event_name == 'scanbarycentre': - return this.eddn.export_journal_scanbarycentre(cmdr, is_beta, entry) + return this.eddn.export_journal_scanbarycentre(cmdr, state['StarPos'], is_beta, entry) elif event_name == 'navroute': return this.eddn.export_journal_navroute(cmdr, is_beta, entry) elif event_name == 'approachsettlement': - return this.eddn.export_journal_approachsettlement(cmdr, is_beta, entry) + return this.eddn.export_journal_approachsettlement( + cmdr, + state['StarSystem'], + state['StarPos'], + is_beta, + entry + ) # Send journal schema events to EDDN, but not when on a crew if (config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain'] and From 6f0095e5fe9ae8d1b4e33901e0f5376e2a114e52 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 9 Feb 2022 16:14:15 +0000 Subject: [PATCH 167/186] eddn: Slightly rationalise passed StarSystem * Call it system_name in `export_journal_fssdiscoveryscan()`, as `system` could be amigbuous. * Might as well use `system` passed in to `journal_entry()` when calling `export_journal_approachsettlement()`. --- plugins/eddn.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index a630cfd8..8da1954f 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -834,13 +834,13 @@ class EDDN: return entry def export_journal_fssdiscoveryscan( - self, cmdr: str, system: str, system_starpos: list, is_beta: bool, entry: Mapping[str, Any] + self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: Mapping[str, Any] ) -> Optional[str]: """ Send an FSSDiscoveryScan to EDDN on the correct schema. :param cmdr: the commander under which this upload is made - :param system: Name of current star system + :param system_name: Name of current star system :param system_starpos: Coordinates of current star system :param is_beta: whether or not we are in beta mode :param entry: the journal entry to send @@ -854,7 +854,7 @@ class EDDN: ####################################################################### # Augmentations ####################################################################### - ret = this.eddn.entry_augment_system_data(entry, system, system_starpos) + ret = this.eddn.entry_augment_system_data(entry, system_name, system_starpos) if isinstance(ret, str): return ret @@ -1463,7 +1463,7 @@ def journal_entry( # noqa: C901, CCR001 elif event_name == 'approachsettlement': return this.eddn.export_journal_approachsettlement( cmdr, - state['StarSystem'], + system, state['StarPos'], is_beta, entry From 2d0fd92ebf4d20ab43d8e319604ba75805ab7bb8 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 9 Feb 2022 16:36:22 +0000 Subject: [PATCH 168/186] develop: Bump appversion to 5.3.0-beta11 --- config/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/__init__.py b/config/__init__.py index 6e97602d..79f88393 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -52,7 +52,7 @@ appcmdname = 'EDMC' # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.3.0-beta10' +_static_appversion = '5.3.0-beta11' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD' From 5831426eed4f6a3590eb137be8c4d2fd282fa4d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Feb 2022 17:21:15 +0000 Subject: [PATCH 169/186] build(deps-dev): bump types-requests from 2.27.8 to 2.27.9 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.8 to 2.27.9. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ba5a4cd2..e98c5953 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,7 +18,7 @@ flake8-use-fstring==1.3 mypy==0.931 pep8-naming==0.12.1 safety==1.10.3 -types-requests==2.27.8 +types-requests==2.27.9 # Code formatting tools autopep8==1.6.0 From 46e914fcec1284cb05096f3984e1186732626540 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 11 Feb 2022 11:57:05 +0000 Subject: [PATCH 170/186] monitor: Revert state['StarSystem'] addition, it's not needed. We pass `monitor.system` in to `journal_entry()` anyway, so it doesn't need to be separately in the passed `state`. --- monitor.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/monitor.py b/monitor.py index 9c049429..b271b0c3 100644 --- a/monitor.py +++ b/monitor.py @@ -180,7 +180,6 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below 'SuitLoadouts': {}, 'Taxi': None, # True whenever we are _in_ a taxi. ie, this is reset on Disembark etc. 'Dropship': None, # Best effort as to whether or not the above taxi is a dropship. - 'StarSystem': None, # Best effort name of current system. 'StarPos': None, # Best effort current system's galaxy position. 'Body': None, 'BodyType': None, @@ -811,13 +810,10 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below self.systempopulation = entry.get('Population') if entry['StarSystem'] == 'ProvingGround': - to_set = 'CQC' + self.system = 'CQC' else: - to_set = entry['StarSystem'] - - # EDDN plugin needs this in `state` - self.state['StarSystem'] = self.system = to_set + self.system = entry['StarSystem'] self.station = entry.get('StationName') # May be None # If on foot in-station 'Docked' is false, but we have a From dd6c6fbc3638b79c5183a7a156f0d8a726420926 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 11 Feb 2022 12:20:06 +0000 Subject: [PATCH 171/186] Inara: Do not update credits from CAPI data CAPI only has the Cmdr wallet value (and loan if applicable), without anything for 'total assets'. If we send Inara setCommanderCredits without `commanderAssets` then Inara makes a bad judgement on it and sets an incorrect total assets value. Also: > Do NOT set credits/assets unless you are absolutely sure they are correct. The journals currently doesn't contain crew wage cuts, so credit gains are very probably off for most of the players. Also, please, do not send each minor credits change, as it will spam player's credits log with unusable data and they won't be most likely very happy about it. It may be good to set credits just on the session start, session end and on the big changes or in hourly intervals. So, let's just not. --- plugins/inara.py | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/plugins/inara.py b/plugins/inara.py index 9388d5da..23387c3a 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -1384,26 +1384,8 @@ def cmdr_data(data: CAPIData, is_beta): # noqa: CCR001 this.station_link.update_idletasks() if config.get_int('inara_out') and not is_beta and not this.multicrew and credentials(this.cmdr): - if ( - abs(this.last_credits - data['commander']['credits']) >= - min(abs(this.last_credits * CREDITS_DELTA_MIN_FRACTION), CREDITS_DELTA_MIN_ABSOLUTE) - ): - this.filter_events( - Credentials(this.cmdr, this.FID, str(credentials(this.cmdr))), - lambda e: e.name != 'setCommanderCredits' - ) - - # this.events = [x for x in this.events if x['eventName'] != 'setCommanderCredits'] # Remove any unsent - new_add_event( - 'setCommanderCredits', - data['timestamp'], - { - 'commanderCredits': data['commander']['credits'], - 'commanderLoan': data['commander'].get('debt', 0), - } - ) - - this.last_credits = int(data['commander']['credits']) + # Only here to ensure the conditional is correct for future additions + pass def make_loadout(state: Dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR001 From 67c2077962a54a4ccc014a8b8a9ed113050b765d Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 11 Feb 2022 16:15:24 +0000 Subject: [PATCH 172/186] pre-commit: pre-commit runs mypy *in its own venv* As such it doesn't have access to any extra modules you installed for your program, or manually running `mypy `. So, leverage `additional_dependencies` to list anything that mypy ends up complaining "that's not installed". Ref: plugins\eddn.py:40: error: Library stubs not installed for "requests" (or incompatible with Python 3.10) plugins\eddn.py:40: note: Hint: "python3 -m pip install types-requests" plugins\eddn.py:40: note: (or run "mypy --install-types" to install all missing stub packages) plugins\eddn.py:40: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports Found 1 error in 1 file (checked 1 source file) --- .pre-commit-config.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e9b0cc92..19b21930 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,11 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: +# This can be used to check how pre-commit is being run +# - repo: meta +# hooks: +# - id: identity + - repo: https://github.com/pre-commit/pre-commit-hooks rev: 'v4.1.0' hooks: @@ -44,6 +49,9 @@ repos: rev: 'v0.931' hooks: - id: mypy + # verbose: true + # log_file: 'pre-commit_mypy.log' + additional_dependencies: [ types-requests ] # args: [ "--follow-imports", "skip", "--ignore-missing-imports", "--scripts-are-modules" ] ### # pydocstyle.exe From a93ee14c0546127a6ade7c252bb5b3521477f419 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 11 Feb 2022 16:17:03 +0000 Subject: [PATCH 173/186] eddn: Implement schema fssallbodiesfound/1 --- plugins/eddn.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index 8da1954f..1c3fdc43 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1145,6 +1145,44 @@ class EDDN: this.eddn.export_journal_entry(cmdr, entry, msg) return None + def export_journal_fssallbodiesfound( + self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any] + ) -> Optional[str]: + """ + Send an FSSAllBodiesFound message to EDDN on the correct schema. + + :param cmdr: the commander under which this upload is made + :param system_name: Name of current star system + :param system_starpos: Coordinates of current star system + :param is_beta: whether or not we are in beta mode + :param entry: the journal entry to send + """ + # { + # "Count": 3, + # "SystemAddress": 9466778822057, + # "SystemName": "LP 704-74", + # "event": "FSSAllBodiesFound", + # "timestamp": "2022-02-09T18:15:14Z" + # } + ####################################################################### + # Augmentations + ####################################################################### + # In this case should add StarSystem and StarPos + ret = this.eddn.entry_augment_system_data(entry, system_name, system_starpos) + if isinstance(ret, str): + return ret + + entry = ret + ####################################################################### + + msg = { + '$schemaRef': f'https://eddn.edcd.io/schemas/fssallbodiesfound/1{"/test" if is_beta else ""}', + 'message': entry + } + + this.eddn.export_journal_entry(cmdr, entry, msg) + return None + def canonicalise(self, item: str) -> str: """ Canonicalise the given commodity name. @@ -1469,6 +1507,15 @@ def journal_entry( # noqa: C901, CCR001 entry ) + elif event_name == 'fssallbodiesfound': + return this.eddn.export_journal_fssallbodiesfound( + cmdr, + system, + state['StarPos'], + is_beta, + entry + ) + # Send journal schema events to EDDN, but not when on a crew if (config.get_int('output') & config.OUT_SYS_EDDN and not state['Captain'] and (event_name in ('location', 'fsdjump', 'docked', 'scan', 'saasignalsfound', 'carrierjump')) and From c57bbe878ac3f982be2c3561c4b651905813d3a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Feb 2022 17:03:46 +0000 Subject: [PATCH 174/186] build(deps): bump actions/github-script from 5 to 6 Bumps [actions/github-script](https://github.com/actions/github-script) from 5 to 6. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/github-script dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/submodule-update.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/submodule-update.yml b/.github/workflows/submodule-update.yml index ecd14e27..09562e1f 100644 --- a/.github/workflows/submodule-update.yml +++ b/.github/workflows/submodule-update.yml @@ -49,7 +49,7 @@ jobs: - name: Create pull request against target branch if: steps.check_for_changes.outputs.changes == 'true' - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: github-token: ${{ inputs.github_token }} script: | @@ -64,7 +64,7 @@ jobs: - name: Add labels if: steps.check_for_changes.outputs.changes == 'true' - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: github-token: ${{ inputs.github_token }} script: | From 91276aa99c483798c70f83fbcc8754b2ba668c29 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 12 Feb 2022 18:12:17 +0000 Subject: [PATCH 175/186] windows-build: Try out pre-release py2exe w/ semantic_version==2.9.0 --- requirements-dev.txt | 4 +++- requirements.txt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e98c5953..fd8478b8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -30,7 +30,9 @@ grip==4.6.0 # Used to put together a WiX configuration from template/auto-gen lxml==4.7.1 # We only need py2exe on windows. -py2exe==0.11.0.1; sys_platform == 'win32' +# Pre-release version addressing semantic_version 2.9.0+ issues: +# +py2exe==0.11.1.0; sys_platform == 'win32' # Testing pytest==7.0.0 diff --git a/requirements.txt b/requirements.txt index 479425c9..402c5a86 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ watchdog==2.1.6 infi.systray==0.1.12; sys_platform == 'win32' # argh==0.26.2 watchdog dep # pyyaml==5.3.1 watchdog dep -semantic-version==2.8.5 +semantic-version==2.9.0 # Base requirement for MacOS pyobjc; sys_platform == 'darwin' From ac7aefbd3ea2fa61130a4cbb874c639332f58944 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 12 Feb 2022 18:23:47 +0000 Subject: [PATCH 176/186] pre-commit: Fix `safety` invocation For some reason this was complaining about file names being passed in by pre-commit when `requirements-dev.txt` and `requirements.txt` were the two changed files. Explicitly passing them in `files:` config avoids this. --- .pre-commit-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 19b21930..20a49627 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -72,8 +72,9 @@ repos: hooks: - id: python-safety-dependencies-check entry: safety - args: [check, --bare, -r] + args: [check, --bare, --file] language: system + files: requirements-dev.txt, requirements.txt # Check translation comments are up to date - repo: local From 447908fd0c977793c5048f21707b1ad980dc69c0 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 12 Feb 2022 22:37:46 +0000 Subject: [PATCH 177/186] requirements-dev.txt: Start listing setuptools explicitly It turned out that the "fixes semantic_version 2.9.0" version of py2exe then has an issue with infi.systray because I was still using `setuptools==60.6.0`, as that's what was latest when I last did anything to cause its installation in my venv. So, list it explicitly on the latest version, which works with py2exe from , and then dependabot should prod us to keep it up to date. --- requirements-dev.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index fd8478b8..8086867d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,6 +2,11 @@ # Using legacy 'setup.py install' for flake8-annotations-coverage, since package 'wheel' is not installed. wheel +# We can't rely on just picking this up from either the base (not venv), +# or venv-init-time version. Specify here so that dependabot will prod us +# about new versions. +setupttools=60.8.2 + # Static analysis tools flake8==4.0.1 flake8-annotations-coverage==0.0.5 From 8489ceeffc1c468826f9f57267be4f122b2a3b91 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sat, 12 Feb 2022 22:42:18 +0000 Subject: [PATCH 178/186] requirements-dev: Fix setupttools= typos --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 8086867d..88f68a80 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,7 +5,7 @@ wheel # We can't rely on just picking this up from either the base (not venv), # or venv-init-time version. Specify here so that dependabot will prod us # about new versions. -setupttools=60.8.2 +setuptools==60.8.2 # Static analysis tools flake8==4.0.1 From 6414f7c48a06cd7250dc02397baf36065a6779db Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sun, 13 Feb 2022 15:01:46 +0000 Subject: [PATCH 179/186] Pre-Release 5.3.0-beta11: ChangeLog (appversion already bumped) --- ChangeLog.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 74766105..2d5edf25 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -27,6 +27,32 @@ produce the Windows executables and installer. --- +Pre-Release 5.3.0-beta11 +=== + +* EDND: Implement forthcoming approachsettlement/1 schema. + +* EDND: Implement forthcoming fssallbodiesfound/1 schema. + +* Inara: Only send a `setCommanderCredits` message at game login. Yes, you + will **NEED** to relog to send an updated balance. This is the only way + in which to sanely keep the 'Total Assets' value on Inara from bouncing + around. Refer to [Inara:API:docs:setCommanderCredits](https://inara.cz/inara-api-docs/#event-1). + + Closes [#1401](https://github.com/EDCD/EDMarketConnector/issues/1401). + +Plugin Developers +--- + +* It's unlikely to affect you, but our `requirements-dev.txt` now explicitly + cites a specific version of `setuptools`. This was necessary to ensure we + have a version that works with `py2exe` for the windows build process. + + If anything this will ensure you have a *more up to date* version of + `setuptools` installed. + +--- + Pre-Release 5.3.0-beta10 === @@ -41,6 +67,8 @@ Pre-Release 5.3.0-beta9 * Inara: Fix for always sending a Rank Progress of 0%. + Closes [#1378](https://github.com/EDCD/EDMarketConnector/issues/1378). + --- Pre-Release 5.3.0-beta8 From 893a6b243a04c2b6a1759515d3bba4a43d7bb55a Mon Sep 17 00:00:00 2001 From: Athanasius Date: Sun, 13 Feb 2022 15:36:01 +0000 Subject: [PATCH 180/186] develop: Bump appversion --- config/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/__init__.py b/config/__init__.py index 79f88393..aef24949 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -52,7 +52,7 @@ appcmdname = 'EDMC' # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.3.0-beta11' +_static_appversion = '5.3.0-beta12' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD' From 63052142c17a672c1fe2f060ad4ead2885b437f6 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Mon, 14 Feb 2022 17:46:55 +0000 Subject: [PATCH 181/186] ChangeLog: Don't lose the 5.2.4 section --- ChangeLog.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 2d5edf25..c196ec91 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -308,6 +308,22 @@ libraries then you should check it has Python 3.10 wheels available. --- +Release 5.2.4 +=== +This is a *very* minor update that simply imports the latest versions of +data files so that some niche functionality works properly. + +* Update `commodity.csv` and `rare_commodity.csv` from the latest + [EDCD/FDevIDs](https://github.com/EDCD/FDevIDs) versions. This addresses + an issue with export of market data in Trade Dangerous format containing + `OnionHeadC` rather than the correct name, `Onionhead Gamma Strain`, that + Trade Dangerous is expecting. + + This will only have affected Trade Dangerous users who use EDMarketConnector + as a source of market data. + +--- + Release 5.2.3 === From e152372b3a1974c23e1e364f2c2f11a963d7840e Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 15 Feb 2022 14:25:26 +0000 Subject: [PATCH 182/186] ChangeLog: Fix 'EDND' tyops --- ChangeLog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index c196ec91..07cf9236 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -30,9 +30,9 @@ produce the Windows executables and installer. Pre-Release 5.3.0-beta11 === -* EDND: Implement forthcoming approachsettlement/1 schema. +* EDDN: Implement forthcoming approachsettlement/1 schema. -* EDND: Implement forthcoming fssallbodiesfound/1 schema. +* EDDN: Implement forthcoming fssallbodiesfound/1 schema. * Inara: Only send a `setCommanderCredits` message at game login. Yes, you will **NEED** to relog to send an updated balance. This is the only way From 25e5ed9c14719cb63407f3753ca6c32603650afd Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 15 Feb 2022 15:07:04 +0000 Subject: [PATCH 183/186] setup.py: Explicitly package asyncio and multiprocessing We always did before, certainly in 5.2.4, so we should continue to do so, even though newer python and/or py2exe means they're no longer automatically included in library.zip by py2exe. --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 57383fe7..22975678 100755 --- a/setup.py +++ b/setup.py @@ -191,6 +191,8 @@ elif sys.platform == 'win32': 'dist_dir': dist_dir, 'optimize': 2, 'packages': [ + 'asyncio', # No longer auto as of py3.10+py2exe 0.11 + 'multiprocessing', # No longer auto as of py3.10+py2exe 0.11 'sqlite3', # Included for plugins 'util', # 2022-02-01 only imported in plugins/eddn.py ], From 5d685f3f3916f1f5f7e2dee32f2269d66178fd6a Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 15 Feb 2022 15:09:39 +0000 Subject: [PATCH 184/186] Release 5.3.0: appversion & ChangeLog --- ChangeLog.md | 241 +++++++++++++++++---------------------------- config/__init__.py | 2 +- 2 files changed, 90 insertions(+), 153 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 07cf9236..ddb3d871 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -27,101 +27,54 @@ produce the Windows executables and installer. --- -Pre-Release 5.3.0-beta11 +Release 5.3.0 === -* EDDN: Implement forthcoming approachsettlement/1 schema. - -* EDDN: Implement forthcoming fssallbodiesfound/1 schema. - -* Inara: Only send a `setCommanderCredits` message at game login. Yes, you - will **NEED** to relog to send an updated balance. This is the only way - in which to sanely keep the 'Total Assets' value on Inara from bouncing - around. Refer to [Inara:API:docs:setCommanderCredits](https://inara.cz/inara-api-docs/#event-1). - - Closes [#1401](https://github.com/EDCD/EDMarketConnector/issues/1401). - -Plugin Developers ---- - -* It's unlikely to affect you, but our `requirements-dev.txt` now explicitly - cites a specific version of `setuptools`. This was necessary to ensure we - have a version that works with `py2exe` for the windows build process. - - If anything this will ensure you have a *more up to date* version of - `setuptools` installed. - ---- - -Pre-Release 5.3.0-beta10 -=== - -* Revert `semantic_version` python module to v2.8.5, as v2.9.0 doesn't get - correctly included by py2exe. This leads to application crash at startup - when using the Windows .exe. - ---- - -Pre-Release 5.3.0-beta9 -=== - -* Inara: Fix for always sending a Rank Progress of 0%. - - Closes [#1378](https://github.com/EDCD/EDMarketConnector/issues/1378). - ---- - -Pre-Release 5.3.0-beta8 -=== - -This release is simply to check we've correctly updated the windows-build -configuration to pull in `FDevIDs` when the build is performed on GitHub. - ---- - -Pre-Release 5.3.0-beta7 -=== - -**NB: Due to [this issue with GitHub's Actions](https://github.com/actions/virtual-environments/issues/5023) -we were unable to build this version on GitHub. THE INSTALLER FOR THIS -RELEASE WAS BUILT ON A DEVELOPER'S OWN MACHINE. THERE IS NO REASON TO BELIEVE -THAT THIS IN ANY WAY INCREASES THE RISK OF IT ACTUALLY CONTAINING MALWARE.** - As has sadly become routine now, please read [our statement about malware false positives](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting#installer-and-or-executables-flagged-as-malicious-viruses) affecting our installers and/or the files they contain. We are as confident -as we can be, without detailed auditing of python.org's releases and all of +as we can be, without detailed auditing of python.org's releases and all the py2exe source and releases, that there is no malware in the files we make available. +This release is primarily aimed at fixing some more egregious bugs, +shortcomings and annoyances with the application. It also adds support for +two additional +[EDDN](https://github.com/EDCD/EDDN/blob/live/README.md) +schemas. + * We now test and build using Python 3.10.2. We do not *yet* make use of any - features specific to Python 3.10 (or 3.9). Let us restate that we - absolutely reserve the right to commence doing so. + features specific to Python 3.10 (or 3.9). Let us restate that we + absolutely reserve the right to commence doing so. + +* We now set a custom User-Agent header in all web requests, i.e. to EDDN, + EDSM and the like. This is of the form: + + `EDCD-EDMarketConnector-` * "File" -> "Status" will now show the new Odyssey ranks, both the new - categories and the new 'prestige' ranks, e.g. 'Elite I'. Closes - [#1369](https://github.com/EDCD/EDMarketConnector/issues/1369). + categories and the new 'prestige' ranks, e.g. 'Elite I'. + + Closes [#1369](https://github.com/EDCD/EDMarketConnector/issues/1369). * Running `EDMarketConnector.exe --reset-ui` will now also reset any changes to - the application "UI Scale" or geometry (position and size). Closes - [#1155](https://github.com/EDCD/EDMarketConnector/issues/1155). + the application "UI Scale" or geometry (position and size). + + Closes [#1155](https://github.com/EDCD/EDMarketConnector/issues/1155). * We now use UTC-based timestamps in the application's log files. Prior to - this change it was the "local time", but without any indication of the - applied timezone. Each line's timestamp has ` UTC` as a suffix now. We - are assuming that your local clock is correct *and* the timezone is set - correctly, such that Python's `time.gmtime()` yields UTC times. + this change it was the "local time", but without any indication of the + applied timezone. Each line's timestamp has ` UTC` as a suffix now. We + are assuming that your local clock is correct *and* the timezone is set + correctly, such that Python's `time.gmtime()` yields UTC times. - This should make it easier to correlate application logfiles with in-game - time and/or third-party service timestamps. - -* All communication with remote web servers and APIs now uses the same custom - "User-Agent" header to make attribution to this application clear. + This should make it easier to correlate application logfiles with in-game + time and/or third-party service timestamps. * The process used to build the Windows installers should now always pick up - all the necessary files automatically. Prior to this we used a manual - process to update the installer configuration which was prone to both user - error and neglecting to update it as necessary. + all the necessary files automatically. Prior to this we used a manual + process to update the installer configuration which was prone to both user + error and neglecting to update it as necessary. * If the application fails to load valid data from the `NavRoute.json` file when processing a Journal `NavRoute` event, it will attempt to retry this @@ -140,10 +93,15 @@ available. value when sending a `setCommanderCredits` message to Inara to set `commanderAssets`. - This should cause Inara and EDMC to agree on the Commander's current total - assets credits figure, and thus prevent it bouncing between two values. + In addition, a `setCommanderCredits` message at game login **will now only + ever be sent at game login**. Yes, you will **NEED** to relog to send an + updated balance. This is the only way in which to sanely keep the + 'Total Assets' value on Inara from bouncing around. + + Refer to [Inara:API:docs:setCommanderCredits](https://inara.cz/inara-api-docs/#event-1). Closes [#1401](https://github.com/EDCD/EDMarketConnector/issues/1401). + * Inara: Send a `setCommanderRankPilot` message when the player logs in to the game on-foot. Previously you would *HAVE* to be in a ship at login time for this to be sent. @@ -153,17 +111,9 @@ available. Closes [#1378](https://github.com/EDCD/EDMarketConnector/issues/1378). -* Inara: We will now send the new idea of "credits in the Commander's wallet" - when the delta from the last sent value is either 5% *or* at least - 10 million. It used to require a strict 5% difference, which meant needing - very large deltas if you had a large credit balance. +* Inara: Fix for always sending a Rank Progress of 0%. - The intention of the 5% is so that newer/poorer commanders still get - updates at an acceptable frequency, with the 10 million alternative trigger - allowing richer commanders to also experience more frequent updates than - was previously the case. - - Close [#1255](https://github.com/EDCD/EDMarketConnector/issues/1255). + Closes [#1378](https://github.com/EDCD/EDMarketConnector/issues/1378). * Inara: You should once more see updates for any materials used in Engineering. The bug was in our more general Journal event processing @@ -176,24 +126,30 @@ available. Closes [#1395](https://github.com/EDCD/EDMarketConnector/issues/1395). +* EDDN: Implement new [approachsettlement/1](https://github.com/EDCD/EDDN/blob/live/schemas/approachsettlement-README.md) + schema. + +* EDDN: Implement new [fssallbodiesfound/1](https://github.com/EDCD/EDDN/blob/live/schemas/fssallbodiesfound-README.md) + schema. + * EDDN: We now compress all outgoing messages. This might help get some - particularly large `navroute` messages go through. + particularly large `navroute` messages go through. - If any message is now rejected as 'too large' we will drop it, and thus - not retry it later. The application logs will reflect this. + If any message is now rejected as 'too large' we will drop it, and thus + not retry it later. The application logs will reflect this. - NB: The EDDN Gateway was updated to allow messages up to 1 MiB in size - anyway. The old limit was 100 KiB. + NB: The EDDN Gateway was updated to allow messages up to 1 MiB in size + anyway. The old limit was 100 KiB. - Closes [#1390](https://github.com/EDCD/EDMarketConnector/issues/1390). + Closes [#1390](https://github.com/EDCD/EDMarketConnector/issues/1390). * EDDN: In an attempt to diagnose some errors observed on the EDDN Gateway with respect to messages sent from this application some additional checks and logging have been added. **NB: After some thorough investigation it was concluded that these EDDN - errors were likely the result of long-delayed messages due to use of - the "Delay sending until docked" option.** + errors were likely the result of long-delayed messages due to use of + the "Delay sending until docked" option.** There should be no functional changes for users. But if you see any of the following in this application's log files **PLEASE OPEN @@ -209,7 +165,7 @@ available. - `this.coordinates is falsey, can't add StarPos` - `this.systemaddress is falsey, can't add SystemAddress` - `this.status_body_name was not set properly: ...` - + You might also see any of the following in the application status text (bottom of the window): @@ -222,9 +178,38 @@ available. Ref: [#1403](https://github.com/EDCD/EDMarketConnector/issues/1403) [#1393](https://github.com/EDCD/EDMarketConnector/issues/1393). +Translations +--- + +* Use a different workaround for OneSky (translations website) using "zh-Hans" + for Chinese (Simplified), whereas Windows will call this "zh-CN". This is + in-code and documented with a comment, as opposed to some 'magic' in the + Windows Installer configuration that had no such documentation. It's less + fragile than relying on that, or developers using a script/documented + process to rename the file. + Plugin Developers --- +We now test against, and package with Python 3.10.2. + +* We've made no explicit changes to the Python stdlib, or other modules, we + currently offer, but we did have to start explicitly including + `asyncio` and `multiprocessing` due to using a newer version of `py2exe` + for the windows build. + +* We will now include in the Windows installer *all* of the files that `py2exe` + places in the build directory. This is vulnerable to a later version of + our code, python and/or py2exe no longer causing inclusion of a module. + + We have endeavoured to ensure this release contains *at least* all of the + same modules that 5.2.4 did. + + We are looking into + [including all of Python stdlib](https://github.com/EDCD/EDMarketConnector/issues/1327), + but if there's a particular part of this we don't package then please ask + us to by opening an issue on GitHub. + * We now have an `.editorconfig` file which will instruct your editor/IDE to change some settings pertaining to things like indentation and line wrap, assuming your editor/IDE supports the file. @@ -250,62 +235,14 @@ Plugin Developers Developers of third-party plugins should never have been using these files anyway, so this shouldn't break anything for them. -* We will now include in the Windows installer *all* of the files that `py2exe` - places in the build directory. We still do *not* yet include *all* of the - Python 'stdlib', so open an issue on GitHub if something you need is - missing. +* It's unlikely to affect you, but our `requirements-dev.txt` now explicitly + cites a specific version of `setuptools`. This was necessary to ensure we + have a version that works with `py2exe` for the windows build process. + + If anything this will ensure you have a *more up to date* version of + `setuptools` installed. --- -5.3.0-beta3 through 5.3.0-beta6 were used internally, no public pre-releases. - ---- - -Pre-Release 5.3.0-beta2 -=== - -In terms of application functionality this is identical to 5.3.0-beta1. The -only changes are related to the process of building the Windows installer. - -- Ensure we always package all files into the Windows installer. -- Use a different workaround for OneSky (translations website) using "zh-Hans" - for Chinese (Simplified), whereas Windows will call this "zh-CN". This is - in-code and documented with a comment, as opposed to some 'magic' in the - Windows Installer configuration that had no such documentation. It's less - fragile than relying on that, or developers using a script/documented - process to rename the file. - -Pre-Release 5.3.0-beta1 -=== - -This is a test release to ensure packaging with Python 3.10.1 is working -correctly. There is also a small change to metadata in any remote web -request we make. - -* We now set a custom User-Agent header in all web requests, i.e. to EDDN, - EDSM and the like. This is of the form: - - `EDCD-EDMarketConnector-` - -Developers ---- - -We now test against, and package with Python 3.10.1. - -We've made no explicit changes to the Python stdlib, or other modules, we -currently offer. However, the newer [py2exe](https://github.com/py2exe/py2exe/) -we now use has decided we no longer need: - - - _aynscio.pyd - - _multiprocessing.pyd - - _overlapped.pyd - -so those are no longer included in the Windows installers. We are looking into -[including all of Python stdlib](https://github.com/EDCD/EDMarketConnector/issues/1327) -so this would be resolved by that. - -If your plugin utilises any module/package that requires per-architecture -libraries then you should check it has Python 3.10 wheels available. - --- Release 5.2.4 diff --git a/config/__init__.py b/config/__init__.py index aef24949..63505dac 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -52,7 +52,7 @@ appcmdname = 'EDMC' # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.3.0-beta12' +_static_appversion = '5.3.0' _cached_version: Optional[semantic_version.Version] = None copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD' From d13cf73688be21c89a257ef6981406662f80c641 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 15 Feb 2022 15:17:06 +0000 Subject: [PATCH 185/186] l10n: Updated translations from OneSky --- L10n/pl.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/L10n/pl.strings b/L10n/pl.strings index 4ac1d33b..6e4f7244 100644 --- a/L10n/pl.strings +++ b/L10n/pl.strings @@ -250,6 +250,9 @@ /* eddn.py: EDDN returned some sort of HTTP error, one we didn't expect. {STATUS} contains a number; In files: eddn.py:344; */ "EDDN Error: Returned {STATUS} status code" = "Błąd EDDN: Zwrócono kod {STATUS}"; +/* eddn.py: No 'Route' found in NavRoute.json file; In files: eddn.py:970; */ +"No 'Route' array in NavRoute.json contents" = "Brak tablicy \"Route\" wewnątrz NavRoute.json"; + /* eddn.py: Enable EDDN support for station data checkbox label; In files: eddn.py:1121; */ "Send station data to the Elite Dangerous Data Network" = "Wyślij dane do Elite Dangerous Data Network"; From e6c342136593b2aabda29898811e43fcbf3d18b1 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 15 Feb 2022 15:17:44 +0000 Subject: [PATCH 186/186] Release 5.3.0: ChanegLog: Oops, translations --- ChangeLog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index ddb3d871..ca5ad793 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -55,6 +55,8 @@ schemas. * "File" -> "Status" will now show the new Odyssey ranks, both the new categories and the new 'prestige' ranks, e.g. 'Elite I'. + **NB: Due to an oversight there are currently no translations for these.** + Closes [#1369](https://github.com/EDCD/EDMarketConnector/issues/1369). * Running `EDMarketConnector.exe --reset-ui` will now also reset any changes to @@ -188,6 +190,11 @@ Translations fragile than relying on that, or developers using a script/documented process to rename the file. +* As noted above we forgot to upload to + [OneSky](https://marginal.oneskyapp.com/collaboration/project/52710) + after adding the Odyssey new ranks/categories. This has now been done, + and some new phrases await translation. + Plugin Developers ---