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

Merge branch 'stable' into releases

This commit is contained in:
David Sangrey 2024-01-03 19:56:34 -05:00
commit 0e0096771e
No known key found for this signature in database
GPG Key ID: 3AEADBB0186884BC
24 changed files with 109 additions and 58 deletions

@ -53,7 +53,7 @@ jobs:
# Get Python set up
####################################################################
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version-file: '.python-version'
- name: Install dependencies

@ -26,7 +26,7 @@ jobs:
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version-file: '.python-version'
- name: Install dependencies

@ -1,5 +1,5 @@
# vim: tabstop=2 shiftwidth=2
name: Build EDMC for Windows
name: Build EDMC
on:
push:
@ -11,7 +11,7 @@ jobs:
variables:
outputs:
sem_ver: ${{ steps.var.outputs.sem_ver }}
archive_exclusions: ${{ steps.var.outputs.archive_exclusions }}
short_sha: ${{ steps.var.outputs.short_sha }}
runs-on: "ubuntu-latest"
steps:
- name: Setting global variables
@ -20,6 +20,7 @@ jobs:
with:
script: |
core.setOutput('sem_ver', '${{ github.ref_name }}'.replaceAll('Release\/', ''))
core.setOutput('short_sha', '${{ github.sha }}'.substring(0, 8))
linux_build:
needs: [variables]
@ -29,6 +30,9 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: true
- name: Create .gitversion
run: |
echo "${{ needs.variables.outputs.short_sha }}" > .gitversion
- name: Make tar archive
run: |
@ -40,7 +44,11 @@ jobs:
--exclude=EDMarketConnector-release-*.* \
--exclude=.editorconfig \
--exclude=.flake8 \
--exclude=.git* \
--exclude=.gitattributes \
--exclude=.gitignore \
--exclude=.gitmodules \
--exclude=.git \
--exclude=.github \
--exclude=.mypy.ini \
--exclude=.pre-commit-config.yaml \
--exclude=build.py \
@ -93,7 +101,7 @@ jobs:
# presumably due to a Windows CL length limit.
exclusions: 'EDMarketConnector/EDMarketConnector-release-*.* EDMarketConnector/.editorconfig EDMarketConnector/.flake8 EDMarketConnector/.git* EDMarketConnector/.mypy.ini EDMarketConnector/.pre-commit-config.yaml EDMarketConnector/build.py EDMarketConnector/*.manifest EDMarketConnector/coriolis-data/ EDMarketConnector/img/ EDMarketConnector/pyproject.toml EDMarketConnector/scripts/ EDMarketConnector/tests/'
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version-file: '.python-version'
architecture: "x86"
@ -105,9 +113,9 @@ jobs:
- name: Download winsparkle
run: |
Invoke-Webrequest -UseBasicParsing https://github.com/vslavik/winsparkle/releases/download/v0.8.0/WinSparkle-0.8.0.zip -OutFile out.zip
Invoke-Webrequest -UseBasicParsing https://github.com/vslavik/winsparkle/releases/download/v0.8.1/WinSparkle-0.8.1.zip -OutFile out.zip
Expand-Archive out.zip
Move-Item 'out\WinSparkle-0.8.0\Release\*' '.\'
Move-Item 'out\WinSparkle-0.8.1\Release\*' '.\'
- name: Build EDMC
run: |

1
.gitignore vendored

@ -52,6 +52,7 @@ htmlcov/
.coverage
pylintrc
pylint.txt
.pylintrc
# Ignore Submodule data directory
coriolis-data/

@ -1 +1 @@
3.11.1
3.11.7

