1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-22 20:00:29 +03:00

Merge branch 'release-5.6.0' into releases

This commit is contained in:
Athanasius 2022-11-28 16:35:50 +00:00
commit 7ed08396c6
No known key found for this signature in database
GPG Key ID: 772697E181BB2767
26 changed files with 878 additions and 327 deletions

View File

@ -1,6 +0,0 @@
[run]
omit =
# The tests themselves
tests/*
# Any venv files
venv/*

View File

@ -21,7 +21,7 @@ repos:
# - id: autopep8
### # flake8 --show-source <file>
### - repo: https://gitlab.com/pycqa/flake8
### - repo: https://github.com/PyCQA/flake8
### rev: ''
### hooks:
### - id: flake8

View File

@ -1 +1 @@
3.10.7
3.10.8

View File

@ -9,7 +9,7 @@ produce the Windows executables and installer.
---
* We now test against, and package with, Python 3.10.7.
* We now test against, and package with, Python 3.10.8.
**As a consequence of this we no longer support Windows 7.
This is due to
@ -27,6 +27,136 @@ produce the Windows executables and installer.
---
Release 5.6.0
===
Tha major reason for this release is to address the Live versus Legacy galaxy
split [coming in Update 14 of the game](https://www.elitedangerous.com/news/elite-dangerous-update-14-and-beyond-live-and-legacy-modes).
See the section "Update 14 and the Galaxy Split" below for how this might
impact you.
Changes
---
* We now test against, and package with, Python 3.10.8.
* The code for sending data to EDDN has been reworked. This changes the
'replay log' from utilising an internal array, backed by a flat file
(`replay.jsonl`), to an sqlite3 database.
As a result:
1. Any messages stored in the old `replay.jsonl` are converted at startup,
if that file is present, and then the file removed.
2. All new messages are stored in this new sqlite3 queue before any attempt
is made to send them. An immediate attempt is then made to send any
message not affected by "Delay sending until docked".
3. Sending of queued messages will be attempted every 5 minutes, unless
"Delay sending until docked" is active and the Cmdr is not docked in
their own ship. This is in case a message failed to send due to an issue
communicating with the EDDN Gateway.
4. When you dock in your own ship an immediate attempt to send all queued
messages will be initiated.
5. When processing queued messages the same 0.4-second inter-message delay
as with the old code has been implemented. This serves to not suddenly
flood the EDDN Gateway. If any message fails to send for Gateway reasons,
i.e. not a bad message, then this processing is abandoned to wait for
the next invocation.
The 5-minute timer in point 3 differs from the old code, where almost any
new message sending attempt could initiate processing of the queue. At
application startup this delay is only 10 seconds.
Currently, the feedback of "Sending data to EDDN..." in the UI status line
has been removed.
**If you do not have "Delay sending until docked" active, then the only
messages that will be at all delayed will be where there was a communication
problem with the EDDN Gateway, or it otherwise indicated a problem other
than 'your message is bad'.**
* As a result of this EDDN rework this application now sends appropriate
`gameversion` and `gamebuild` strings in EDDN message headers.
The rework was necessary in order to enable this, in case of any queued
or delayed messages which did not contain this information in the legacy
`replay.jsonl` format.
* For EDSM there is a very unlikely set of circumstances that could, in theory
lead to some events not being sent. This is so as to safeguard against
sending a batch with a gameversion/build claimed that does not match for
*all* of the events in that batch.
It would take a combination of "communications with EDSM are slow", more
events (the ones that would be lost), a game client crash, *and* starting
a new game client before the 'more events' are sent.
Update 14 and the Galaxy Split
---
Due to the galaxy split [announced by Frontier](https://www.elitedangerous.com/news/elite-dangerous-update-14-and-beyond-live-and-legacy-modes)
there are some changes to the major third-party websites and tools.
* Inara [has chosen](https://inara.cz/elite/board-thread/7049/463292/#463292)
to only accept Live galaxy data on its API.
This application will not even process Journal data for Inara after
2022-11-29T09:00:00+00:00 *unless the `gameversion` indicates a Live client*.
This explicitly checks that the game's version is semantically equal to or
greater than '4.0.0'.
If a Live client is *not* detected, then there is an INFO level logging
message "Inara only accepts Live galaxy data", which is also set as the main
UI status line. This message will repeat, at most, every 5 minutes.
If you continue to play in the Legacy galaxy only then you probably want to
just disable the Inara plugin with the checkbox on Settings > Inara.
* All batches of events sent to EDSM will be tagged with a `gameversion`, in
a similar manner to the EDDN header.
Ref: [EDSM api-journal-v1](https://www.edsm.net/en/api-journal-v1)
* All EDDN messages will now have appropriate `gameversion` and `gamebuild`
fields in the `header` as per
[EDDN/docs/Developers.md](https://github.com/EDCD/EDDN/blob/live/docs/Developers.md#gameversions-and-gamebuild).
As a result of this you can expect third-party sites to choose to filter data
based on that.
Look for announcements by individual sites/tools as to what they have chosen
to do.
Known Bugs
---
In testing if it had been broken at all due to 5.5.0 -> 5.6.0 changes it has
come to light that `EDMC.EXE -n`, to send data to EDDN, was already broken in
5.5.0.
In addition, there is now some extra 'INFO' logging output which will be
produced by any invocation of `EDMC.EXE`. This might break third-party use of
it, e.g. [Trade Computer Extension Mk.II](https://forums.frontier.co.uk/threads/trade-computer-extension-mk-ii.223056/).
This will be fixed as soon as the dust settles from Update 14, with emphasis
being on ensuring the GUI `EDMarketConnector.exe` functions properly.
Notes for EDDN Listeners
---
* Where EDMC sourced data from the Journal files it will set `gameversion`
and `gamebuild` as per their values in `Fileheader` or `LoadGame`, whichever
was more recent (there are some events that occur between these).
* *If any message was already delayed such that it did not
have the EDDN header recorded, then the `gameversion` and `gamebuild` will
be empty strings*. In order to indicate this the `softwareName` will have
` (legacy replay)` appended to it, e.g. `E:D Market Connector Connector
[Windows] (legacy replay)`. In general this indicates that the message was
queued up using a version of EDMC prior to this one. If you're only
interested in Live galaxy data then you might want to ignore such messages.
* Where EDMC sourced data from a CAPI endpoint, the resulting EDDN message
will have a `gameversion` of `CAPI-<endpoint>` set, e.g. `CAPI-market`.
**At this time it is not 100% certain which galaxy this data will be for, so
all listeners are advised to ignore/queue such data until this is clarified**.
`gamebuild` will be an empty string for all CAPI-sourced data.
Plugin Developers
---
* There is a new flag in `state` passed to plugins, `IsDocked`. See PLUGINS.md
for details.
---
Release 5.5.0
===

View File

@ -244,6 +244,46 @@ 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).
### Test Coverage
As we work towards actually having tests for as much of the code as possible
it is useful to monitor the current test coverage.
Running `pytest` will also produce the overall coverage report, see the
configured options in `pyproject.toml`.
One issue you might run into is where there is code that only runs on one
platform. By default `pytest-cov`/`coverage` will count this code as not
tested when run on a different platform. We utilise the
`coverage-conditional-plugin` module so that `#pragma` comments can be used
to give hints to coverage about this.
The pragmas are defined in the
`tool.coverage.coverage_conditional_plugin.rules` section of `pyproject.toml`,
e.g.
```toml
[tool.coverage.coverage_conditional_plugin.rules]
sys-platform-win32 = "sys_platform != 'win32'"
...
```
And are used as in:
```python
import sys
if sys.platform == 'win32': # pragma: sys-platform-win32
...
else: # pragma: sys-platform-not-win32
...
```
Note the inverted sense of the pragma definitions, as the comments cause
`coverage` to *not* consider that code block on this platform.
As of 2022-10-02 and `coverage-conditional-plugin==0.7.0` there is no way to
signal that an entire file should be excluded from coverage reporting on the
current platform. See
[this GitHub issue comment](https://github.com/wemake-services/coverage-conditional-plugin/issues/2#issuecomment-1263918296)
.
---
## Imports used only in core plugins

View File

@ -922,7 +922,7 @@ class AppWindow(object):
return False
# Ignore possibly missing shipyard info
elif (config.get_int('output') & config.OUT_MKT_EDDN) \
elif (config.get_int('output') & config.OUT_EDDN_SEND_STATION_DATA) \
and not (data['lastStarport'].get('commodities') or data['lastStarport'].get('modules')):
if not self.status['text']:
# LANG: Status - Either no market or no modules data for station from Frontier CAPI

View File

@ -46,6 +46,9 @@
/* 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: The Inara API only accepts Live galaxy data, not Legacy galaxy data; In files: inara.py:383; inara.py:386; */
"Inara only accepts Live galaxy data" = "Inara only accepts Live galaxy data";
/* 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}";
@ -744,4 +747,3 @@
/* stats.py: Status dialog title; In files: stats.py:422; */
"Ships" = "Ships";

