mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-12 15:27:14 +03:00
Merge branch 'develop' into enhancement/2114/pathlib-handover
This commit is contained in:
commit
6139d66b8e
3
.gitignore
vendored
3
.gitignore
vendored
@ -9,8 +9,9 @@ build
|
|||||||
dist.win32/
|
dist.win32/
|
||||||
dist.*
|
dist.*
|
||||||
|
|
||||||
# Ignore generated ChangeLog.html file
|
# Ignore generated ChangeLog files
|
||||||
ChangeLog.html
|
ChangeLog.html
|
||||||
|
/scripts/script_output
|
||||||
|
|
||||||
# Ignore files
|
# Ignore files
|
||||||
dump
|
dump
|
||||||
|
12
ChangeLog.md
12
ChangeLog.md
@ -6,6 +6,18 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are
|
|||||||
in the source (not distributed with the Windows installer) for the
|
in the source (not distributed with the Windows installer) for the
|
||||||
currently used version.
|
currently used version.
|
||||||
---
|
---
|
||||||
|
Release 5.11.2
|
||||||
|
===
|
||||||
|
|
||||||
|
This release fixes a bug where minimizing to the system tray could cause the program to not un-minimize.
|
||||||
|
|
||||||
|
**Changes and Enhancements**
|
||||||
|
* Updated Translations
|
||||||
|
* Added a developer utility to help speed up changelog development
|
||||||
|
|
||||||
|
**Bug Fixes**
|
||||||
|
* Fixed a bug where minimizing to the system tray could cause the program to not un-minimize.
|
||||||
|
|
||||||
Release 5.11.1
|
Release 5.11.1
|
||||||
===
|
===
|
||||||
|
|
||||||
|
@ -42,7 +42,6 @@ import logging
|
|||||||
import logging.handlers
|
import logging.handlers
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import tempfile
|
|
||||||
import warnings
|
import warnings
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
@ -52,9 +51,7 @@ from threading import get_native_id as thread_native_id
|
|||||||
from time import gmtime
|
from time import gmtime
|
||||||
from traceback import print_exc
|
from traceback import print_exc
|
||||||
from typing import TYPE_CHECKING, cast
|
from typing import TYPE_CHECKING, cast
|
||||||
|
from config import appcmdname, appname, config, trace_on
|
||||||
import config as config_mod
|
|
||||||
from config import appcmdname, appname, config
|
|
||||||
|
|
||||||
# TODO: Tests:
|
# TODO: Tests:
|
||||||
#
|
#
|
||||||
@ -105,7 +102,7 @@ warnings.simplefilter('default', DeprecationWarning)
|
|||||||
|
|
||||||
|
|
||||||
def _trace_if(self: logging.Logger, condition: str, message: str, *args, **kwargs) -> None:
|
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):
|
if any(fnmatch(condition, p) for p in trace_on):
|
||||||
self._log(logging.TRACE, message, args, **kwargs) # type: ignore # we added it
|
self._log(logging.TRACE, message, args, **kwargs) # type: ignore # we added it
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -185,8 +182,7 @@ class Logger:
|
|||||||
# We want the files in %TEMP%\{appname}\ as {logger_name}-debug.log and
|
# We want the files in %TEMP%\{appname}\ as {logger_name}-debug.log and
|
||||||
# rotated versions.
|
# rotated versions.
|
||||||
# This is {logger_name} so that EDMC.py logs to a different file.
|
# This is {logger_name} so that EDMC.py logs to a different file.
|
||||||
logfile_rotating = pathlib.Path(tempfile.gettempdir())
|
logfile_rotating = pathlib.Path(config.app_dir_path / 'logs')
|
||||||
logfile_rotating /= f'{appname}'
|
|
||||||
logfile_rotating.mkdir(exist_ok=True)
|
logfile_rotating.mkdir(exist_ok=True)
|
||||||
logfile_rotating /= f'{logger_name}-debug.log'
|
logfile_rotating /= f'{logger_name}-debug.log'
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import tempfile
|
|
||||||
from os import chdir, environ
|
from os import chdir, environ
|
||||||
from time import localtime, strftime, time
|
from time import localtime, strftime, time
|
||||||
from typing import TYPE_CHECKING, Any, Literal
|
from typing import TYPE_CHECKING, Any, Literal
|
||||||
@ -42,16 +41,19 @@ else:
|
|||||||
# not frozen.
|
# not frozen.
|
||||||
chdir(pathlib.Path(__file__).parent)
|
chdir(pathlib.Path(__file__).parent)
|
||||||
|
|
||||||
|
|
||||||
# config will now cause an appname logger to be set up, so we need the
|
# config will now cause an appname logger to be set up, so we need the
|
||||||
# console redirect before this
|
# console redirect before this
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Keep this as the very first code run to be as sure as possible of no
|
# Keep this as the very first code run to be as sure as possible of no
|
||||||
# output until after this redirect is done, if needed.
|
# output until after this redirect is done, if needed.
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
|
from config import config
|
||||||
# By default py2exe tries to write log to dirname(sys.executable) which fails when installed
|
# By default py2exe tries to write log to dirname(sys.executable) which fails when installed
|
||||||
# unbuffered not allowed for text in python3, so use `1 for line buffering
|
# unbuffered not allowed for text in python3, so use `1 for line buffering
|
||||||
log_file_path = pathlib.Path(tempfile.gettempdir()) / f'{appname}.log'
|
log_file_path = pathlib.Path(config.app_dir_path / 'logs')
|
||||||
|
log_file_path.mkdir(exist_ok=True)
|
||||||
|
log_file_path /= f'{appname}.log'
|
||||||
|
|
||||||
sys.stdout = sys.stderr = open(log_file_path, mode='wt', buffering=1) # Do NOT use WITH here.
|
sys.stdout = sys.stderr = open(log_file_path, mode='wt', buffering=1) # Do NOT use WITH here.
|
||||||
# TODO: Test: Make *sure* this redirect is working, else py2exe is going to cause an exit popup
|
# TODO: Test: Make *sure* this redirect is working, else py2exe is going to cause an exit popup
|
||||||
|
|
||||||
@ -454,7 +456,7 @@ class AppWindow:
|
|||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
from simplesystray import SysTrayIcon
|
from simplesystray import SysTrayIcon
|
||||||
|
|
||||||
def open_window(systray: 'SysTrayIcon') -> None:
|
def open_window(systray: 'SysTrayIcon', *args) -> None:
|
||||||
self.w.deiconify()
|
self.w.deiconify()
|
||||||
|
|
||||||
menu_options = (("Open", None, open_window),)
|
menu_options = (("Open", None, open_window),)
|
||||||
@ -619,7 +621,7 @@ class AppWindow:
|
|||||||
self.help_menu.add_command(command=lambda: self.updater.check_for_updates()) # Check for Updates...
|
self.help_menu.add_command(command=lambda: self.updater.check_for_updates()) # Check for Updates...
|
||||||
# About E:D Market Connector
|
# About E:D Market Connector
|
||||||
self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w))
|
self.help_menu.add_command(command=lambda: not self.HelpAbout.showing and self.HelpAbout(self.w))
|
||||||
logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname
|
logfile_loc = pathlib.Path(config.app_dir_path / 'logs')
|
||||||
self.help_menu.add_command(command=lambda: prefs.open_folder(logfile_loc)) # Open Log Folder
|
self.help_menu.add_command(command=lambda: prefs.open_folder(logfile_loc)) # Open Log Folder
|
||||||
self.help_menu.add_command(command=lambda: prefs.help_open_system_profiler(self)) # Open Log Folde
|
self.help_menu.add_command(command=lambda: prefs.help_open_system_profiler(self)) # Open Log Folde
|
||||||
|
|
||||||
@ -845,9 +847,20 @@ class AppWindow:
|
|||||||
)
|
)
|
||||||
update_msg = update_msg.replace('\\n', '\n')
|
update_msg = update_msg.replace('\\n', '\n')
|
||||||
update_msg = update_msg.replace('\\r', '\r')
|
update_msg = update_msg.replace('\\r', '\r')
|
||||||
stable_popup = tk.messagebox.askyesno(title=title, message=update_msg, parent=postargs.get('Parent'))
|
stable_popup = tk.messagebox.askyesno(title=title, message=update_msg)
|
||||||
if stable_popup:
|
if stable_popup:
|
||||||
webbrowser.open("https://github.com/edCD/eDMarketConnector/releases/latest")
|
webbrowser.open("https://github.com/EDCD/eDMarketConnector/releases/latest")
|
||||||
|
if postargs.get('Restart_Req'):
|
||||||
|
# LANG: Text of Notification Popup for EDMC Restart
|
||||||
|
restart_msg = tr.tl('A restart of EDMC is required. EDMC will now restart.')
|
||||||
|
restart_box = tk.messagebox.Message(
|
||||||
|
title=tr.tl('Restart Required'), # LANG: Title of Notification Popup for EDMC Restart
|
||||||
|
message=restart_msg,
|
||||||
|
type=tk.messagebox.OK
|
||||||
|
)
|
||||||
|
restart_box.show()
|
||||||
|
if restart_box:
|
||||||
|
app.onexit(restart=True)
|
||||||
|
|
||||||
def set_labels(self):
|
def set_labels(self):
|
||||||
"""Set main window labels, e.g. after language change."""
|
"""Set main window labels, e.g. after language change."""
|
||||||
@ -1851,7 +1864,7 @@ class AppWindow:
|
|||||||
)
|
)
|
||||||
exit_thread.start()
|
exit_thread.start()
|
||||||
|
|
||||||
def onexit(self, event=None) -> None:
|
def onexit(self, event=None, restart: bool = False) -> None:
|
||||||
"""Application shutdown procedure."""
|
"""Application shutdown procedure."""
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
shutdown_thread = threading.Thread(
|
shutdown_thread = threading.Thread(
|
||||||
@ -1914,6 +1927,8 @@ class AppWindow:
|
|||||||
self.w.destroy()
|
self.w.destroy()
|
||||||
|
|
||||||
logger.info('Done.')
|
logger.info('Done.')
|
||||||
|
if restart:
|
||||||
|
os.execv(sys.executable, ['python'] + sys.argv)
|
||||||
|
|
||||||
def drag_start(self, event) -> None:
|
def drag_start(self, event) -> None:
|
||||||
"""Initiate dragging the window."""
|
"""Initiate dragging the window."""
|
||||||
|
@ -468,8 +468,11 @@
|
|||||||
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
/* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */
|
||||||
"Plugins folder" = "Plugins folder";
|
"Plugins folder" = "Plugins folder";
|
||||||
|
|
||||||
/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */
|
/* prefs.py: Label on button used to open the Plugin Folder; */
|
||||||
"Open" = "Open";
|
"Open Plugins Folder" = "Open Plugins Folder";
|
||||||
|
|
||||||
|
/* prefs.py: Selecting the Location of the Plugin Directory on the Filesystem; */
|
||||||
|
"Plugin Directory Location" = "Plugin Directory Location";
|
||||||
|
|
||||||
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
/* prefs.py: Tip/label about how to disable plugins; In files: prefs.py:923; */
|
||||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name";
|
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name" = "Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name";
|
||||||
@ -804,12 +807,20 @@
|
|||||||
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
/* EDMarketConnector.py: Inform the user the Update Track has changed; */
|
||||||
"Update Track Changed to {TRACK}" = "Update Track Changed to {TRACK}";
|
"Update Track Changed to {TRACK}" = "Update Track Changed to {TRACK}";
|
||||||
|
|
||||||
|
|
||||||
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
/* EDMarketConnector.py: Inform User of Beta -> Stable Transition Risks; */
|
||||||
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?";
|
"Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?" = "Update track changed to Stable from Beta. You will no longer receive Beta updates. You will stay on your current Beta version until the next Stable release.\r\n\r\nYou can manually revert to the latest Stable version. To do so, you must download and install the latest Stable version manually. Note that this may introduce bugs or break completely if downgrading between major versions with significant changes.\r\n\r\nDo you want to open GitHub to download the latest release?";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Title of Notification Popup for EDMC Restart; */
|
||||||
|
"Restart Required" = "Restart Required";
|
||||||
|
|
||||||
|
/* EDMarketConnector.py: Text of Notification Popup for EDMC Restart; */
|
||||||
|
"A restart of EDMC is required. EDMC will now restart." = "A restart of EDMC is required. EDMC will now restart.";
|
||||||
|
|
||||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||||
"Cannot paste non-text content." = "Cannot paste non-text content.";
|
"Cannot paste non-text content." = "Cannot paste non-text content.";
|
||||||
|
|
||||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||||
"Open in {URL}" = "Open in {URL}";
|
"Open in {URL}" = "Open in {URL}";
|
||||||
|
|
||||||
|
/* ttkHyperlinkLabel.py: Copy the Inara SLEF Format of the active ship to the clipboard; */
|
||||||
|
"Copy Inara SLEF" = "Copy Inara SLEF";
|
||||||
|
@ -52,7 +52,7 @@ appcmdname = 'EDMC'
|
|||||||
# <https://semver.org/#semantic-versioning-specification-semver>
|
# <https://semver.org/#semantic-versioning-specification-semver>
|
||||||
# Major.Minor.Patch(-prerelease)(+buildmetadata)
|
# Major.Minor.Patch(-prerelease)(+buildmetadata)
|
||||||
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
|
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
|
||||||
_static_appversion = '5.11.1'
|
_static_appversion = '5.11.2'
|
||||||
_cached_version: semantic_version.Version | None = None
|
_cached_version: semantic_version.Version | None = None
|
||||||
copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'
|
copyright = '© 2015-2019 Jonathan Harris, 2020-2024 EDCD'
|
||||||
|
|
||||||
@ -189,6 +189,7 @@ class AbstractConfig(abc.ABC):
|
|||||||
|
|
||||||
app_dir_path: pathlib.Path
|
app_dir_path: pathlib.Path
|
||||||
plugin_dir_path: pathlib.Path
|
plugin_dir_path: pathlib.Path
|
||||||
|
default_plugin_dir_path: pathlib.Path
|
||||||
internal_plugin_dir_path: pathlib.Path
|
internal_plugin_dir_path: pathlib.Path
|
||||||
respath_path: pathlib.Path
|
respath_path: pathlib.Path
|
||||||
home_path: pathlib.Path
|
home_path: pathlib.Path
|
||||||
@ -279,6 +280,11 @@ class AbstractConfig(abc.ABC):
|
|||||||
"""Return a string version of plugin_dir."""
|
"""Return a string version of plugin_dir."""
|
||||||
return str(self.plugin_dir_path)
|
return str(self.plugin_dir_path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_plugin_dir(self) -> str:
|
||||||
|
"""Return a string version of plugin_dir."""
|
||||||
|
return str(self.default_plugin_dir_path)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def internal_plugin_dir(self) -> str:
|
def internal_plugin_dir(self) -> str:
|
||||||
"""Return a string version of internal_plugin_dir."""
|
"""Return a string version of internal_plugin_dir."""
|
||||||
|
@ -31,9 +31,7 @@ class LinuxConfig(AbstractConfig):
|
|||||||
self.app_dir_path = xdg_data_home / appname
|
self.app_dir_path = xdg_data_home / appname
|
||||||
self.app_dir_path.mkdir(exist_ok=True, parents=True)
|
self.app_dir_path.mkdir(exist_ok=True, parents=True)
|
||||||
|
|
||||||
self.plugin_dir_path = self.app_dir_path / 'plugins'
|
self.default_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.respath_path = pathlib.Path(__file__).parent.parent
|
||||||
|
|
||||||
self.internal_plugin_dir_path = self.respath_path / 'plugins'
|
self.internal_plugin_dir_path = self.respath_path / 'plugins'
|
||||||
@ -62,6 +60,12 @@ class LinuxConfig(AbstractConfig):
|
|||||||
|
|
||||||
self.config.add_section(self.SECTION)
|
self.config.add_section(self.SECTION)
|
||||||
|
|
||||||
|
if (plugdir_str := self.get_str('plugin_dir')) is None or not pathlib.Path(plugdir_str).is_dir():
|
||||||
|
self.set("plugin_dir", str(self.default_plugin_dir_path))
|
||||||
|
plugdir_str = self.default_plugin_dir
|
||||||
|
self.plugin_dir_path = pathlib.Path(plugdir_str)
|
||||||
|
self.plugin_dir_path.mkdir(exist_ok=True)
|
||||||
|
|
||||||
if (outdir := self.get_str('outdir')) is None or not pathlib.Path(outdir).is_dir():
|
if (outdir := self.get_str('outdir')) is None or not pathlib.Path(outdir).is_dir():
|
||||||
self.set('outdir', self.home)
|
self.set('outdir', self.home)
|
||||||
|
|
||||||
|
@ -49,10 +49,28 @@ class WinConfig(AbstractConfig):
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
REGISTRY_SUBKEY = r'Software\Marginal\EDMarketConnector' # noqa: N806
|
||||||
|
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=REGISTRY_SUBKEY)
|
||||||
|
|
||||||
|
except OSError:
|
||||||
|
logger.exception('Could not create required registry keys')
|
||||||
|
raise
|
||||||
|
|
||||||
self.app_dir_path = pathlib.Path(known_folder_path(FOLDERID_LocalAppData)) / appname # type: ignore
|
self.app_dir_path = pathlib.Path(known_folder_path(FOLDERID_LocalAppData)) / appname # type: ignore
|
||||||
self.app_dir_path.mkdir(exist_ok=True)
|
self.app_dir_path.mkdir(exist_ok=True)
|
||||||
|
|
||||||
self.plugin_dir_path = self.app_dir_path / 'plugins'
|
self.default_plugin_dir_path = self.app_dir_path / 'plugins'
|
||||||
|
if (plugdir_str := self.get_str('plugin_dir')) is None or not pathlib.Path(plugdir_str).is_dir():
|
||||||
|
self.set("plugin_dir", str(self.default_plugin_dir_path))
|
||||||
|
plugdir_str = self.default_plugin_dir
|
||||||
|
self.plugin_dir_path = pathlib.Path(plugdir_str)
|
||||||
self.plugin_dir_path.mkdir(exist_ok=True)
|
self.plugin_dir_path.mkdir(exist_ok=True)
|
||||||
|
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
@ -68,20 +86,6 @@ class WinConfig(AbstractConfig):
|
|||||||
known_folder_path(FOLDERID_SavedGames)) / 'Frontier Developments' / 'Elite Dangerous' # type: ignore
|
known_folder_path(FOLDERID_SavedGames)) / 'Frontier Developments' / 'Elite Dangerous' # type: ignore
|
||||||
self.default_journal_dir_path = journal_dir_path if journal_dir_path.is_dir() else None # type: ignore
|
self.default_journal_dir_path = journal_dir_path if journal_dir_path.is_dir() else None # type: ignore
|
||||||
|
|
||||||
REGISTRY_SUBKEY = r'Software\Marginal\EDMarketConnector' # noqa: N806
|
|
||||||
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=REGISTRY_SUBKEY)
|
|
||||||
|
|
||||||
except OSError:
|
|
||||||
logger.exception('Could not create required registry keys')
|
|
||||||
raise
|
|
||||||
|
|
||||||
self.identifier = applongname
|
self.identifier = applongname
|
||||||
if (outdir_str := self.get_str('outdir')) is None or not pathlib.Path(outdir_str).is_dir():
|
if (outdir_str := self.get_str('outdir')) is None or not pathlib.Path(outdir_str).is_dir():
|
||||||
docs = known_folder_path(FOLDERID_Documents)
|
docs = known_folder_path(FOLDERID_Documents)
|
||||||
|
@ -51,7 +51,8 @@ if __name__ == "__main__":
|
|||||||
for m in list(data['Ships'].values()):
|
for m in list(data['Ships'].values()):
|
||||||
name = coriolis_ship_map.get(m['properties']['name'], str(m['properties']['name']))
|
name = coriolis_ship_map.get(m['properties']['name'], str(m['properties']['name']))
|
||||||
assert name in reverse_ship_map, name
|
assert name in reverse_ship_map, name
|
||||||
ships[name] = {'hullMass': m['properties']['hullMass']}
|
ships[name] = {'hullMass': m['properties']['hullMass'],
|
||||||
|
'reserveFuelCapacity': m['properties']['reserveFuelCapacity']}
|
||||||
for i, bulkhead in enumerate(bulkheads):
|
for i, bulkhead in enumerate(bulkheads):
|
||||||
modules['_'.join([reverse_ship_map[name], 'armour', bulkhead])] = {'mass': m['bulkheads'][i]['mass']}
|
modules['_'.join([reverse_ship_map[name], 'armour', bulkhead])] = {'mass': m['bulkheads'][i]['mass']}
|
||||||
|
|
||||||
|
@ -4,21 +4,19 @@ from __future__ import annotations
|
|||||||
import gzip
|
import gzip
|
||||||
import json
|
import json
|
||||||
import pathlib
|
import pathlib
|
||||||
import tempfile
|
|
||||||
import threading
|
import threading
|
||||||
import zlib
|
import zlib
|
||||||
from http import server
|
from http import server
|
||||||
from typing import Any, Callable, Literal
|
from typing import Any, Callable, Literal
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
|
import config
|
||||||
from config import appname
|
|
||||||
from EDMCLogging import get_main_logger
|
from EDMCLogging import get_main_logger
|
||||||
|
|
||||||
logger = get_main_logger()
|
logger = get_main_logger()
|
||||||
|
|
||||||
output_lock = threading.Lock()
|
output_lock = threading.Lock()
|
||||||
output_data_path = pathlib.Path(tempfile.gettempdir()) / f'{appname}' / 'http_debug'
|
output_data_path = pathlib.Path(config.app_dir_path / 'logs' / 'http_debug')
|
||||||
SAFE_TRANSLATE = str.maketrans({x: '_' for x in "!@#$%^&*()./\\\r\n[]-+='\";:?<>,~`"})
|
SAFE_TRANSLATE = str.maketrans(dict.fromkeys("!@#$%^&*()./\\\r\n[]-+='\";:?<>,~`", '_'))
|
||||||
|
|
||||||
|
|
||||||
class LoggingHandler(server.BaseHTTPRequestHandler):
|
class LoggingHandler(server.BaseHTTPRequestHandler):
|
||||||
|
34
monitor.py
34
monitor.py
@ -20,9 +20,10 @@ from time import gmtime, localtime, mktime, sleep, strftime, strptime, time
|
|||||||
from typing import TYPE_CHECKING, Any, BinaryIO, MutableMapping
|
from typing import TYPE_CHECKING, Any, BinaryIO, MutableMapping
|
||||||
import semantic_version
|
import semantic_version
|
||||||
import util_ships
|
import util_ships
|
||||||
from config import config
|
from config import config, appname, appversion
|
||||||
from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised
|
from edmc_data import edmc_suit_shortnames, edmc_suit_symbol_localised, ship_name_map
|
||||||
from EDMCLogging import get_main_logger
|
from EDMCLogging import get_main_logger
|
||||||
|
from edshipyard import ships
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import tkinter
|
import tkinter
|
||||||
@ -108,6 +109,7 @@ class EDLogs(FileSystemEventHandler):
|
|||||||
self.group: str | None = None
|
self.group: str | None = None
|
||||||
self.cmdr: str | None = None
|
self.cmdr: str | None = None
|
||||||
self.started: int | None = None # Timestamp of the LoadGame event
|
self.started: int | None = None # Timestamp of the LoadGame event
|
||||||
|
self.slef: str | None = None
|
||||||
|
|
||||||
self._navroute_retries_remaining = 0
|
self._navroute_retries_remaining = 0
|
||||||
self._last_navroute_journal_timestamp: float | None = None
|
self._last_navroute_journal_timestamp: float | None = None
|
||||||
@ -701,6 +703,34 @@ class EDLogs(FileSystemEventHandler):
|
|||||||
module.pop('AmmoInHopper')
|
module.pop('AmmoInHopper')
|
||||||
|
|
||||||
self.state['Modules'][module['Slot']] = module
|
self.state['Modules'][module['Slot']] = module
|
||||||
|
# SLEF
|
||||||
|
initial_dict: dict[str, dict[str, Any]] = {
|
||||||
|
"header": {"appName": appname, "appVersion": str(appversion())}
|
||||||
|
}
|
||||||
|
data_dict = {}
|
||||||
|
for module in entry['Modules']:
|
||||||
|
if module.get('Slot') == 'FuelTank':
|
||||||
|
cap = module['Item'].split('size')
|
||||||
|
cap = cap[1].split('_')
|
||||||
|
cap = 2 ** int(cap[0])
|
||||||
|
ship = ship_name_map[entry["Ship"]]
|
||||||
|
fuel = {'Main': cap, 'Reserve': ships[ship]['reserveFuelCapacity']}
|
||||||
|
data_dict.update({"FuelCapacity": fuel})
|
||||||
|
data_dict.update({
|
||||||
|
'Ship': entry["Ship"],
|
||||||
|
'ShipName': entry['ShipName'],
|
||||||
|
'ShipIdent': entry['ShipIdent'],
|
||||||
|
'HullValue': entry['HullValue'],
|
||||||
|
'ModulesValue': entry['ModulesValue'],
|
||||||
|
'Rebuy': entry['Rebuy'],
|
||||||
|
'MaxJumpRange': entry['MaxJumpRange'],
|
||||||
|
'UnladenMass': entry['UnladenMass'],
|
||||||
|
'CargoCapacity': entry['CargoCapacity'],
|
||||||
|
'Modules': entry['Modules'],
|
||||||
|
})
|
||||||
|
initial_dict.update({'data': data_dict})
|
||||||
|
output = json.dumps(initial_dict, indent=4)
|
||||||
|
self.slef = str(f"[{output}]")
|
||||||
|
|
||||||
elif event_type == 'modulebuy':
|
elif event_type == 'modulebuy':
|
||||||
self.state['Modules'][entry['Slot']] = {
|
self.state['Modules'][entry['Slot']] = {
|
||||||
|
93
prefs.py
93
prefs.py
@ -8,7 +8,6 @@ from os.path import expandvars
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import warnings
|
import warnings
|
||||||
from os import system
|
from os import system
|
||||||
@ -20,7 +19,6 @@ import myNotebook as nb # noqa: N813
|
|||||||
import plug
|
import plug
|
||||||
from config import appversion_nobuild, config
|
from config import appversion_nobuild, config
|
||||||
from EDMCLogging import edmclogger, get_main_logger
|
from EDMCLogging import edmclogger, get_main_logger
|
||||||
from constants import appname
|
|
||||||
from hotkey import hotkeymgr
|
from hotkey import hotkeymgr
|
||||||
from l10n import translations as tr
|
from l10n import translations as tr
|
||||||
from monitor import monitor
|
from monitor import monitor
|
||||||
@ -43,7 +41,7 @@ def help_open_log_folder() -> None:
|
|||||||
"""Open the folder logs are stored in."""
|
"""Open the folder logs are stored in."""
|
||||||
warnings.warn('prefs.help_open_log_folder is deprecated, use open_log_folder instead. '
|
warnings.warn('prefs.help_open_log_folder is deprecated, use open_log_folder instead. '
|
||||||
'This function will be removed in 6.0 or later', DeprecationWarning, stacklevel=2)
|
'This function will be removed in 6.0 or later', DeprecationWarning, stacklevel=2)
|
||||||
open_folder(Path(tempfile.gettempdir()) / appname)
|
open_folder(pathlib.Path(config.app_dir_path / 'logs'))
|
||||||
|
|
||||||
|
|
||||||
def open_folder(file: Path) -> None:
|
def open_folder(file: Path) -> None:
|
||||||
@ -239,6 +237,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
|
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
self.req_restart = False
|
||||||
# LANG: File > Settings (macOS)
|
# LANG: File > Settings (macOS)
|
||||||
self.title(tr.tl('Settings'))
|
self.title(tr.tl('Settings'))
|
||||||
|
|
||||||
@ -323,7 +322,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
self.geometry(f"+{position.left}+{position.top}")
|
self.geometry(f"+{position.left}+{position.top}")
|
||||||
|
|
||||||
# Set Log Directory
|
# Set Log Directory
|
||||||
self.logfile_loc = Path(tempfile.gettempdir()) / appname
|
self.logfile_loc = pathlib.Path(config.app_dir_path / 'logs')
|
||||||
|
|
||||||
# Set minimum size to prevent content cut-off
|
# Set minimum size to prevent content cut-off
|
||||||
self.update_idletasks() # Update "requested size" from geometry manager
|
self.update_idletasks() # Update "requested size" from geometry manager
|
||||||
@ -911,20 +910,15 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
# Plugin settings and info
|
# Plugin settings and info
|
||||||
plugins_frame = nb.Frame(notebook)
|
plugins_frame = nb.Frame(notebook)
|
||||||
plugins_frame.columnconfigure(0, weight=1)
|
plugins_frame.columnconfigure(0, weight=1)
|
||||||
plugdir = tk.StringVar()
|
|
||||||
plugdir.set(config.plugin_dir)
|
|
||||||
row = AutoInc(start=0)
|
row = AutoInc(start=0)
|
||||||
|
self.plugdir = tk.StringVar()
|
||||||
# Section heading in settings
|
self.plugdir.set(str(config.get_str('plugin_dir')))
|
||||||
# LANG: Label for location of third-party plugins folder
|
# LANG: Label for location of third-party plugins folder
|
||||||
nb.Label(plugins_frame, text=tr.tl('Plugins folder') + ':').grid(
|
self.plugdir_label = nb.Label(plugins_frame, text=tr.tl('Plugins folder') + ':')
|
||||||
padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get()
|
self.plugdir_label.grid(padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get())
|
||||||
)
|
self.plugdir_entry = ttk.Entry(plugins_frame, takefocus=False,
|
||||||
|
textvariable=self.plugdir) # Link StringVar to Entry widget
|
||||||
plugdirentry = ttk.Entry(plugins_frame, justify=tk.LEFT)
|
self.plugdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
||||||
self.displaypath(plugdir, plugdirentry)
|
|
||||||
plugdirentry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get())
|
|
||||||
|
|
||||||
with row as cur_row:
|
with row as cur_row:
|
||||||
nb.Label(
|
nb.Label(
|
||||||
plugins_frame,
|
plugins_frame,
|
||||||
@ -932,19 +926,41 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
# LANG: Tip/label about how to disable plugins
|
# LANG: Tip/label about how to disable plugins
|
||||||
text=tr.tl(
|
text=tr.tl(
|
||||||
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')
|
"Tip: You can disable a plugin by{CR}adding '{EXT}' to its folder name").format(EXT='.disabled')
|
||||||
).grid(columnspan=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
).grid(columnspan=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||||
|
|
||||||
ttk.Button(
|
# Open Plugin Folder Button
|
||||||
|
self.open_plug_folder_btn = ttk.Button(
|
||||||
plugins_frame,
|
plugins_frame,
|
||||||
# LANG: Label on button used to open a filesystem folder
|
# LANG: Label on button used to open the Plugin Folder
|
||||||
text=tr.tl('Open'), # Button that opens a folder in Explorer/Finder
|
text=tr.tl('Open Plugins Folder'),
|
||||||
command=lambda: open_folder(config.plugin_dir_path)
|
command=lambda: open_folder(config.plugin_dir_path)
|
||||||
).grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.N, row=cur_row)
|
)
|
||||||
|
self.open_plug_folder_btn.grid(column=1, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||||
|
|
||||||
|
# Browse Button
|
||||||
|
text = tr.tl('Browse...') # LANG: NOT-macOS Settings - files location selection button
|
||||||
|
self.plugbutton = ttk.Button(
|
||||||
|
plugins_frame,
|
||||||
|
text=text,
|
||||||
|
# LANG: Selecting the Location of the Plugin Directory on the Filesystem
|
||||||
|
command=lambda: self.filebrowse(tr.tl('Plugin Directory Location'), self.plugdir)
|
||||||
|
)
|
||||||
|
self.plugbutton.grid(column=2, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||||
|
|
||||||
|
if config.default_journal_dir_path:
|
||||||
|
# Appearance theme and language setting
|
||||||
|
ttk.Button(
|
||||||
|
plugins_frame,
|
||||||
|
# LANG: Settings > Configuration - Label on 'reset journal files location to default' button
|
||||||
|
text=tr.tl('Default'),
|
||||||
|
command=self.plugdir_reset,
|
||||||
|
state=tk.NORMAL if config.get_str('plugin_dir') else tk.DISABLED
|
||||||
|
).grid(column=3, padx=self.PADX, pady=self.PADY, sticky=tk.EW, row=cur_row)
|
||||||
|
|
||||||
enabled_plugins = list(filter(lambda x: x.folder and x.module, plug.PLUGINS))
|
enabled_plugins = list(filter(lambda x: x.folder and x.module, plug.PLUGINS))
|
||||||
if enabled_plugins:
|
if enabled_plugins:
|
||||||
ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid(
|
ttk.Separator(plugins_frame, orient=tk.HORIZONTAL).grid(
|
||||||
columnspan=3, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
columnspan=4, padx=self.PADX, pady=self.SEPY, sticky=tk.EW, row=row.get()
|
||||||
)
|
)
|
||||||
nb.Label(
|
nb.Label(
|
||||||
plugins_frame,
|
plugins_frame,
|
||||||
@ -1122,6 +1138,13 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
|
|
||||||
self.outvarchanged()
|
self.outvarchanged()
|
||||||
|
|
||||||
|
def plugdir_reset(self) -> None:
|
||||||
|
"""Reset the log dir to the default."""
|
||||||
|
if config.default_plugin_dir_path:
|
||||||
|
self.plugdir.set(config.default_plugin_dir)
|
||||||
|
|
||||||
|
self.outvarchanged()
|
||||||
|
|
||||||
def disable_autoappupdatecheckingame_changed(self) -> None:
|
def disable_autoappupdatecheckingame_changed(self) -> None:
|
||||||
"""Save out the auto update check in game config."""
|
"""Save out the auto update check in game config."""
|
||||||
config.set('disable_autoappupdatecheckingame', self.disable_autoappupdatecheckingame.get())
|
config.set('disable_autoappupdatecheckingame', self.disable_autoappupdatecheckingame.get())
|
||||||
@ -1217,7 +1240,7 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
|
|
||||||
return 'break' # stops further processing - insertion, Tab traversal etc
|
return 'break' # stops further processing - insertion, Tab traversal etc
|
||||||
|
|
||||||
def apply(self) -> None:
|
def apply(self) -> None: # noqa: CCR001
|
||||||
"""Update the config with the options set on the dialog."""
|
"""Update the config with the options set on the dialog."""
|
||||||
config.set('PrefsVersion', prefsVersion.stringToSerial(appversion_nobuild()))
|
config.set('PrefsVersion', prefsVersion.stringToSerial(appversion_nobuild()))
|
||||||
config.set(
|
config.set(
|
||||||
@ -1273,20 +1296,30 @@ class PreferencesDialog(tk.Toplevel):
|
|||||||
config.set('dark_text', self.theme_colors[0])
|
config.set('dark_text', self.theme_colors[0])
|
||||||
config.set('dark_highlight', self.theme_colors[1])
|
config.set('dark_highlight', self.theme_colors[1])
|
||||||
theme.apply(self.parent)
|
theme.apply(self.parent)
|
||||||
|
if self.plugdir.get() != config.get('plugin_dir'):
|
||||||
|
config.set(
|
||||||
|
'plugin_dir',
|
||||||
|
join(config.home_path, self.plugdir.get()[2:]) if self.plugdir.get().startswith(
|
||||||
|
'~') else self.plugdir.get()
|
||||||
|
)
|
||||||
|
self.req_restart = True
|
||||||
|
|
||||||
# Send to the Post Config if we updated the update branch
|
|
||||||
post_flags = {
|
|
||||||
'Update': True if self.curr_update_track != self.update_paths.get() else False,
|
|
||||||
'Track': self.update_paths.get(),
|
|
||||||
'Parent': self
|
|
||||||
}
|
|
||||||
# Notify
|
# Notify
|
||||||
if self.callback:
|
if self.callback:
|
||||||
self.callback(**post_flags)
|
self.callback()
|
||||||
|
|
||||||
plug.notify_prefs_changed(monitor.cmdr, monitor.is_beta)
|
plug.notify_prefs_changed(monitor.cmdr, monitor.is_beta)
|
||||||
|
|
||||||
self._destroy()
|
self._destroy()
|
||||||
|
# Send to the Post Config if we updated the update branch or need to restart
|
||||||
|
post_flags = {
|
||||||
|
'Update': True if self.curr_update_track != self.update_paths.get() else False,
|
||||||
|
'Track': self.update_paths.get(),
|
||||||
|
'Parent': self,
|
||||||
|
'Restart_Req': True if self.req_restart else False
|
||||||
|
}
|
||||||
|
if self.callback:
|
||||||
|
self.callback(**post_flags)
|
||||||
|
|
||||||
def _destroy(self) -> None:
|
def _destroy(self) -> None:
|
||||||
"""widget.destroy wrapper that does some extra cleanup."""
|
"""widget.destroy wrapper that does some extra cleanup."""
|
||||||
|
@ -8,10 +8,10 @@ wheel
|
|||||||
setuptools==70.0.0
|
setuptools==70.0.0
|
||||||
|
|
||||||
# Static analysis tools
|
# Static analysis tools
|
||||||
flake8==7.0.0
|
flake8==7.1.0
|
||||||
flake8-annotations-coverage==0.0.6
|
flake8-annotations-coverage==0.0.6
|
||||||
flake8-cognitive-complexity==0.1.0
|
flake8-cognitive-complexity==0.1.0
|
||||||
flake8-comprehensions==3.14.0
|
flake8-comprehensions==3.15.0
|
||||||
flake8-docstrings==1.7.0
|
flake8-docstrings==1.7.0
|
||||||
flake8-json==24.4.0
|
flake8-json==24.4.0
|
||||||
flake8-noqa==1.4.0
|
flake8-noqa==1.4.0
|
||||||
@ -19,7 +19,7 @@ flake8-polyfill==1.0.2
|
|||||||
flake8-use-fstring==1.4
|
flake8-use-fstring==1.4
|
||||||
|
|
||||||
mypy==1.10.0
|
mypy==1.10.0
|
||||||
pep8-naming==0.13.3
|
pep8-naming==0.14.1
|
||||||
safety==3.2.0
|
safety==3.2.0
|
||||||
types-requests==2.31.0.20240311
|
types-requests==2.31.0.20240311
|
||||||
types-pkg-resources==0.1.3
|
types-pkg-resources==0.1.3
|
||||||
@ -31,14 +31,14 @@ autopep8==2.2.0
|
|||||||
pre-commit==3.7.1
|
pre-commit==3.7.1
|
||||||
|
|
||||||
# HTML changelogs
|
# HTML changelogs
|
||||||
grip==4.6.2
|
mistune==3.0.2
|
||||||
|
|
||||||
# Packaging
|
# Packaging
|
||||||
# We only need py2exe on windows.
|
# We only need py2exe on windows.
|
||||||
py2exe==0.13.0.1; sys_platform == 'win32'
|
py2exe==0.13.0.1; sys_platform == 'win32'
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
pytest==8.2.0
|
pytest==8.2.2
|
||||||
pytest-cov==5.0.0 # Pytest code coverage support
|
pytest-cov==5.0.0 # Pytest code coverage support
|
||||||
coverage[toml]==7.5.0 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
|
coverage[toml]==7.5.0 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs
|
||||||
coverage-conditional-plugin==0.9.0
|
coverage-conditional-plugin==0.9.0
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
pillow==10.3.0
|
pillow==10.3.0
|
||||||
watchdog==4.0.0
|
watchdog==4.0.1
|
||||||
simplesystray==0.1.0; sys_platform == 'win32'
|
simplesystray==0.1.0; sys_platform == 'win32'
|
||||||
semantic-version==2.10.0
|
semantic-version==2.10.0
|
||||||
|
165
scripts/build_changelog.py
Normal file
165
scripts/build_changelog.py
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# flake8: noqa
|
||||||
|
"""
|
||||||
|
build_changelog.py - Read the latest changelog file and format for the Forums and Reddit.
|
||||||
|
|
||||||
|
Copyright (c) EDCD, All Rights Reserved
|
||||||
|
Licensed under the GNU General Public License.
|
||||||
|
See LICENSE file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
from os import chdir
|
||||||
|
import mistune
|
||||||
|
|
||||||
|
|
||||||
|
def get_changelog() -> tuple[str, str]:
|
||||||
|
"""Pull the last full changelog details in MD."""
|
||||||
|
try:
|
||||||
|
with open("../CHANGELOG.md", encoding="utf-8") as changelog_file:
|
||||||
|
content = changelog_file.read()
|
||||||
|
except FileNotFoundError as exc:
|
||||||
|
raise FileNotFoundError("Changelog file not found.") from exc
|
||||||
|
|
||||||
|
changelog_list = content.split("---", maxsplit=2)
|
||||||
|
if len(changelog_list) < 3:
|
||||||
|
raise ValueError("Changelog format is incorrect.")
|
||||||
|
|
||||||
|
changelog = changelog_list[2].split("===", maxsplit=2)
|
||||||
|
if len(changelog) < 2:
|
||||||
|
raise ValueError("Changelog format is incorrect.")
|
||||||
|
|
||||||
|
changelog[0] = changelog[0].strip()
|
||||||
|
changelog[1] = "\n".join(changelog[1].strip().split("\n")[:-2])
|
||||||
|
version = changelog[0]
|
||||||
|
version = version.split(" ")[1]
|
||||||
|
final_changelog = changelog[1].strip()
|
||||||
|
|
||||||
|
return final_changelog, version
|
||||||
|
|
||||||
|
|
||||||
|
def build_html(md_changelog: str, version: str) -> str:
|
||||||
|
"""Convert markdown changelog to HTML."""
|
||||||
|
html_out = f"<h2>Release {version}</h2>\n"
|
||||||
|
html_out += mistune.html(md_changelog)
|
||||||
|
html_out = re.sub(r"h1", "h2", html_out) + "\n<hr>"
|
||||||
|
|
||||||
|
with open("script_output/html_changelog.txt", "w", encoding="utf-8") as html_file:
|
||||||
|
html_file.write(html_out)
|
||||||
|
|
||||||
|
return html_out
|
||||||
|
|
||||||
|
|
||||||
|
def format_fdev(md_log: str) -> str:
|
||||||
|
"""Format changelog for FDEV forums."""
|
||||||
|
md_log = re.sub(r"<p>|</p>", "", md_log)
|
||||||
|
md_log = re.sub(r"<strong>", "\n[HEADING=3]", md_log)
|
||||||
|
md_log = re.sub(r"</strong>", "[/HEADING]", md_log)
|
||||||
|
md_log = re.sub(r"<ul>", "[LIST]", md_log)
|
||||||
|
md_log = re.sub(r"<li>", "[*]", md_log)
|
||||||
|
md_log = re.sub(r"</li>", "", md_log)
|
||||||
|
md_log = re.sub(r"<code>", "[ICODE]", md_log)
|
||||||
|
md_log = re.sub(r"</code>", "[/ICODE]", md_log)
|
||||||
|
md_log = re.sub(r"</ul>\n", "[/LIST]", md_log)
|
||||||
|
md_log = re.sub(r"<hr>", "", md_log)
|
||||||
|
md_log = re.sub(r"Changes and Enhancements", "What's Changed", md_log)
|
||||||
|
return md_log
|
||||||
|
|
||||||
|
|
||||||
|
def build_fdev(
|
||||||
|
vt_signed: str, vt_unsigned: str, version: str, gh_link: str, html: str
|
||||||
|
) -> None:
|
||||||
|
"""Build changelog for FDEV forums."""
|
||||||
|
fdev_out = (
|
||||||
|
f"[HEADING=2][URL='{gh_link}'][SIZE=7]Release {version}[/SIZE][/URL][/HEADING]\n"
|
||||||
|
f"[URL='{vt_signed}']Pre-emptive upload to VirusTotal[/URL]. "
|
||||||
|
f"([URL='{vt_unsigned}']Unsigned Installer[/URL])\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
if version.startswith(("Pre-Release", "Beta")):
|
||||||
|
fdev_out += (
|
||||||
|
f'This is a release candidate for {version}. It has been pushed to the "Beta" track for updates!\n\n'
|
||||||
|
'For more information on the "Beta" update track, please read '
|
||||||
|
"[URL='https://github.com/EDCD/EDMarketConnector/wiki/Participating-in-Open-Betas-of-EDMC']"
|
||||||
|
"This Wiki Article[/URL]. Questions and comments are welcome!\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
md_log = html.split("\n", maxsplit=1)[1]
|
||||||
|
md_log = format_fdev(md_log)
|
||||||
|
fdev_out += md_log
|
||||||
|
|
||||||
|
with open("script_output/fdev_changelog.txt", "w", encoding="utf-8") as fdev_file:
|
||||||
|
fdev_file.write(fdev_out)
|
||||||
|
|
||||||
|
|
||||||
|
def build_reddit(
|
||||||
|
md_changelog: str, vt_signed: str, vt_unsigned: str, version: str, gh_link: str
|
||||||
|
) -> None:
|
||||||
|
"""Build changelog for Reddit."""
|
||||||
|
reddit_start = """# What Is Elite Dangerous Market Connector?
|
||||||
|
|
||||||
|
Elite Dangerous Market Connector ("EDMC") is a third-party application for use with Frontier Developments' game "Elite Dangerous". Its purpose is to facilitate supplying certain game data to, and in some cases retrieving it from, a number of websites and other tools.
|
||||||
|
|
||||||
|
To achieve this it utilizes the Journal Files written by the game when played on a PC. It also makes use of Frontier's Companion API ("Frontier's CAPI"), accessible once you've authorized this application.
|
||||||
|
|
||||||
|
EDMC has a plugin system that many other developers have made use of to extend its functionality.
|
||||||
|
|
||||||
|
Find out more on the [EDMC Wiki](https://github.com/EDCD/EDMarketConnector/wiki).
|
||||||
|
|
||||||
|
~~----------------------------------------------------~~
|
||||||
|
|
||||||
|
You can also view the Elite: Dangerous Forum thread [HERE](https://forums.frontier.co.uk/threads/elite-dangerous-market-connector-edmc.618708/).
|
||||||
|
|
||||||
|
~~----------------------------------------------------~~
|
||||||
|
|
||||||
|
**As has become routine now, various anti-virus software are reporting a false positive on our installer and/or files it contains. We've pre-emptively uploaded the installer to** [VirusTotal]("""
|
||||||
|
reddit_mid_1 = """) **if you want to check what it's saying. Please see our** [Troubleshooting/AV-false-positives FAQ](https://github.com/EDCD/EDMarketConnector/wiki/Troubleshooting#installer-and-or-executables-flagged-as-malicious-viruses) **for further information.**
|
||||||
|
|
||||||
|
[Unsigned Installer]("""
|
||||||
|
|
||||||
|
reddit_mid_2 = """) if you don't want to use the code-signed option.
|
||||||
|
|
||||||
|
~~----------------------------------------------------~~
|
||||||
|
"""
|
||||||
|
updated = f"# [Release {version}]({gh_link})\n\n"
|
||||||
|
md_changelog = re.sub(r"===\n", "", md_changelog)
|
||||||
|
md_changelog = re.sub(f"Release {version}", updated, md_changelog)
|
||||||
|
reddit_end = f"""
|
||||||
|
|
||||||
|
**Linux**
|
||||||
|
|
||||||
|
If you're running on Linux, try the [Flatpak](https://flathub.org/apps/io.edcd.EDMarketConnector) build of EDMC! (Update to {version} coming soon.)"""
|
||||||
|
|
||||||
|
reddit_out = (
|
||||||
|
reddit_start
|
||||||
|
+ vt_signed
|
||||||
|
+ reddit_mid_1
|
||||||
|
+ vt_unsigned
|
||||||
|
+ reddit_mid_2
|
||||||
|
+ updated
|
||||||
|
+ md_changelog
|
||||||
|
+ reddit_end
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(
|
||||||
|
"script_output/reddit_changelog.txt", "w", encoding="utf-8"
|
||||||
|
) as reddit_file:
|
||||||
|
reddit_file.write(reddit_out)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Run the Changelog Generator"""
|
||||||
|
md_changelog, version = get_changelog()
|
||||||
|
print(f"Detected version {version} in the changelog. Continuing...")
|
||||||
|
gh_link = input(f"Please enter the GitHub link for {version}: ")
|
||||||
|
vt_signed = input("Please enter the VirusTotal URL for the Signed Installer: ")
|
||||||
|
vt_unsigned = input("Please enter the VirusTotal URL for the Unsigned Installer: ")
|
||||||
|
build_reddit(md_changelog, vt_signed, vt_unsigned, version, gh_link)
|
||||||
|
html = build_html(md_changelog, version)
|
||||||
|
build_fdev(vt_signed, vt_unsigned, version, gh_link, html)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
chdir(pathlib.Path(__file__).parent)
|
||||||
|
main()
|
117
ships.json
117
ships.json
@ -1,119 +1,158 @@
|
|||||||
{
|
{
|
||||||
"Adder": {
|
"Adder": {
|
||||||
"hullMass": 35
|
"hullMass": 35,
|
||||||
|
"reserveFuelCapacity": 0.36
|
||||||
},
|
},
|
||||||
"Alliance Challenger": {
|
"Alliance Challenger": {
|
||||||
"hullMass": 450
|
"hullMass": 450,
|
||||||
|
"reserveFuelCapacity": 0.77
|
||||||
},
|
},
|
||||||
"Alliance Chieftain": {
|
"Alliance Chieftain": {
|
||||||
"hullMass": 400
|
"hullMass": 400,
|
||||||
|
"reserveFuelCapacity": 0.77
|
||||||
},
|
},
|
||||||
"Alliance Crusader": {
|
"Alliance Crusader": {
|
||||||
"hullMass": 500
|
"hullMass": 500,
|
||||||
|
"reserveFuelCapacity": 0.77
|
||||||
},
|
},
|
||||||
"Anaconda": {
|
"Anaconda": {
|
||||||
"hullMass": 400
|
"hullMass": 400,
|
||||||
|
"reserveFuelCapacity": 1.07
|
||||||
},
|
},
|
||||||
"Asp Explorer": {
|
"Asp Explorer": {
|
||||||
"hullMass": 280
|
"hullMass": 280,
|
||||||
|
"reserveFuelCapacity": 0.63
|
||||||
},
|
},
|
||||||
"Asp Scout": {
|
"Asp Scout": {
|
||||||
"hullMass": 150
|
"hullMass": 150,
|
||||||
|
"reserveFuelCapacity": 0.47
|
||||||
},
|
},
|
||||||
"Beluga Liner": {
|
"Beluga Liner": {
|
||||||
"hullMass": 950
|
"hullMass": 950,
|
||||||
|
"reserveFuelCapacity": 0.81
|
||||||
},
|
},
|
||||||
"Cobra MkIII": {
|
"Cobra MkIII": {
|
||||||
"hullMass": 180
|
"hullMass": 180,
|
||||||
|
"reserveFuelCapacity": 0.49
|
||||||
},
|
},
|
||||||
"Cobra MkIV": {
|
"Cobra MkIV": {
|
||||||
"hullMass": 210
|
"hullMass": 210,
|
||||||
|
"reserveFuelCapacity": 0.51
|
||||||
},
|
},
|
||||||
"Diamondback Explorer": {
|
"Diamondback Explorer": {
|
||||||
"hullMass": 260
|
"hullMass": 260,
|
||||||
|
"reserveFuelCapacity": 0.52
|
||||||
},
|
},
|
||||||
"Diamondback Scout": {
|
"Diamondback Scout": {
|
||||||
"hullMass": 170
|
"hullMass": 170,
|
||||||
|
"reserveFuelCapacity": 0.49
|
||||||
},
|
},
|
||||||
"Dolphin": {
|
"Dolphin": {
|
||||||
"hullMass": 140
|
"hullMass": 140,
|
||||||
|
"reserveFuelCapacity": 0.5
|
||||||
},
|
},
|
||||||
"Eagle": {
|
"Eagle": {
|
||||||
"hullMass": 50
|
"hullMass": 50,
|
||||||
|
"reserveFuelCapacity": 0.34
|
||||||
},
|
},
|
||||||
"Federal Assault Ship": {
|
"Federal Assault Ship": {
|
||||||
"hullMass": 480
|
"hullMass": 480,
|
||||||
|
"reserveFuelCapacity": 0.72
|
||||||
},
|
},
|
||||||
"Federal Corvette": {
|
"Federal Corvette": {
|
||||||
"hullMass": 900
|
"hullMass": 900,
|
||||||
|
"reserveFuelCapacity": 1.13
|
||||||
},
|
},
|
||||||
"Federal Dropship": {
|
"Federal Dropship": {
|
||||||
"hullMass": 580
|
"hullMass": 580,
|
||||||
|
"reserveFuelCapacity": 0.83
|
||||||
},
|
},
|
||||||
"Federal Gunship": {
|
"Federal Gunship": {
|
||||||
"hullMass": 580
|
"hullMass": 580,
|
||||||
|
"reserveFuelCapacity": 0.82
|
||||||
},
|
},
|
||||||
"Fer-de-Lance": {
|
"Fer-de-Lance": {
|
||||||
"hullMass": 250
|
"hullMass": 250,
|
||||||
|
"reserveFuelCapacity": 0.67
|
||||||
},
|
},
|
||||||
"Hauler": {
|
"Hauler": {
|
||||||
"hullMass": 14
|
"hullMass": 14,
|
||||||
|
"reserveFuelCapacity": 0.25
|
||||||
},
|
},
|
||||||
"Imperial Clipper": {
|
"Imperial Clipper": {
|
||||||
"hullMass": 400
|
"hullMass": 400,
|
||||||
|
"reserveFuelCapacity": 0.74
|
||||||
},
|
},
|
||||||
"Imperial Courier": {
|
"Imperial Courier": {
|
||||||
"hullMass": 35
|
"hullMass": 35,
|
||||||
|
"reserveFuelCapacity": 0.41
|
||||||
},
|
},
|
||||||
"Imperial Cutter": {
|
"Imperial Cutter": {
|
||||||
"hullMass": 1100
|
"hullMass": 1100,
|
||||||
|
"reserveFuelCapacity": 1.16
|
||||||
},
|
},
|
||||||
"Imperial Eagle": {
|
"Imperial Eagle": {
|
||||||
"hullMass": 50
|
"hullMass": 50,
|
||||||
|
"reserveFuelCapacity": 0.37
|
||||||
},
|
},
|
||||||
"Keelback": {
|
"Keelback": {
|
||||||
"hullMass": 180
|
"hullMass": 180,
|
||||||
|
"reserveFuelCapacity": 0.39
|
||||||
},
|
},
|
||||||
"Krait MkII": {
|
"Krait MkII": {
|
||||||
"hullMass": 320
|
"hullMass": 320,
|
||||||
|
"reserveFuelCapacity": 0.63
|
||||||
},
|
},
|
||||||
"Krait Phantom": {
|
"Krait Phantom": {
|
||||||
"hullMass": 270
|
"hullMass": 270,
|
||||||
|
"reserveFuelCapacity": 0.63
|
||||||
},
|
},
|
||||||
"Mamba": {
|
"Mamba": {
|
||||||
"hullMass": 250
|
"hullMass": 250,
|
||||||
|
"reserveFuelCapacity": 0.5
|
||||||
},
|
},
|
||||||
"Orca": {
|
"Orca": {
|
||||||
"hullMass": 290
|
"hullMass": 290,
|
||||||
|
"reserveFuelCapacity": 0.79
|
||||||
},
|
},
|
||||||
"Python": {
|
"Python": {
|
||||||
"hullMass": 350
|
"hullMass": 350,
|
||||||
|
"reserveFuelCapacity": 0.83
|
||||||
},
|
},
|
||||||
"Python Mk II": {
|
"Python Mk II": {
|
||||||
"hullMass": 450
|
"hullMass": 450,
|
||||||
|
"reserveFuelCapacity": 0.83
|
||||||
},
|
},
|
||||||
"Sidewinder": {
|
"Sidewinder": {
|
||||||
"hullMass": 25
|
"hullMass": 25,
|
||||||
|
"reserveFuelCapacity": 0.3
|
||||||
},
|
},
|
||||||
"Type-10 Defender": {
|
"Type-10 Defender": {
|
||||||
"hullMass": 1200
|
"hullMass": 1200,
|
||||||
|
"reserveFuelCapacity": 0.77
|
||||||
},
|
},
|
||||||
"Type-6 Transporter": {
|
"Type-6 Transporter": {
|
||||||
"hullMass": 155
|
"hullMass": 155,
|
||||||
|
"reserveFuelCapacity": 0.39
|
||||||
},
|
},
|
||||||
"Type-7 Transporter": {
|
"Type-7 Transporter": {
|
||||||
"hullMass": 350
|
"hullMass": 350,
|
||||||
|
"reserveFuelCapacity": 0.52
|
||||||
},
|
},
|
||||||
"Type-9 Heavy": {
|
"Type-9 Heavy": {
|
||||||
"hullMass": 850
|
"hullMass": 850,
|
||||||
|
"reserveFuelCapacity": 0.77
|
||||||
},
|
},
|
||||||
"Viper MkIII": {
|
"Viper MkIII": {
|
||||||
"hullMass": 50
|
"hullMass": 50,
|
||||||
|
"reserveFuelCapacity": 0.41
|
||||||
},
|
},
|
||||||
"Viper MkIV": {
|
"Viper MkIV": {
|
||||||
"hullMass": 190
|
"hullMass": 190,
|
||||||
|
"reserveFuelCapacity": 0.46
|
||||||
},
|
},
|
||||||
"Vulture": {
|
"Vulture": {
|
||||||
"hullMass": 230
|
"hullMass": 230,
|
||||||
|
"reserveFuelCapacity": 0.57
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -193,6 +193,10 @@ class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore
|
|||||||
menu.add_command(label=tr.tl('Copy'), command=self.copy) # As in Copy and Paste
|
menu.add_command(label=tr.tl('Copy'), command=self.copy) # As in Copy and Paste
|
||||||
|
|
||||||
if self.name == 'ship':
|
if self.name == 'ship':
|
||||||
|
# LANG: Copy the Inara SLEF Format of the active ship to the clipboard
|
||||||
|
menu.add_command(label=tr.tl('Copy Inara SLEF'), command=self.copy_slef, state=tk.DISABLED)
|
||||||
|
menu.entryconfigure(1, state=monitor.slef and tk.NORMAL or tk.DISABLED)
|
||||||
|
|
||||||
menu.add_separator()
|
menu.add_separator()
|
||||||
for url in plug.provides('shipyard_url'):
|
for url in plug.provides('shipyard_url'):
|
||||||
menu.add_command(
|
menu.add_command(
|
||||||
@ -223,3 +227,8 @@ class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore
|
|||||||
"""Copy the current text to the clipboard."""
|
"""Copy the current text to the clipboard."""
|
||||||
self.clipboard_clear()
|
self.clipboard_clear()
|
||||||
self.clipboard_append(self['text'])
|
self.clipboard_append(self['text'])
|
||||||
|
|
||||||
|
def copy_slef(self) -> None:
|
||||||
|
"""Copy the current text to the clipboard."""
|
||||||
|
self.clipboard_clear()
|
||||||
|
self.clipboard_append(monitor.slef)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user