@ -9,7 +9,7 @@ produce the Windows executables and installer.
---
* We now test against, and package with, Python 3.11.1, 32-bit.
* We now test against, and package with, Python 3.11.7, 32-bit.
**As a consequence of this we no longer support Windows 7.
This is due to
@ -33,6 +33,35 @@ produce the Windows executables and installer.
currently used version in a given branch.
---
Release 5.10.1
===
This release contains a number of bugfixes, minor performance enhancements,
workflow and dependency updates, and a function deprecation.
Note to plugin developers: modules.p and ships.p are deprecated, and slated
for removal in the next major release! Please look for that change coming soon.
Note to plugin developers: The `openurl()` function in ttkHyperlinkLabel has been deprecated,
and slated for removal in the next major release! Please migrate to `webbrowser.open()`.
**Changes and Enhancements**
* Deprecated `openurl()`. Please migrate to `webbrowser.open()`
* Updated a number of list comparisons to use more efficient tuple comparisons
* Updated a few type hints
* Updated a few binary comparitors to be more efficient
* Moved `resources.json` and `modules.json` back to the top level for all users
* Updated several dependencies
* Updated Python version to 3.11.7
**Bug Fixes**
* Fixed an issue where resources files could be in different locations for different users.
* These files are now in the same location (top level) for all users on all distributions.
* Fixed an issue where CMDRs without the Git application installed would crash on start if running from Source.
* Thanks to the Flatpak team for pointing this one out!
* Fixed a bug where CMDRs running from source would have their git hash version displayed as UNKNOWN.
* We're now more failure tolerant and use the bundled .gitversion if no true git hash is provided.
* Fixed a bug where starting two copies of EDMC with a valid install would not generate a duplicate warning.
Release 5.10.0
===
This release contains a number of under-the-hood changes to EDMC designed to improve performance, code

@ -336,6 +336,8 @@ if __name__ == '__main__': # noqa: C901
def already_running_popup():
"""Create the "already running" popup."""
import tkinter as tk
from tkinter import ttk
# Check for CL arg that suppresses this popup.
if args.suppress_dupe_process_popup:
sys.exit(0)
@ -1547,7 +1549,7 @@ class AppWindow:
self.w.update_idletasks()
# Companion login
if entry['event'] in [None, 'StartUp', 'NewCommander', 'LoadGame'] and monitor.cmdr:
if entry['event'] in (None, 'StartUp', 'NewCommander', 'LoadGame') and monitor.cmdr:
if not config.get_list('cmdrs') or monitor.cmdr not in config.get_list('cmdrs'):
config.set('cmdrs', config.get_list('cmdrs', default=[]) + [monitor.cmdr])
self.login()
@ -1565,7 +1567,7 @@ class AppWindow:
logger.trace_if('journal.queue', 'Startup, returning')
return # Startup
if entry['event'] in ['StartUp', 'LoadGame'] and monitor.started:
if entry['event'] in ('StartUp', 'LoadGame') and monitor.started:
logger.info('StartUp or LoadGame event')
# Disable WinSparkle automatic update checks, IFF configured to do so when in-game