View File

@ -1,3 +1,6 @@
/* inara.py: The Inara API only accepts Live galaxy data, not Legacy galaxy data; In files: inara.py:383; inara.py:386; */
"Inara only accepts Live galaxy data" = "Inaraは現行の銀河データのみ受け付けます";
/* Language name */
"!Language" = "日本語";

View File

@ -1,3 +1,6 @@
/* inara.py: The Inara API only accepts Live galaxy data, not Legacy galaxy data; In files: inara.py:383; inara.py:386; */
"Inara only accepts Live galaxy data" = "Inara apenas aceita dados da versão Live.";
/* Language name */
"!Language" = "Português (Brasil)";

View File

@ -1,3 +1,6 @@
/* inara.py: The Inara API only accepts Live galaxy data, not Legacy galaxy data; In files: inara.py:383; inara.py:386; */
"Inara only accepts Live galaxy data" = "A API Inara só aceita dados da versão Live";
/* Language name */
"!Language" = "Português (Portugal)";

View File

@ -1,3 +1,6 @@
/* inara.py: The Inara API only accepts Live galaxy data, not Legacy galaxy data; In files: inara.py:383; inara.py:386; */
"Inara only accepts Live galaxy data" = "Inara принимает только данные Live-версии";
/* Language name */
"!Language" = "Русский";

