mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-12 15:27:14 +03:00
Merge branch 'develop' into fix/2232/edsm-preferences
This commit is contained in:
commit
5099093682
@ -11,13 +11,16 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import html
|
||||
import locale
|
||||
import os
|
||||
import pathlib
|
||||
import queue
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import webbrowser
|
||||
import tempfile
|
||||
from os import chdir, environ, path
|
||||
from time import localtime, strftime, time
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
@ -47,8 +50,6 @@ if __name__ == '__main__':
|
||||
# output until after this redirect is done, if needed.
|
||||
if getattr(sys, 'frozen', False):
|
||||
# By default py2exe tries to write log to dirname(sys.executable) which fails when installed
|
||||
import tempfile
|
||||
|
||||
# unbuffered not allowed for text in python3, so use `1 for line buffering
|
||||
log_file_path = path.join(tempfile.gettempdir(), f'{appname}.log')
|
||||
sys.stdout = sys.stderr = open(log_file_path, mode='wt', buffering=1) # Do NOT use WITH here.
|
||||
@ -336,29 +337,13 @@ if __name__ == '__main__': # noqa: C901
|
||||
|
||||
def already_running_popup():
|
||||
"""Create the "already running" popup."""
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter import messagebox
|
||||
# Check for CL arg that suppresses this popup.
|
||||
if args.suppress_dupe_process_popup:
|
||||
sys.exit(0)
|
||||
|
||||
root = tk.Tk(className=appname.lower())
|
||||
|
||||
frame = tk.Frame(root)
|
||||
frame.grid(row=1, column=0, sticky=tk.NSEW)
|
||||
|
||||
label = tk.Label(frame, text='An EDMarketConnector.exe process was already running, exiting.')
|
||||
label.grid(row=1, column=0, sticky=tk.NSEW)
|
||||
|
||||
button = ttk.Button(frame, text='OK', command=lambda: sys.exit(0))
|
||||
button.grid(row=2, column=0, sticky=tk.S)
|
||||
|
||||
try:
|
||||
root.mainloop()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Ctrl+C Detected, Attempting Clean Shutdown")
|
||||
sys.exit()
|
||||
logger.info('Exiting')
|
||||
messagebox.showerror(title=appname, message="An EDMarketConnector process was already running, exiting.")
|
||||
sys.exit(0)
|
||||
|
||||
journal_lock = JournalLock()
|
||||
locked = journal_lock.obtain_lock()
|
||||
@ -432,25 +417,10 @@ from hotkey import hotkeymgr
|
||||
from l10n import translations as tr
|
||||
from monitor import monitor
|
||||
from theme import theme
|
||||
from ttkHyperlinkLabel import HyperlinkLabel
|
||||
from ttkHyperlinkLabel import HyperlinkLabel, SHIPYARD_HTML_TEMPLATE
|
||||
|
||||
SERVER_RETRY = 5 # retry pause for Companion servers [s]
|
||||
|
||||
SHIPYARD_HTML_TEMPLATE = """
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; url={link}">
|
||||
<title>Redirecting you to your {ship_name} at {provider_name}...</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="{link}">
|
||||
You should be redirected to your {ship_name} at {provider_name} shortly...
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
class AppWindow:
|
||||
"""Define the main application window."""
|
||||
@ -515,13 +485,13 @@ class AppWindow:
|
||||
self.cmdr_label = tk.Label(frame, name='cmdr_label')
|
||||
self.cmdr = tk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name='cmdr')
|
||||
self.ship_label = tk.Label(frame, name='ship_label')
|
||||
self.ship = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.shipyard_url, name='ship')
|
||||
self.ship = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.shipyard_url, name='ship', popup_copy=True)
|
||||
self.suit_label = tk.Label(frame, name='suit_label')
|
||||
self.suit = tk.Label(frame, compound=tk.RIGHT, anchor=tk.W, name='suit')
|
||||
self.system_label = tk.Label(frame, name='system_label')
|
||||
self.system = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.system_url, popup_copy=True, name='system')
|
||||
self.station_label = tk.Label(frame, name='station_label')
|
||||
self.station = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.station_url, name='station')
|
||||
self.station = HyperlinkLabel(frame, compound=tk.RIGHT, url=self.station_url, name='station', popup_copy=True)
|
||||
# system and station text is set/updated by the 'provider' plugins
|
||||
# edsm and inara. Look for:
|
||||
#
|
||||
@ -648,7 +618,8 @@ class AppWindow:
|
||||
self.help_menu.add_command(command=lambda: self.updater.check_for_updates()) # Check for Updates...
|
||||
# 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=prefs.help_open_log_folder) # Open Log Folder
|
||||
logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname
|
||||
self.help_menu.add_command(command=lambda: prefs.open_folder(logfile_loc)) # Open Log Folder
|
||||
|
||||
self.menubar.add_cascade(menu=self.help_menu)
|
||||
if sys.platform == 'win32':
|
||||
@ -1627,7 +1598,7 @@ class AppWindow:
|
||||
hotkeymgr.play_bad()
|
||||
|
||||
def shipyard_url(self, shipname: str) -> str | None:
|
||||
"""Despatch a ship URL to the configured handler."""
|
||||
"""Dispatch a ship URL to the configured handler."""
|
||||
if not (loadout := monitor.ship()):
|
||||
logger.warning('No ship loadout, aborting.')
|
||||
return ''
|
||||
@ -1654,13 +1625,13 @@ class AppWindow:
|
||||
return f'file://localhost/{file_name}'
|
||||
|
||||
def system_url(self, system: str) -> str | None:
|
||||
"""Despatch a system URL to the configured handler."""
|
||||
"""Dispatch a system URL to the configured handler."""
|
||||
return plug.invoke(
|
||||
config.get_str('system_provider', default='EDSM'), 'EDSM', 'system_url', monitor.state['SystemName']
|
||||
)
|
||||
|
||||
def station_url(self, station: str) -> str | None:
|
||||
"""Despatch a station URL to the configured handler."""
|
||||
"""Dispatch a station URL to the configured handler."""
|
||||
return plug.invoke(
|
||||
config.get_str('station_provider', default='EDSM'), 'EDSM', 'station_url',
|
||||
monitor.state['SystemName'], monitor.state['StationName']
|
||||
@ -2226,7 +2197,29 @@ sys.path: {sys.path}'''
|
||||
if theme.default_ui_scale is not None:
|
||||
root.tk.call('tk', 'scaling', theme.default_ui_scale * float(ui_scale) / 100.0)
|
||||
|
||||
app = AppWindow(root)
|
||||
try:
|
||||
app = AppWindow(root)
|
||||
except Exception as err:
|
||||
logger.exception(f"EDMC Critical Error: {err}")
|
||||
title = tr.tl("Error") # LANG: Generic error prefix
|
||||
message = tr.tl( # LANG: EDMC Critical Error Notification
|
||||
"EDSM encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!"
|
||||
)
|
||||
err = f"{err.__class__.__name__}: {err}" # type: ignore # hijacking the existing exception detection
|
||||
detail = tr.tl( # LANG: EDMC Critical Error Details
|
||||
r"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?"
|
||||
).format(ERR=err)
|
||||
detail = detail.replace('\\n', '\n')
|
||||
detail = detail.replace('\\r', '\r')
|
||||
msg = tk.messagebox.askyesno(
|
||||
title=title, message=message, detail=detail, icon=tkinter.messagebox.ERROR, type=tkinter.messagebox.YESNO
|
||||
)
|
||||
if msg:
|
||||
webbrowser.open(
|
||||
"https://github.com/EDCD/EDMarketConnector/issues/new?"
|
||||
"assignees=&labels=bug%2C+unconfirmed&projects=&template=bug_report.md&title="
|
||||
)
|
||||
os.kill(os.getpid(), signal.SIGTERM)
|
||||
|
||||
def messagebox_broken_plugins():
|
||||
"""Display message about 'broken' plugins that failed to load."""
|
||||
|
@ -36,6 +36,12 @@
|
||||
/* companion.py: Failed to get Access Token from Frontier Auth service; In files: companion.py:508; */
|
||||
"Error: unable to get token" = "Error: unable to get token";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Notification; */
|
||||
"EDSM encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!" = "EDSM encountered a critical error, and cannot recover. EDMC is shutting down for its own protection!";
|
||||
|
||||
/* EDMarketConnector.py: EDMC Critical Error Details; */
|
||||
"Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?" = "Here's what EDMC Detected:\r\n\r\n{ERR}\r\n\r\nDo you want to file a Bug Report on GitHub?";
|
||||
|
||||
/* companion.py: Frontier CAPI returned 418, meaning down for maintenance; In files: companion.py:844; */
|
||||
"Frontier CAPI down for maintenance" = "Frontier CAPI down for maintenance";
|
||||
|
||||
@ -782,3 +788,6 @@
|
||||
|
||||
/* myNotebook.py: Can't Paste Images or Files in Text; */
|
||||
"Cannot paste non-text content." = "Cannot paste non-text content.";
|
||||
|
||||
/* ttkHyperlinkLabel.py: Open Element In Selected Provider; */
|
||||
"Open in {URL}" = "Open in {URL}";
|
@ -38,7 +38,7 @@ if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL, HWND, LPARAM, LPWSTR
|
||||
|
||||
from watchdog.events import FileCreatedEvent, FileSystemEventHandler
|
||||
from watchdog.events import FileSystemEventHandler, FileSystemEvent
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.observers.api import BaseObserver
|
||||
|
||||
@ -60,7 +60,7 @@ else:
|
||||
FileSystemEventHandler = object # dummy
|
||||
if TYPE_CHECKING:
|
||||
# this isn't ever used, but this will make type checking happy
|
||||
from watchdog.events import FileCreatedEvent
|
||||
from watchdog.events import FileSystemEvent
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.observers.api import BaseObserver
|
||||
|
||||
@ -346,7 +346,7 @@ class EDLogs(FileSystemEventHandler):
|
||||
"""
|
||||
return bool(self.thread and self.thread.is_alive())
|
||||
|
||||
def on_created(self, event: 'FileCreatedEvent') -> None:
|
||||
def on_created(self, event: 'FileSystemEvent') -> None:
|
||||
"""Watchdog callback when, e.g. client (re)started."""
|
||||
if not event.is_directory and self._RE_LOGFILE.search(basename(event.src_path)):
|
||||
|
||||
|
@ -121,7 +121,7 @@ class EntryMenu(ttk.Entry):
|
||||
class Entry(EntryMenu):
|
||||
"""Custom ttk.Entry class to fix some display issues."""
|
||||
|
||||
# DEPRECATED: Migrate to EntryMenu. Will remove in 5.12 or later.
|
||||
# DEPRECATED: Migrate to EntryMenu. Will remove in 6.0 or later.
|
||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||
EntryMenu.__init__(self, master, **kw)
|
||||
|
||||
@ -139,7 +139,7 @@ class Button(ttk.Button):
|
||||
class ColoredButton(tk.Button):
|
||||
"""Custom tk.Button class to fix some display issues."""
|
||||
|
||||
# DEPRECATED: Migrate to tk.Button. Will remove in 5.12 or later.
|
||||
# DEPRECATED: Migrate to tk.Button. Will remove in 6.0 or later.
|
||||
def __init__(self, master: ttk.Frame | None = None, **kw):
|
||||
tk.Button.__init__(self, master, **kw)
|
||||
|
||||
|
@ -550,23 +550,6 @@ def journal_entry( # noqa: C901, CCR001
|
||||
|
||||
# Ship change
|
||||
if event_name == 'Loadout' and this.shipswap:
|
||||
cur_ship = {
|
||||
'shipType': state['ShipType'],
|
||||
'shipGameID': state['ShipID'],
|
||||
'shipName': state['ShipName'], # Can be None
|
||||
'shipIdent': state['ShipIdent'], # Can be None
|
||||
'isCurrentShip': True,
|
||||
}
|
||||
|
||||
if state['HullValue']:
|
||||
cur_ship['shipHullValue'] = state['HullValue']
|
||||
|
||||
if state['ModulesValue']:
|
||||
cur_ship['shipModulesValue'] = state['ModulesValue']
|
||||
|
||||
cur_ship['shipRebuyCost'] = state['Rebuy']
|
||||
new_add_event('setCommanderShip', entry['timestamp'], cur_ship)
|
||||
|
||||
this.loadout = make_loadout(state)
|
||||
new_add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout)
|
||||
this.shipswap = False
|
||||
@ -854,7 +837,7 @@ def journal_entry( # noqa: C901, CCR001
|
||||
for ship in this.fleet:
|
||||
new_add_event('setCommanderShip', entry['timestamp'], ship)
|
||||
# Loadout
|
||||
if event_name == 'Loadout' and not this.newsession:
|
||||
if event_name == 'Loadout':
|
||||
loadout = make_loadout(state)
|
||||
if this.loadout != loadout:
|
||||
this.loadout = loadout
|
||||
@ -868,6 +851,26 @@ def journal_entry( # noqa: C901, CCR001
|
||||
|
||||
new_add_event('setCommanderShipLoadout', entry['timestamp'], this.loadout)
|
||||
|
||||
cur_ship = {
|
||||
'shipType': state['ShipType'],
|
||||
'shipGameID': state['ShipID'],
|
||||
'shipName': state['ShipName'], # Can be None
|
||||
'shipIdent': state['ShipIdent'], # Can be None
|
||||
'isCurrentShip': True,
|
||||
'shipMaxJumpRange': entry['MaxJumpRange'],
|
||||
'shipCargoCapacity': entry['CargoCapacity']
|
||||
}
|
||||
if state['HullValue']:
|
||||
cur_ship['shipHullValue'] = state['HullValue']
|
||||
|
||||
if state['ModulesValue']:
|
||||
cur_ship['shipModulesValue'] = state['ModulesValue']
|
||||
|
||||
if state['Rebuy']:
|
||||
cur_ship['shipRebuyCost'] = state['Rebuy']
|
||||
|
||||
new_add_event('setCommanderShip', entry['timestamp'], cur_ship)
|
||||
|
||||
# Stored modules
|
||||
if event_name == 'StoredModules':
|
||||
items = {mod['StorageSlot']: mod for mod in entry['Items']} # Impose an order
|
||||
|
23
prefs.py
23
prefs.py
@ -8,7 +8,6 @@ import pathlib
|
||||
import sys
|
||||
import tempfile
|
||||
import tkinter as tk
|
||||
import webbrowser
|
||||
from os import system
|
||||
from os.path import expanduser, expandvars, join, normpath
|
||||
from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812
|
||||
@ -40,14 +39,21 @@ logger = get_main_logger()
|
||||
|
||||
def help_open_log_folder() -> None:
|
||||
"""Open the folder logs are stored in."""
|
||||
logfile_loc = pathlib.Path(tempfile.gettempdir())
|
||||
logfile_loc /= f'{appname}'
|
||||
logger.warning(
|
||||
DeprecationWarning("This function is deprecated, use open_log_folder instead. "
|
||||
"This function will be removed in 6.0 or later")
|
||||
)
|
||||
open_folder(pathlib.Path(tempfile.gettempdir()) / appname)
|
||||
|
||||
|
||||
def open_folder(file: pathlib.Path) -> None:
|
||||
"""Open the given file in the OS file explorer."""
|
||||
if sys.platform.startswith('win'):
|
||||
# On Windows, use the "start" command to open the folder
|
||||
system(f'start "" "{logfile_loc}"')
|
||||
system(f'start "" "{file}"')
|
||||
elif sys.platform.startswith('linux'):
|
||||
# On Linux, use the "xdg-open" command to open the folder
|
||||
system(f'xdg-open "{logfile_loc}"')
|
||||
system(f'xdg-open "{file}"')
|
||||
|
||||
|
||||
class PrefsVersion:
|
||||
@ -296,6 +302,9 @@ class PreferencesDialog(tk.Toplevel):
|
||||
):
|
||||
self.geometry(f"+{position.left}+{position.top}")
|
||||
|
||||
# Set Log Directory
|
||||
self.logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname
|
||||
|
||||
def __setup_output_tab(self, root_notebook: ttk.Notebook) -> None:
|
||||
output_frame = nb.Frame(root_notebook)
|
||||
output_frame.columnconfigure(0, weight=1)
|
||||
@ -623,7 +632,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
config_frame,
|
||||
# LANG: Label on button used to open a filesystem folder
|
||||
text=tr.tl('Open Log Folder'), # Button that opens a folder in Explorer/Finder
|
||||
command=lambda: help_open_log_folder()
|
||||
command=lambda: open_folder(self.logfile_loc)
|
||||
).grid(column=2, padx=self.PADX, pady=0, sticky=tk.NSEW, row=cur_row)
|
||||
|
||||
# Big spacer
|
||||
@ -884,7 +893,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
plugins_frame,
|
||||
# LANG: Label on button used to open a filesystem folder
|
||||
text=tr.tl('Open'), # Button that opens a folder in Explorer/Finder
|
||||
command=lambda: webbrowser.open(f'file:///{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)
|
||||
|
||||
enabled_plugins = list(filter(lambda x: x.folder and x.module, plug.PLUGINS))
|
||||
|
@ -1,5 +1,5 @@
|
||||
requests==2.31.0
|
||||
pillow==10.3.0
|
||||
watchdog==3.0.0
|
||||
watchdog==4.0.0
|
||||
infi.systray==0.1.12; sys_platform == 'win32'
|
||||
semantic-version==2.10.0
|
||||
|
@ -19,14 +19,34 @@ In addition to standard ttk.Label arguments, takes the following arguments:
|
||||
May be imported by plugins
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import html
|
||||
from functools import partial
|
||||
import sys
|
||||
import tkinter as tk
|
||||
import webbrowser
|
||||
from tkinter import font as tk_font
|
||||
from tkinter import ttk
|
||||
from typing import Any
|
||||
import plug
|
||||
from os import path
|
||||
from config import config, logger
|
||||
from l10n import translations as tr
|
||||
from monitor import monitor
|
||||
|
||||
SHIPYARD_HTML_TEMPLATE = """
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; url={link}">
|
||||
<title>Redirecting you to your {ship_name} at {provider_name}...</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="{link}">
|
||||
You should be redirected to your {ship_name} at {provider_name} shortly...
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore
|
||||
@ -64,6 +84,72 @@ class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore
|
||||
text=kw.get('text'),
|
||||
font=kw.get('font', ttk.Style().lookup('TLabel', 'font')))
|
||||
|
||||
# Add Menu Options
|
||||
self.plug_options = kw.pop('plug_options', None)
|
||||
self.name = kw.get('name', None)
|
||||
if self.name == 'ship':
|
||||
self.menu.add_separator()
|
||||
for url in plug.provides('shipyard_url'):
|
||||
self.menu.add_command(
|
||||
label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider
|
||||
command=partial(self.open_shipyard, url)
|
||||
)
|
||||
|
||||
if self.name == 'station':
|
||||
self.menu.add_separator()
|
||||
for url in plug.provides('station_url'):
|
||||
self.menu.add_command(
|
||||
label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider
|
||||
command=partial(self.open_station, url)
|
||||
)
|
||||
|
||||
if self.name == 'system':
|
||||
self.menu.add_separator()
|
||||
for url in plug.provides('system_url'):
|
||||
self.menu.add_command(
|
||||
label=tr.tl("Open in {URL}").format(URL=url), # LANG: Open Element In Selected Provider
|
||||
command=partial(self.open_system, url)
|
||||
)
|
||||
|
||||
def open_shipyard(self, url: str):
|
||||
"""Open the Current Ship Loadout in the Selected Provider."""
|
||||
if not (loadout := monitor.ship()):
|
||||
logger.warning('No ship loadout, aborting.')
|
||||
return ''
|
||||
if not bool(config.get_int("use_alt_shipyard_open")):
|
||||
opener = plug.invoke(url, 'EDSY', 'shipyard_url', loadout, monitor.is_beta)
|
||||
if opener:
|
||||
return webbrowser.open(opener)
|
||||
else:
|
||||
# Avoid file length limits if possible
|
||||
provider = config.get_str('shipyard_provider', default='EDSY')
|
||||
target = plug.invoke(provider, 'EDSY', 'shipyard_url', loadout, monitor.is_beta)
|
||||
file_name = path.join(config.app_dir_path, "last_shipyard.html")
|
||||
|
||||
with open(file_name, 'w') as f:
|
||||
f.write(SHIPYARD_HTML_TEMPLATE.format(
|
||||
link=html.escape(str(target)),
|
||||
provider_name=html.escape(str(provider)),
|
||||
ship_name=html.escape("Ship")
|
||||
))
|
||||
|
||||
webbrowser.open(f'file://localhost/{file_name}')
|
||||
|
||||
def open_system(self, url: str):
|
||||
"""Open the Current System in the Selected Provider."""
|
||||
opener = plug.invoke(url, 'EDSM', 'system_url', monitor.state['SystemName'])
|
||||
if opener:
|
||||
return webbrowser.open(opener)
|
||||
|
||||
def open_station(self, url: str):
|
||||
"""Open the Current Station in the Selected Provider."""
|
||||
opener = plug.invoke(
|
||||
url, 'EDSM', 'station_url',
|
||||
monitor.state['SystemName'], monitor.state['StationName']
|
||||
)
|
||||
if opener:
|
||||
return webbrowser.open(opener)
|
||||
|
||||
def configure( # noqa: CCR001
|
||||
self, cnf: dict[str, Any] | None = None, **kw: Any
|
||||
) -> dict[str, tuple[str, str, str, Any, Any]] | None:
|
||||
|
Loading…
x
Reference in New Issue
Block a user