@ -77,8 +77,8 @@ def generate_data_files(
"snd_good.wav",
"snd_bad.wav",
"modules.p", # TODO: Remove in 6.0
"resources/modules.json",
"resources/ships.json",
"modules.json",
"ships.json",
"ships.p", # TODO: Remove in 6.0
f"{app_name}.VisualElementsManifest.xml",
f"{app_name}.ico",

@ -54,7 +54,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.10.0'
_static_appversion = '5.10.1'
_cached_version: semantic_version.Version | None = None
copyright = '© 2015-2019 Jonathan Harris, 2020-2023 EDCD'
@ -81,15 +81,15 @@ else:
_T = TypeVar('_T')
def git_shorthash_from_head() -> str:
def git_shorthash_from_head() -> str | None:
"""
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.
:return: str | None: None if we couldn't determine the short hash.
"""
shorthash: str = None # type: ignore
shorthash: str | None = None
try:
git_cmd = subprocess.Popen(
@ -99,14 +99,14 @@ def git_shorthash_from_head() -> str:
)
out, err = git_cmd.communicate()
except subprocess.CalledProcessError as e:
except (subprocess.CalledProcessError, FileNotFoundError) 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
shorthash = None
if shorthash is not None:
with contextlib.suppress(Exception):
@ -134,13 +134,19 @@ def appversion() -> semantic_version.Version:
# 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, encoding='utf-8') as gitv:
shorthash = gitv.read()
shorthash: str | None = gitv.read()
else:
# Running from source
# Running from source. Use git rev-parse --short HEAD
# or fall back to .gitversion file if it exists.
# This is also required for the Flatpak
shorthash = git_shorthash_from_head()
if shorthash is None:
shorthash = 'UNKNOWN'
if pathlib.Path(sys.path[0] + "/" + GITVERSION_FILE).exists():
with open(pathlib.Path(sys.path[0] + "/" + GITVERSION_FILE), encoding='utf-8') as gitv:
shorthash = gitv.read()
else:
shorthash = 'UNKNOWN'
_cached_version = semantic_version.Version(f'{_static_appversion}+{shorthash}')
return _cached_version

@ -57,7 +57,7 @@ if __name__ == "__main__":
modules['_'.join([reverse_ship_map[name], 'armour', bulkhead])] = {'mass': m['bulkheads'][i]['mass']}
ships = OrderedDict([(k, ships[k]) for k in sorted(ships)]) # sort for easier diffing
with open("resources/ships.json", "w") as ships_file:
with open("ships.json", "w") as ships_file:
json.dump(ships, ships_file, indent=4)
# Module masses
@ -92,5 +92,5 @@ if __name__ == "__main__":
add(modules, 'hpt_multicannon_fixed_medium_advanced', {'mass': 4})
modules = OrderedDict([(k, modules[k]) for k in sorted(modules)]) # sort for easier diffing
with open("resources/modules.json", "w") as modules_file:
with open("modules.json", "w") as modules_file:
json.dump(modules, modules_file, indent=4)

@ -24,7 +24,7 @@ __Module = dict[str, Union[str, list[str]]] # Have to keep old-style here for c
ship_map = ship_name_map.copy()
# Ship masses
ships_file = config.respath_path / "resources" / "ships.json"
ships_file = config.respath_path / "ships.json"
with open(ships_file, encoding="utf-8") as ships_file_handle:
ships = json.load(ships_file_handle)

@ -211,12 +211,12 @@ class MacHotkeyMgr(AbstractHotkeyMgr):
return False
# BkSp, Del, Clear = clear hotkey
if keycode in [0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)]:
if keycode in (0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)):
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
return None
# don't allow keys needed for typing in System Map
if keycode in [0x13, 0x20, 0x2d] or 0x61 <= keycode <= 0x7a:
if keycode in (0x13, 0x20, 0x2d) or 0x61 <= keycode <= 0x7a:
NSBeep()
self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE
return None