View File

@ -1,3 +1,6 @@
/* inara.py: The Inara API only accepts Live galaxy data, not Legacy galaxy data; In files: inara.py:383; inara.py:386; */
"Inara only accepts Live galaxy data" = "Inara prihvata samo Live galaxy podatke";
/* Language name */
"!Language" = "Srpski (Latinica, Bosna i Hercegovina)";

View File

@ -1,3 +1,6 @@
/* inara.py: The Inara API only accepts Live galaxy data, not Legacy galaxy data; In files: inara.py:383; inara.py:386; */
"Inara only accepts Live galaxy data" = "Inara prihvata samo \"žive\" podatke o galaksiji";
/* Language name */
"!Language" = "Srpski (Latinica)";

View File

@ -617,6 +617,7 @@ Content of `state` (updated to the current journal entry):
| `Modules` | `dict` | Currently fitted modules |
| `NavRoute` | `dict` | Last plotted multi-hop route |
| `ModuleInfo` | `dict` | Last loaded ModulesInfo.json data |
| `IsDocked` | `bool` | Whether the Cmdr is currently docked *in their own ship*. |
| `OnFoot` | `bool` | Whether the Cmdr is on foot |
| `Component` | `dict` | 'Component' MicroResources in Odyssey, `int` count each. |
| `Item` | `dict` | 'Item' MicroResources in Odyssey, `int` count each. |
@ -710,6 +711,17 @@ NB: It *is* possible, if a player is quick enough, to plot and clear a route
before we load it, in which case we'd be retaining the *previous* plotted
route.
New in version 5.6.0:
`IsDocked` boolean added to `state`. This is set True for a `Location` event
having `"Docked":true"`, or the `Docked` event. It is set back to False (its
default value) for an `Undocked` event. Being on-foot in a station at login
time does *not* count as docked for this.
In general on-foot, including being in a taxi, might not set this 100%
correctly. Its main use in core code is to detect being docked so as to send
any stored EDDN messages due to "Delay sending until docked" option.
___
##### Synthetic Events

View File

@ -52,7 +52,7 @@ appcmdname = 'EDMC'
# <https://semver.org/#semantic-versioning-specification-semver>
# Major.Minor.Patch(-prerelease)(+buildmetadata)
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
_static_appversion = '5.5.0'
_static_appversion = '5.6.0'
_cached_version: Optional[semantic_version.Version] = None
copyright = '© 2015-2019 Jonathan Harris, 2020-2022 EDCD'
@ -162,7 +162,7 @@ def appversion_nobuild() -> semantic_version.Version:
class AbstractConfig(abc.ABC):
"""Abstract root class of all platform specific Config implementations."""
OUT_MKT_EDDN = 1
OUT_EDDN_SEND_STATION_DATA = 1
# OUT_MKT_BPC = 2 # No longer supported
OUT_MKT_TD = 4
OUT_MKT_CSV = 8
@ -171,12 +171,12 @@ class AbstractConfig(abc.ABC):
# 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
OUT_EDDN_SEND_NON_STATION = 2048
OUT_EDDN_DELAY = 4096
OUT_STATION_ANY = OUT_EDDN_SEND_STATION_DATA | OUT_MKT_TD | OUT_MKT_CSV
app_dir_path: pathlib.Path
plugin_dir_path: pathlib.Path
@ -454,19 +454,19 @@ def get_config(*args, **kwargs) -> AbstractConfig:
:param kwargs: Args to be passed through to implementation.
:return: Instance of the implementation.
"""
if sys.platform == "darwin":
if sys.platform == "darwin": # pragma: sys-platform-darwin
from .darwin import MacConfig
return MacConfig(*args, **kwargs)
elif sys.platform == "win32":
elif sys.platform == "win32": # pragma: sys-platform-win32
from .windows import WinConfig
return WinConfig(*args, **kwargs)
elif sys.platform == "linux":
elif sys.platform == "linux": # pragma: sys-platform-linux
from .linux import LinuxConfig
return LinuxConfig(*args, **kwargs)
else:
else: # pragma: sys-platform-not-known
raise ValueError(f'Unknown platform: {sys.platform=}')

View File

@ -1,3 +1,4 @@
"""Darwin/macOS implementation of AbstractConfig."""
import pathlib
import sys
from typing import Any, Dict, List, Union

View File

@ -94,7 +94,7 @@ class JournalLock:
:return: LockResult - See the class Enum definition
"""
if sys.platform == 'win32':
if sys.platform == 'win32': # pragma: sys-platform-win32
logger.trace_if('journal-lock', 'win32, using msvcrt')
# win32 doesn't have fcntl, so we have to use msvcrt
import msvcrt
@ -107,7 +107,7 @@ class JournalLock:
f", assuming another process running: {e!r}")
return JournalLockResult.ALREADY_LOCKED
else: # pytest coverage only sees this on !win32
else: # pragma: sys-platform-not-win32
logger.trace_if('journal-lock', 'NOT win32, using fcntl')
try:
import fcntl
@ -143,7 +143,7 @@ class JournalLock:
return True # We weren't locked, and still aren't
unlocked = False
if sys.platform == 'win32':
if sys.platform == 'win32': # pragma: sys-platform-win32
logger.trace_if('journal-lock', 'win32, using msvcrt')
# win32 doesn't have fcntl, so we have to use msvcrt
import msvcrt
@ -160,7 +160,7 @@ class JournalLock:
else:
unlocked = True
else: # pytest coverage only sees this on !win32
else: # pragma: sys-platform-not-win32
logger.trace_if('journal-lock', 'NOT win32, using fcntl')
try:
import fcntl

View File