@ -284,23 +284,23 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr):
| ((GetKeyState(VK_RWIN) & 0x8000) and MOD_WIN)
keycode = event.keycode
if keycode in [VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, VK_RWIN]:
if keycode in (VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, VK_RWIN):
return 0, modifiers
if not modifiers:
if keycode == VK_ESCAPE: # Esc = retain previous
return False
if keycode in [VK_BACK, VK_DELETE, VK_CLEAR, VK_OEM_CLEAR]: # BkSp, Del, Clear = clear hotkey
if keycode in (VK_BACK, VK_DELETE, VK_CLEAR, VK_OEM_CLEAR): # BkSp, Del, Clear = clear hotkey
return None
if (
keycode in [VK_RETURN, VK_SPACE, VK_OEM_MINUS] or ord('A') <= keycode <= ord('Z')
keycode in (VK_RETURN, VK_SPACE, VK_OEM_MINUS) or ord('A') <= keycode <= ord('Z')
): # don't allow keys needed for typing in System Map
winsound.MessageBeep()
return None
if (keycode in [VK_NUMLOCK, VK_SCROLL, VK_PROCESSKEY]
if (keycode in (VK_NUMLOCK, VK_SCROLL, VK_PROCESSKEY)
or VK_CAPITAL <= keycode <= VK_MODECHANGE): # ignore unmodified mode switch keys
return 0, modifiers

@ -25,7 +25,7 @@ from config import config
from EDMCLogging import get_main_logger
if TYPE_CHECKING:
def _(x: str) -> str: ...
def _(x: str) -> str: return x
# Note that this is also done in EDMarketConnector.py, and thus removing this here may not have a desired effect
try:

@ -57,7 +57,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
"""
# Lazily populate
if not moduledata:
modules_path = config.respath_path / "resources" / "modules.json"
modules_path = config.respath_path / "modules.json"
moduledata.update(json.loads(modules_path.read_text()))
if not module.get('name'):
@ -79,7 +79,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
new['rating'] = 'I'
# Skip uninteresting stuff - some no longer present in ED 3.1 cAPI data
elif (name[0] in [
elif (name[0] in (
'bobble',
'decal',
'nameplate',
@ -87,7 +87,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
'enginecustomisation',
'voicepack',
'weaponcustomisation'
]
)
or name[1].startswith('shipkit')):
return None
@ -205,10 +205,10 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0
elif len(name) < 4 and name[1] == 'resourcesiphon': # Hack! 128066402 has no size or class.
(new['class'], new['rating']) = ('1', 'I')
elif len(name) < 4 and name[1] in ['guardianpowerdistributor', 'guardianpowerplant']: # Hack! No class.
elif len(name) < 4 and name[1] in ('guardianpowerdistributor', 'guardianpowerplant'): # Hack! No class.
(new['class'], new['rating']) = (str(name[2][4:]), 'A')
elif len(name) < 4 and name[1] in ['guardianfsdbooster']: # Hack! No class.
elif len(name) < 4 and name[1] == 'guardianfsdbooster': # Hack! No class.
(new['class'], new['rating']) = (str(name[2][4:]), 'H')
else:

@ -165,7 +165,7 @@ def load_plugins(master: tk.Tk) -> None:
def _load_internal_plugins():
internal = []
for name in sorted(os.listdir(config.internal_plugin_dir_path)):
if name.endswith('.py') and name[0] not in ['.', '_']:
if name.endswith('.py') and name[0] not in ('.', '_'):
try:
plugin = Plugin(name[:-3], os.path.join(config.internal_plugin_dir_path, name), logger)
plugin.folder = None
@ -184,7 +184,7 @@ def _load_found_plugins():
for name in sorted(os.listdir(config.plugin_dir_path), key=lambda n: (
not os.path.isfile(os.path.join(config.plugin_dir_path, n, '__init__.py')), n.lower())):
if not os.path.isdir(os.path.join(config.plugin_dir_path, name)) or name[0] in ['.', '_']:
if not os.path.isdir(os.path.join(config.plugin_dir_path, name)) or name[0] in ('.', '_'):
pass
elif name.endswith('.disabled'):
name, discard = name.rsplit('.', 1)

@ -918,7 +918,7 @@ def journal_entry( # noqa: C901, CCR001
])
# optional mission-specific properties
for (iprop, prop) in [
for (iprop, prop) in (
('missionExpiry', 'Expiry'), # Listed as optional in the docs, but always seems to be present
('starsystemNameTarget', 'DestinationSystem'),
('stationNameTarget', 'DestinationStation'),
@ -932,7 +932,7 @@ def journal_entry( # noqa: C901, CCR001
('passengerCount', 'PassengerCount'),
('passengerIsVIP', 'PassengerVIPs'),
('passengerIsWanted', 'PassengerWanted'),
]:
):
if prop in entry:
data[iprop] = entry[prop]
@ -1295,7 +1295,7 @@ def journal_entry( # noqa: C901, CCR001
# Friends
if event_name == 'Friends':
if entry['Status'] in ['Added', 'Online']:
if entry['Status'] in ('Added', 'Online'):
new_add_event(
'addCommanderFriend',
entry['timestamp'],
@ -1305,7 +1305,7 @@ def journal_entry( # noqa: C901, CCR001
}
)
elif entry['Status'] in ['Declined', 'Lost']:
elif entry['Status'] in ('Declined', 'Lost'):
new_add_event(
'delCommanderFriend',
entry['timestamp'],
@ -1666,7 +1666,7 @@ def handle_special_events(data_event: dict[str, Any], reply_event: dict[str, Any
this.lastlocation = reply_event.get('eventData', {})
if not config.shutting_down:
this.system_link.event_generate('<<InaraLocation>>', when="tail")
elif data_event['eventName'] in ['addCommanderShip', 'setCommanderShip']:
elif data_event['eventName'] in ('addCommanderShip', 'setCommanderShip'):
this.lastship = reply_event.get('eventData', {})
if not config.shutting_down:
this.system_link.event_generate('<<InaraShip>>', when="tail")

@ -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.
setuptools==69.0.2
setuptools==69.0.3
# Static analysis tools
flake8==6.1.0
@ -18,17 +18,17 @@ flake8-noqa==1.3.2
flake8-polyfill==1.0.2
flake8-use-fstring==1.4
mypy==1.7.1
mypy==1.8.0
pep8-naming==0.13.3
safety==2.3.5
types-requests==2.31.0.10
types-requests==2.31.0.20231231
types-pkg-resources==0.1.3
# Code formatting tools
autopep8==2.0.4
# Git pre-commit checking
pre-commit==3.5.0
pre-commit==3.6.0
# HTML changelogs
grip==4.6.2
@ -40,7 +40,7 @@ py2exe==0.13.0.1; sys_platform == 'win32'
# Testing
pytest==7.4.3
pytest-cov==4.1.0 # Pytest code coverage support
coverage[toml]==7.3.2 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
coverage[toml]==7.3.4 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
coverage-conditional-plugin==0.9.0
# For manipulating folder permissions and the like.
pywin32==306; sys_platform == 'win32'

@ -5,6 +5,8 @@ Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.
"""
from __future__ import annotations
import csv
import json
import sys
@ -22,7 +24,7 @@ from monitor import monitor
logger = EDMCLogging.get_main_logger()
if TYPE_CHECKING:
def _(x: str) -> str: ...
def _(x: str) -> str: return x
if sys.platform == 'win32':
import ctypes

2
td.py

@ -32,7 +32,7 @@ def export(data: CAPIData) -> None:
with open(data_path / data_filename, 'wb') as h:
# Format described here: https://github.com/eyeonus/Trade-Dangerous/wiki/Price-Data
h.write('#! trade.py import -\n'.encode('utf-8'))
this_platform = sys.platform == 'darwin' and "Mac OS" or system()
this_platform = "Mac OS" if sys.platform == 'darwin' else system()
cmdr_name = data['commander']['name'].strip()
h.write(
f'# Created by {applongname} {appversion()} on {this_platform} for Cmdr {cmdr_name}.\n'.encode('utf-8')

@ -296,7 +296,7 @@ class OldConfig:
None,
ctypes.byref(key_size)
)
or key_type.value not in [REG_SZ, REG_MULTI_SZ]
or key_type.value not in (REG_SZ, REG_MULTI_SZ)
):
return default