@ -21,6 +21,8 @@ from typing import Tuple
if TYPE_CHECKING:
import tkinter
import semantic_version
import util_ships
from config import config
from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised
@ -111,6 +113,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
# Context for journal handling
self.version: Optional[str] = None
self.version_semantic: Optional[semantic_version.Version] = None
self.is_beta = False
self.mode: Optional[str] = None
self.group: Optional[str] = None
@ -131,6 +134,11 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
self._fcmaterials_retries_remaining = 0
self._last_fcmaterials_journal_timestamp: Optional[float] = None
# For determining Live versus Legacy galaxy.
# The assumption is gameversion will parse via `coerce()` and always
# be >= for Live, and < for Legacy.
self.live_galaxy_base_version = semantic_version.Version('4.0.0')
self.__init_state()
def __init_state(self) -> None:
@ -166,6 +174,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
'Modules': None,
'CargoJSON': None, # The raw data from the last time cargo.json was read
'Route': None, # Last plotted route from Route.json file
'IsDocked': False, # Whether we think cmdr is docked
'OnFoot': False, # Whether we think you're on-foot
'Component': defaultdict(int), # Odyssey Components in Ship Locker
'Item': defaultdict(int), # Odyssey Items in Ship Locker
@ -293,6 +302,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
self.currentdir = None
self.version = None
self.version_semantic = None
self.mode = None
self.group = None
self.cmdr = None
@ -306,6 +316,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
self.systemaddress = None
self.is_beta = False
self.state['OnFoot'] = False
self.state['IsDocked'] = False
self.state['Body'] = None
self.state['BodyType'] = None
@ -725,6 +736,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
self.station_marketid = None
self.stationtype = None
self.stationservices = None
self.state['IsDocked'] = False
elif event_type == 'embark':
# This event is logged when a player (on foot) gets into a ship or SRV
@ -791,6 +803,7 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
self.state['Dropship'] = False
elif event_type == 'docked':
self.state['IsDocked'] = True
self.station = entry.get('StationName') # May be None
self.station_marketid = entry.get('MarketID') # May be None
self.stationtype = entry.get('StationType') # May be None
@ -813,6 +826,8 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
if event_type == 'location':
logger.trace_if('journal.locations', '"Location" event')
if entry.get('Docked'):
self.state['IsDocked'] = True
elif event_type == 'fsdjump':
self.planet = None
@ -1677,6 +1692,20 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
self.state['GameVersion'] = entry['gameversion']
self.state['GameBuild'] = entry['build']
self.version = self.state['GameVersion']
try:
self.version_semantic = semantic_version.Version.coerce(self.state['GameVersion'])
except Exception:
# Catching all Exceptions as this is *one* call, and we won't
# get caught out by any semantic_version changes.
self.version_semantic = None
logger.error(f"Couldn't coerce {self.state['GameVersion']=}")
pass
else:
logger.info(f"Parsed {self.state['GameVersion']=} into {self.version_semantic=}")
self.is_beta = any(v in self.version.lower() for v in ('alpha', 'beta')) # type: ignore
except KeyError:
if not suppress:
@ -2348,6 +2377,25 @@ class EDLogs(FileSystemEventHandler): # type: ignore # See below
self._last_fcmaterials_journal_timestamp = None
return file
def is_live_galaxy(self) -> bool:
"""
Indicate if current tracking indicates Live galaxy.
We assume:
1) `gameversion` remains something that semantic_verison.Version.coerce() can parse.
2) Any Live galaxy client reports a version >= the defined base version.
3) Any Legacy client will always report a version < that base version.
:return: True for Live, False for Legacy or unknown.
"""
# If we don't yet know the version we can't tell, so assume the worst
if self.version_semantic is None:
return False
if self.version_semantic >= self.live_galaxy_base_version:
return True
return False
# singleton
monitor = EDLogs()

File diff suppressed because it is too large Load Diff

View File

@ -71,6 +71,9 @@ class This:
def __init__(self):
self.shutting_down = False # Plugin is shutting down.
self.game_version = ""
self.game_build = ""
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
@ -432,6 +435,9 @@ def journal_entry( # noqa: C901, CCR001
if should_return:
return
this.game_version = state['GameVersion']
this.game_build = state['GameBuild']
entry = new_entry
this.on_foot = state['OnFoot']
@ -546,7 +552,7 @@ entry: {entry!r}'''
materials.update(transient)
logger.trace_if(CMDR_EVENTS, f'"LoadGame" event, queueing Materials: {cmdr=}')
this.queue.put((cmdr, materials))
this.queue.put((cmdr, this.game_version, this.game_build, materials))
if entry['event'] in ('CarrierJump', 'FSDJump', 'Location', 'Docked'):
logger.trace_if(
@ -555,7 +561,7 @@ Queueing: {entry!r}'''
)
logger.trace_if(CMDR_EVENTS, f'"{entry["event"]=}" event, queueing: {cmdr=}')
this.queue.put((cmdr, entry))
this.queue.put((cmdr, this.game_version, this.game_build, entry))
# Update system data
@ -638,6 +644,8 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently
pending: List[Mapping[str, Any]] = [] # Unsent events
closing = False
cmdr: str = ""
last_game_version = ""
last_game_build = ""
entry: Mapping[str, Any] = {}
while not this.discarded_events:
@ -657,10 +665,10 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently
logger.debug(f'{this.shutting_down=}, so setting closing = True')
closing = True
item: Optional[Tuple[str, Mapping[str, Any]]] = this.queue.get()
item: Optional[Tuple[str, str, str, Mapping[str, Any]]] = this.queue.get()
if item:
(cmdr, entry) = item
logger.trace_if(CMDR_EVENTS, f'De-queued ({cmdr=}, {entry["event"]=})')
(cmdr, game_version, game_build, entry) = item
logger.trace_if(CMDR_EVENTS, f'De-queued ({cmdr=}, {game_version=}, {game_build=}, {entry["event"]=})')
else:
logger.debug('Empty queue message, setting closing = True')
@ -686,6 +694,20 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently
logger.trace_if(
CMDR_EVENTS, f'({cmdr=}, {entry["event"]=}): not in discarded_events, appending to pending')
# Discard the pending list if it's a new Journal file OR
# if the gameversion has changed. We claim a single
# gameversion for an entire batch of events so can't mix
# them.
# The specific gameversion check caters for scenarios where
# we took some time in the last POST, had new events queued
# in the meantime *and* the game client crashed *and* was
# changed to a different gameversion.
if (
entry['event'].lower() == 'fileheader'
or last_game_version != game_version or last_game_build != game_build
):
pending = []
pending.append(entry)
# drop events if required by killswitch
@ -726,6 +748,8 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently
'apiKey': apikey,
'fromSoftware': applongname,
'fromSoftwareVersion': str(appversion()),
'fromGameVersion': game_version,
'fromGameBuild': game_build,
'message': json.dumps(pending, ensure_ascii=False).encode('utf-8'),
}
@ -807,7 +831,7 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently
plug.show_error(_("Error: Can't connect to EDSM"))
if entry['event'].lower() in ('shutdown', 'commander', 'fileheader'):
# Game shutdown or new login so we MUST not hang on to pending
# Game shutdown or new login, so we MUST not hang on to pending
pending = []
logger.trace_if(CMDR_EVENTS, f'Blanked pending because of event: {entry["event"]}')
@ -815,6 +839,9 @@ def worker() -> None: # noqa: CCR001 C901 # Cant be broken up currently
logger.debug('closing, so returning.')
return
last_game_version = game_version
last_game_build = game_build
logger.debug('Done.')

View File

@ -28,6 +28,7 @@ import time
import tkinter as tk
from collections import OrderedDict, defaultdict, deque
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from operator import itemgetter
from threading import Lock, Thread
from typing import TYPE_CHECKING, Any, Callable, Deque, Dict, List, Mapping, NamedTuple, Optional
@ -44,6 +45,7 @@ import timeout_session
from companion import CAPIData
from config import applongname, appversion, config, debug_senders
from EDMCLogging import get_main_logger
from monitor import monitor
from ttkHyperlinkLabel import HyperlinkLabel
logger = get_main_logger()
@ -88,6 +90,11 @@ class This:
def __init__(self):
self.session = timeout_session.new_session()
self.thread: Thread
self.parent: tk.Tk
# Handle only sending Live galaxy data
self.legacy_galaxy_last_notified: Optional[datetime] = None
self.lastlocation = None # eventData from the last Commander's Flight Log event
self.lastship = None # eventData from the last addCommanderShip or setCommanderShip event
@ -210,6 +217,7 @@ def plugin_start3(plugin_dir: str) -> str:
def plugin_app(parent: tk.Tk) -> None:
"""Plugin UI setup Hook."""
this.parent = parent
this.system_link = parent.children['system'] # system label in main window
this.station_link = parent.children['station'] # station label in main window
this.system_link.bind_all('<<InaraLocation>>', update_location)
@ -361,6 +369,24 @@ def journal_entry( # noqa: C901, CCR001
:return: str - empty if no error, else error string.
"""
if not monitor.is_live_galaxy():
# This only applies after Update 14, which as of 2022-11-27 is scheduled
# for 2022-11-29, with the game servers presumably being down around
# 09:00
if datetime.now(timezone.utc) >= datetime.fromisoformat("2022-11-27T09:00:00+00:00"):
# Update 14 ETA has passed, so perform the check
if (
this.legacy_galaxy_last_notified is None
or (datetime.now(timezone.utc) - this.legacy_galaxy_last_notified) > timedelta(seconds=300)
):
# LANG: The Inara API only accepts Live galaxy data, not Legacy galaxy data
logger.info(_("Inara only accepts Live galaxy data"))
# this.parent.children['status']['text'] =
this.legacy_galaxy_last_notified = datetime.now(timezone.utc)
return _("Inara only accepts Live galaxy data")
return ''
should_return, new_entry = killswitch.check_killswitch('plugins.inara.journal', entry, logger)
if should_return:
plug.show_error(_('Inara disabled. See Log.')) # LANG: INARA support disabled via killswitch

View File

@ -1221,7 +1221,9 @@ class PreferencesDialog(tk.Toplevel):
(self.out_csv.get() and config.OUT_MKT_CSV) +
(config.OUT_MKT_MANUAL if not self.out_auto.get() else 0) +
(self.out_ship.get() and config.OUT_SHIP) +
(config.get_int('output') & (config.OUT_MKT_EDDN | config.OUT_SYS_EDDN | config.OUT_SYS_DELAY))
(config.get_int('output') & (
config.OUT_EDDN_SEND_STATION_DATA | config.OUT_EDDN_SEND_NON_STATION | config.OUT_EDDN_DELAY
))
)
config.set(

View File

@ -7,9 +7,25 @@ line_length = 119
[tool.pytest.ini_options]
testpaths = ["tests"] # Search for tests in tests/
addopts = "--cov . --cov plugins --cov-report=term-missing --no-cov-on-fail"
# --cov-fail-under 80"
[tool.coverage.run]
omit = ["venv/*"] # when running pytest --cov, dont report coverage in venv directories
omit = [ "tests/*", "venv/*", "dist.win32/*" ]
plugins = [ "coverage_conditional_plugin" ]
[tool.coverage.coverage_conditional_plugin.rules]
# NB: The name versus content of all of these are inverted because of the way
# they're used. When a pragma cites one it causes that code block to
# **NOT** be considered for code coverage.
# See Contributing.md#test-coverage for more details.
sys-platform-win32 = "sys_platform != 'win32'"
sys-platform-not-win32 = "sys_platform == 'win32'"
sys-platform-darwin = "sys_platform != 'darwin'"
sys-platform-not-darwin = "sys_platform == 'darwin'"
sys-platform-linux = "sys_platform != 'linux'"
sys-platform-not-linux = "sys_platform == 'linux'"
sys-platform-not-known = "sys_platform in ('darwin', 'linux', 'win32')"
[tool.pyright]
# pythonPlatform = 'Darwin'

View File

@ -5,28 +5,28 @@ 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.
setuptools==65.3.0
setuptools==65.6.0
# Static analysis tools
flake8==5.0.4
flake8-annotations-coverage==0.0.6
flake8-cognitive-complexity==0.1.0
flake8-comprehensions==3.10.0
flake8-comprehensions==3.10.1
flake8-docstrings==1.6.0
isort==5.10.1
flake8-isort==4.2.0
flake8-isort==5.0.3
flake8-json==21.7.0
flake8-noqa==1.2.9
flake8-polyfill==1.0.2
flake8-use-fstring==1.4
mypy==0.971
mypy==0.991
pep8-naming==0.13.2
safety==2.2.0
types-requests==2.28.11
safety==2.3.1
types-requests==2.28.11.5
# Code formatting tools
autopep8==1.7.0
autopep8==2.0.0
# HTML changelogs
grip==4.6.1
@ -37,14 +37,15 @@ lxml==4.9.1
# We only need py2exe on windows.
# Pre-release version addressing semantic_version 2.9.0+ issues:
# <https://github.com/py2exe/py2exe/issues/126>
py2exe==0.12.0.1; sys_platform == 'win32'
py2exe==0.13.0.0; sys_platform == 'win32'
# Testing
pytest==7.1.3
pytest-cov==3.0.0 # Pytest code coverage support
coverage[toml]==6.4.4 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
pytest==7.2.0
pytest-cov==4.0.0 # Pytest code coverage support
coverage[toml]==6.5.0 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
coverage-conditional-plugin==0.7.0
# For manipulating folder permissions and the like.
pywin32==304; sys_platform == 'win32'
pywin32==305; sys_platform == 'win32'
# All of the normal requirements

View File

@ -1,4 +1,4 @@
certifi==2022.9.14
certifi==2022.9.24
requests==2.28.1
watchdog==2.1.9
# Commented out because this doesn't package well with py2exe

View File

@ -8,7 +8,6 @@ import pytest
# Import as other names else they get picked up when used as fixtures
from _pytest import monkeypatch as _pytest_monkeypatch
from _pytest import tmpdir as _pytest_tmpdir
from py._path.local import LocalPath as py_path_local_LocalPath
from config import config
from journal_lock import JournalLock, JournalLockResult
@ -120,7 +119,7 @@ class TestJournalLock:
def mock_journaldir(
self, monkeypatch: _pytest_monkeypatch,
tmp_path_factory: _pytest_tmpdir.TempPathFactory
) -> py_path_local_LocalPath:
) -> _pytest_tmpdir.TempPathFactory:
"""Fixture for mocking config.get_str('journaldir')."""
def get_str(key: str, *, default: str = None) -> str:
"""Mock config.*Config get_str to provide fake journaldir."""
@ -139,7 +138,7 @@ class TestJournalLock:
self,
monkeypatch: _pytest_monkeypatch,
tmp_path_factory: _pytest_tmpdir.TempPathFactory
) -> py_path_local_LocalPath:
) -> _pytest_tmpdir.TempPathFactory:
"""Fixture for mocking config.get_str('journaldir')."""
def get_str(key: str, *, default: str = None) -> str:
"""Mock config.*Config get_str to provide fake journaldir."""
@ -155,7 +154,7 @@ class TestJournalLock:
###########################################################################
# Tests against JournalLock.__init__()
def test_journal_lock_init(self, mock_journaldir: py_path_local_LocalPath):
def test_journal_lock_init(self, mock_journaldir: _pytest_tmpdir.TempPathFactory):
"""Test JournalLock instantiation."""
print(f'{type(mock_journaldir)=}')
tmpdir = str(mock_journaldir.getbasetemp())
@ -177,7 +176,7 @@ class TestJournalLock:
jlock.set_path_from_journaldir()
assert jlock.journal_dir_path is None
def test_path_from_journaldir_with_tmpdir(self, mock_journaldir: py_path_local_LocalPath):
def test_path_from_journaldir_with_tmpdir(self, mock_journaldir: _pytest_tmpdir.TempPathFactory):
"""Test JournalLock.set_path_from_journaldir() with tmpdir."""
tmpdir = mock_journaldir
@ -201,7 +200,7 @@ class TestJournalLock:
locked = jlock.obtain_lock()
assert locked == JournalLockResult.JOURNALDIR_IS_NONE
def test_obtain_lock_with_tmpdir(self, mock_journaldir: py_path_local_LocalPath):
def test_obtain_lock_with_tmpdir(self, mock_journaldir: _pytest_tmpdir.TempPathFactory):
"""Test JournalLock.obtain_lock() with tmpdir."""
jlock = JournalLock()
@ -214,7 +213,7 @@ class TestJournalLock:
assert jlock.release_lock()
os.unlink(str(jlock.journal_dir_lockfile_name))
def test_obtain_lock_with_tmpdir_ro(self, mock_journaldir: py_path_local_LocalPath):
def test_obtain_lock_with_tmpdir_ro(self, mock_journaldir: _pytest_tmpdir.TempPathFactory):
"""Test JournalLock.obtain_lock() with read-only tmpdir."""
tmpdir = str(mock_journaldir.getbasetemp())
print(f'{tmpdir=}')
@ -281,7 +280,7 @@ class TestJournalLock:
assert locked == JournalLockResult.JOURNALDIR_READONLY
def test_obtain_lock_already_locked(self, mock_journaldir: py_path_local_LocalPath):
def test_obtain_lock_already_locked(self, mock_journaldir: _pytest_tmpdir.TempPathFactory):
"""Test JournalLock.obtain_lock() with tmpdir."""
continue_q: mp.Queue = mp.Queue()
exit_q: mp.Queue = mp.Queue()
@ -313,7 +312,7 @@ class TestJournalLock:
###########################################################################
# Tests against JournalLock.release_lock()
def test_release_lock(self, mock_journaldir: py_path_local_LocalPath):
def test_release_lock(self, mock_journaldir: _pytest_tmpdir.TempPathFactory):
"""Test JournalLock.release_lock()."""
# First actually obtain the lock, and check it worked
jlock = JournalLock()
@ -331,12 +330,12 @@ class TestJournalLock:
# Cleanup, to avoid side-effect on other tests
os.unlink(str(jlock.journal_dir_lockfile_name))
def test_release_lock_not_locked(self, mock_journaldir: py_path_local_LocalPath):
def test_release_lock_not_locked(self, mock_journaldir: _pytest_tmpdir.TempPathFactory):
"""Test JournalLock.release_lock() when not locked."""
jlock = JournalLock()
assert jlock.release_lock()
def test_release_lock_lie_locked(self, mock_journaldir: py_path_local_LocalPath):
def test_release_lock_lie_locked(self, mock_journaldir: _pytest_tmpdir.TempPathFactory):
"""Test JournalLock.release_lock() when not locked, but lie we are."""
jlock = JournalLock()
jlock.locked = True
@ -346,7 +345,7 @@ class TestJournalLock:
# Tests against JournalLock.update_lock()
def test_update_lock(
self,
mock_journaldir_changing: py_path_local_LocalPath):
mock_journaldir_changing: _pytest_tmpdir.TempPathFactory):
"""
Test JournalLock.update_lock().
@ -374,7 +373,7 @@ class TestJournalLock:
# And the old_journaldir's lockfile too
os.unlink(str(old_journaldir_lockfile_name))
def test_update_lock_same(self, mock_journaldir: py_path_local_LocalPath):
def test_update_lock_same(self, mock_journaldir: _pytest_tmpdir.TempPathFactory):
"""
Test JournalLock.update_lock().