@ -22,20 +22,21 @@ from __future__ import annotations
import sys
import tkinter as tk
import warnings
import webbrowser
from tkinter import font as tk_font
from tkinter import ttk
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
def _(x: str) -> str: ...
def _(x: str) -> str: return x
# FIXME: Split this into multi-file module to separate the platforms
class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # type: ignore
"""Clickable label for HTTP links."""
def __init__(self, master: tk.Frame | None = None, **kw: Any) -> None:
def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> None:
"""
Initialize the HyperlinkLabel.
@ -80,10 +81,10 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ
) -> dict[str, tuple[str, str, str, Any, Any]] | None:
"""Change cursor and appearance depending on state and text."""
# This class' state
for thing in ['url', 'popup_copy', 'underline']:
for thing in ('url', 'popup_copy', 'underline'):
if thing in kw:
setattr(self, thing, kw.pop(thing))
for thing in ['foreground', 'disabledforeground']:
for thing in ('foreground', 'disabledforeground'):
if thing in kw:
setattr(self, thing, kw[thing])
@ -135,7 +136,7 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ
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
openurl(url)
webbrowser.open(url)
def _contextmenu(self, event: tk.Event) -> None:
if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy):
@ -183,4 +184,6 @@ def openurl(url: str) -> None:
ended up using `webbrowser.open()` *anyway*.
:param url: URL to open.
"""
warnings.warn("This function is deprecated. "
"Please use `webbrowser.open() instead.", DeprecationWarning, stacklevel=2)
webbrowser.open(url)