1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-12 23:37:14 +03:00

[2051] Update Supporting Files

Not touching any of the big ones, but some clarification updates to many of the supporting files.
This commit is contained in:
David Sangrey 2023-11-17 11:33:26 -05:00
parent a169fb5a18
commit b2d5e13465
No known key found for this signature in database
GPG Key ID: 3AEADBB0186884BC
7 changed files with 100 additions and 76 deletions

View File

@ -79,7 +79,7 @@ def addcommodities(data) -> None: # noqa: CCR001
commodities[key] = new commodities[key] = new
if not len(commodities) > size_pre: if len(commodities) <= size_pre:
return return
if isfile(commodityfile): if isfile(commodityfile):
@ -227,7 +227,7 @@ if __name__ == "__main__":
print('Not docked!') print('Not docked!')
continue continue
elif not data.get('lastStarport'): if not data.get('lastStarport'):
print('No starport!') print('No starport!')
continue continue

View File

@ -1,4 +1,6 @@
"""Simple HTTP listener to be used with debugging various EDMC sends.""" """Simple HTTP listener to be used with debugging various EDMC sends."""
from __future__ import annotations
import gzip import gzip
import json import json
import pathlib import pathlib
@ -6,7 +8,7 @@ import tempfile
import threading import threading
import zlib import zlib
from http import server from http import server
from typing import Any, Callable, Literal, Tuple, Union from typing import Any, Callable, Literal
from urllib.parse import parse_qs from urllib.parse import parse_qs
from config import appname from config import appname
@ -22,9 +24,6 @@ SAFE_TRANSLATE = str.maketrans({x: '_' for x in "!@#$%^&*()./\\\r\n[]-+='\";:?<>
class LoggingHandler(server.BaseHTTPRequestHandler): class LoggingHandler(server.BaseHTTPRequestHandler):
"""HTTP Handler implementation that logs to EDMCs logger and writes data to files on disk.""" """HTTP Handler implementation that logs to EDMCs logger and writes data to files on disk."""
def __init__(self, request, client_address: Tuple[str, int], server) -> None:
super().__init__(request, client_address, server)
def log_message(self, format: str, *args: Any) -> None: def log_message(self, format: str, *args: Any) -> None:
"""Override default handler logger with EDMC logger.""" """Override default handler logger with EDMC logger."""
logger.info(format % args) logger.info(format % args)
@ -45,7 +44,7 @@ class LoggingHandler(server.BaseHTTPRequestHandler):
elif len(target_path) == 1 and target_path[0] == '/': elif len(target_path) == 1 and target_path[0] == '/':
target_path = 'WEB_ROOT' target_path = 'WEB_ROOT'
response: Union[Callable[[str], str], str, None] = DEFAULT_RESPONSES.get(target_path) response: Callable[[str], str] | str | None = DEFAULT_RESPONSES.get(target_path)
if callable(response): if callable(response):
response = response(to_save) response = response(to_save)
@ -69,11 +68,11 @@ class LoggingHandler(server.BaseHTTPRequestHandler):
target_file = output_data_path / (safe_file_name(target_path) + '.log') target_file = output_data_path / (safe_file_name(target_path) + '.log')
if target_file.parent != output_data_path: if target_file.parent != output_data_path:
logger.warning(f"REFUSING TO WRITE FILE THAT ISN'T IN THE RIGHT PLACE! {target_file=}") logger.warning(f"REFUSING TO WRITE FILE THAT ISN'T IN THE RIGHT PLACE! {target_file=}")
logger.warning(f'DATA FOLLOWS\n{data}') # type: ignore # mypy thinks data is a byte string here logger.warning(f'DATA FOLLOWS\n{data}')
return return
with output_lock, target_file.open('a') as f: with output_lock, target_file.open('a') as file:
f.write(to_save + "\n\n") file.write(to_save + "\n\n")
@staticmethod @staticmethod
def get_printable(data: bytes, compression: Literal['deflate'] | Literal['gzip'] | str | None = None) -> str: def get_printable(data: bytes, compression: Literal['deflate'] | Literal['gzip'] | str | None = None) -> str:

View File

@ -1,4 +1,11 @@
"""Implements locking of Journal directory.""" """
journal_lock.py - Locking of the Journal Directory.
Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.
"""
from __future__ import annotations
import pathlib import pathlib
import sys import sys
@ -6,7 +13,7 @@ import tkinter as tk
from enum import Enum from enum import Enum
from os import getpid as os_getpid from os import getpid as os_getpid
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Callable, Optional from typing import TYPE_CHECKING, Callable
from config import config from config import config
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
@ -34,9 +41,9 @@ class JournalLock:
def __init__(self) -> None: def __init__(self) -> None:
"""Initialise where the journal directory and lock file are.""" """Initialise where the journal directory and lock file are."""
self.journal_dir: str | None = config.get_str('journaldir') or config.default_journal_dir self.journal_dir: str | None = config.get_str('journaldir') or config.default_journal_dir
self.journal_dir_path: Optional[pathlib.Path] = None self.journal_dir_path: pathlib.Path | None = None
self.set_path_from_journaldir() self.set_path_from_journaldir()
self.journal_dir_lockfile_name: Optional[pathlib.Path] = None self.journal_dir_lockfile_name: pathlib.Path | None = None
# We never test truthiness of this, so let it be defined when first assigned. Avoids type hint issues. # We never test truthiness of this, so let it be defined when first assigned. Avoids type hint issues.
# self.journal_dir_lockfile: Optional[IO] = None # self.journal_dir_lockfile: Optional[IO] = None
self.locked = False self.locked = False
@ -58,7 +65,8 @@ class JournalLock:
self.journal_dir_lockfile_name = self.journal_dir_path / 'edmc-journal-lock.txt' # type: ignore self.journal_dir_lockfile_name = self.journal_dir_path / 'edmc-journal-lock.txt' # type: ignore
logger.trace_if('journal-lock', f'journal_dir_lockfile_name = {self.journal_dir_lockfile_name!r}') logger.trace_if('journal-lock', f'journal_dir_lockfile_name = {self.journal_dir_lockfile_name!r}')
try: try:
self.journal_dir_lockfile = open(self.journal_dir_lockfile_name, mode='w+', encoding='utf-8') with open(self.journal_dir_lockfile_name, mode='w+', encoding='utf-8') as lockfile:
self.journal_dir_lockfile = lockfile
# Linux CIFS read-only mount throws: OSError(30, 'Read-only file system') # Linux CIFS read-only mount throws: OSError(30, 'Read-only file system')
# Linux no-write-perm directory throws: PermissionError(13, 'Permission denied') # Linux no-write-perm directory throws: PermissionError(13, 'Permission denied')

View File

@ -10,10 +10,11 @@ OSX and Windows.
Entire file may be imported by plugins. Entire file may be imported by plugins.
""" """
from __future__ import annotations
import sys import sys
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import Optional
# Can't do this with styles on OSX - http://www.tkdocs.com/tutorial/styles.html#whydifficult # Can't do this with styles on OSX - http://www.tkdocs.com/tutorial/styles.html#whydifficult
if sys.platform == 'darwin': if sys.platform == 'darwin':
@ -29,7 +30,7 @@ elif sys.platform == 'win32':
class Notebook(ttk.Notebook): class Notebook(ttk.Notebook):
"""Custom ttk.Notebook class to fix some display issues.""" """Custom ttk.Notebook class to fix some display issues."""
def __init__(self, master: Optional[ttk.Frame] = None, **kw): def __init__(self, master: ttk.Frame | None = None, **kw):
ttk.Notebook.__init__(self, master, **kw) ttk.Notebook.__init__(self, master, **kw)
style = ttk.Style() style = ttk.Style()
@ -75,9 +76,9 @@ class Frame(sys.platform == 'darwin' and tk.Frame or ttk.Frame): # type: ignore
class Label(tk.Label): class Label(tk.Label):
"""Custom tk.Label class to fix some display issues.""" """Custom tk.Label class to fix some display issues."""
def __init__(self, master: Optional[ttk.Frame] = None, **kw): def __init__(self, master: ttk.Frame | None = None, **kw):
# This format chosen over `sys.platform in (...)` as mypy and friends dont understand that # This format chosen over `sys.platform in (...)` as mypy and friends dont understand that
if sys.platform == 'darwin' or sys.platform == 'win32': if sys.platform in ('darwin', 'win32'):
kw['foreground'] = kw.pop('foreground', PAGEFG) kw['foreground'] = kw.pop('foreground', PAGEFG)
kw['background'] = kw.pop('background', PAGEBG) kw['background'] = kw.pop('background', PAGEBG)
else: else:
@ -89,7 +90,7 @@ class Label(tk.Label):
class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry): # type: ignore class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry): # type: ignore
"""Custom t(t)k.Entry class to fix some display issues.""" """Custom t(t)k.Entry class to fix some display issues."""
def __init__(self, master: Optional[ttk.Frame] = None, **kw): def __init__(self, master: ttk.Frame | None = None, **kw):
if sys.platform == 'darwin': if sys.platform == 'darwin':
kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG) kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
tk.Entry.__init__(self, master, **kw) tk.Entry.__init__(self, master, **kw)
@ -100,7 +101,7 @@ class Entry(sys.platform == 'darwin' and tk.Entry or ttk.Entry): # type: ignore
class Button(sys.platform == 'darwin' and tk.Button or ttk.Button): # type: ignore class Button(sys.platform == 'darwin' and tk.Button or ttk.Button): # type: ignore
"""Custom t(t)k.Button class to fix some display issues.""" """Custom t(t)k.Button class to fix some display issues."""
def __init__(self, master: Optional[ttk.Frame] = None, **kw): def __init__(self, master: ttk.Frame | None = None, **kw):
if sys.platform == 'darwin': if sys.platform == 'darwin':
kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG) kw['highlightbackground'] = kw.pop('highlightbackground', PAGEBG)
tk.Button.__init__(self, master, **kw) tk.Button.__init__(self, master, **kw)
@ -113,7 +114,7 @@ class Button(sys.platform == 'darwin' and tk.Button or ttk.Button): # type: ign
class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button): # type: ignore class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button): # type: ignore
"""Custom t(t)k.ColoredButton class to fix some display issues.""" """Custom t(t)k.ColoredButton class to fix some display issues."""
def __init__(self, master: Optional[ttk.Frame] = None, **kw): def __init__(self, master: ttk.Frame | None = None, **kw):
if sys.platform == 'darwin': if sys.platform == 'darwin':
# Can't set Button background on OSX, so use a Label instead # Can't set Button background on OSX, so use a Label instead
kw['relief'] = kw.pop('relief', tk.RAISED) kw['relief'] = kw.pop('relief', tk.RAISED)
@ -131,7 +132,7 @@ class ColoredButton(sys.platform == 'darwin' and tk.Label or tk.Button): # type
class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton): # type: ignore class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton): # type: ignore
"""Custom t(t)k.Checkbutton class to fix some display issues.""" """Custom t(t)k.Checkbutton class to fix some display issues."""
def __init__(self, master: Optional[ttk.Frame] = None, **kw): def __init__(self, master: ttk.Frame | None = None, **kw):
if sys.platform == 'darwin': if sys.platform == 'darwin':
kw['foreground'] = kw.pop('foreground', PAGEFG) kw['foreground'] = kw.pop('foreground', PAGEFG)
kw['background'] = kw.pop('background', PAGEBG) kw['background'] = kw.pop('background', PAGEBG)
@ -145,7 +146,7 @@ class Checkbutton(sys.platform == 'darwin' and tk.Checkbutton or ttk.Checkbutton
class Radiobutton(sys.platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton): # type: ignore class Radiobutton(sys.platform == 'darwin' and tk.Radiobutton or ttk.Radiobutton): # type: ignore
"""Custom t(t)k.Radiobutton class to fix some display issues.""" """Custom t(t)k.Radiobutton class to fix some display issues."""
def __init__(self, master: Optional[ttk.Frame] = None, **kw): def __init__(self, master: ttk.Frame | None = None, **kw):
if sys.platform == 'darwin': if sys.platform == 'darwin':
kw['foreground'] = kw.pop('foreground', PAGEFG) kw['foreground'] = kw.pop('foreground', PAGEFG)
kw['background'] = kw.pop('background', PAGEBG) kw['background'] = kw.pop('background', PAGEBG)

View File

@ -1,9 +1,14 @@
""" """
Theme support. theme.py - Theme support.
Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.
Because of various ttk limitations this app is an unholy mix of Tk and ttk widgets. Because of various ttk limitations this app is an unholy mix of Tk and ttk widgets.
So can't use ttk's theme support. So have to change colors manually. So can't use ttk's theme support. So have to change colors manually.
""" """
from __future__ import annotations
import os import os
import sys import sys
@ -11,7 +16,7 @@ import tkinter as tk
from os.path import join from os.path import join
from tkinter import font as tk_font from tkinter import font as tk_font
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Set, Tuple from typing import TYPE_CHECKING, Callable
from config import config from config import config
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
@ -36,7 +41,7 @@ if sys.platform == 'win32':
AddFontResourceEx.restypes = [LPCWSTR, DWORD, LPCVOID] # type: ignore AddFontResourceEx.restypes = [LPCWSTR, DWORD, LPCVOID] # type: ignore
FR_PRIVATE = 0x10 FR_PRIVATE = 0x10
FR_NOT_ENUM = 0x20 FR_NOT_ENUM = 0x20
AddFontResourceEx(join(config.respath, u'EUROCAPS.TTF'), FR_PRIVATE, 0) AddFontResourceEx(join(config.respath, 'EUROCAPS.TTF'), FR_PRIVATE, 0)
elif sys.platform == 'linux': elif sys.platform == 'linux':
# pyright: reportUnboundVariable=false # pyright: reportUnboundVariable=false
@ -121,7 +126,7 @@ elif sys.platform == 'linux':
dpy = None dpy = None
class _Theme(object): class _Theme:
# Enum ? Remember these are, probably, based on 'value' of a tk # Enum ? Remember these are, probably, based on 'value' of a tk
# RadioButton set. Looking in prefs.py, they *appear* to be hard-coded # RadioButton set. Looking in prefs.py, they *appear* to be hard-coded
@ -132,18 +137,18 @@ class _Theme(object):
def __init__(self) -> None: def __init__(self) -> None:
self.active: int | None = None # Starts out with no theme self.active: int | None = None # Starts out with no theme
self.minwidth: Optional[int] = None self.minwidth: int | None = None
self.widgets: Dict[tk.Widget | tk.BitmapImage, Set] = {} self.widgets: dict[tk.Widget | tk.BitmapImage, set] = {}
self.widgets_pair: List = [] self.widgets_pair: list = []
self.defaults: Dict = {} self.defaults: dict = {}
self.current: Dict = {} self.current: dict = {}
self.default_ui_scale: float | None = None # None == not yet known self.default_ui_scale: float | None = None # None == not yet known
self.startup_ui_scale: int | None = None self.startup_ui_scale: int | None = None
def register(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, C901 def register(self, widget: tk.Widget | tk.BitmapImage) -> None: # noqa: CCR001, C901
# Note widget and children for later application of a theme. Note if # Note widget and children for later application of a theme. Note if
# the widget has explicit fg or bg attributes. # the widget has explicit fg or bg attributes.
assert isinstance(widget, tk.Widget) or isinstance(widget, tk.BitmapImage), widget assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget
if not self.defaults: if not self.defaults:
# Can't initialise this til window is created # Windows, MacOS # Can't initialise this til window is created # Windows, MacOS
self.defaults = { self.defaults = {
@ -169,14 +174,14 @@ class _Theme(object):
attribs.add('fg') attribs.add('fg')
if widget['background'] not in ['', self.defaults['bitmapbg']]: if widget['background'] not in ['', self.defaults['bitmapbg']]:
attribs.add('bg') attribs.add('bg')
elif isinstance(widget, tk.Entry) or isinstance(widget, ttk.Entry): elif isinstance(widget, (tk.Entry, ttk.Entry)):
if widget['foreground'] not in ['', self.defaults['entryfg']]: if widget['foreground'] not in ['', self.defaults['entryfg']]:
attribs.add('fg') attribs.add('fg')
if widget['background'] not in ['', self.defaults['entrybg']]: if widget['background'] not in ['', self.defaults['entrybg']]:
attribs.add('bg') attribs.add('bg')
if 'font' in widget.keys() and str(widget['font']) not in ['', self.defaults['entryfont']]: if 'font' in widget.keys() and str(widget['font']) not in ['', self.defaults['entryfont']]:
attribs.add('font') attribs.add('font')
elif isinstance(widget, tk.Frame) or isinstance(widget, ttk.Frame) or isinstance(widget, tk.Canvas): elif isinstance(widget, (tk.Canvas, tk.Frame, ttk.Frame)):
if ( if (
('background' in widget.keys() or isinstance(widget, tk.Canvas)) ('background' in widget.keys() or isinstance(widget, tk.Canvas))
and widget['background'] not in ['', self.defaults['frame']] and widget['background'] not in ['', self.defaults['frame']]
@ -200,21 +205,21 @@ class _Theme(object):
attribs.add('font') attribs.add('font')
self.widgets[widget] = attribs self.widgets[widget] = attribs
if isinstance(widget, tk.Frame) or isinstance(widget, ttk.Frame): if isinstance(widget, (tk.Frame, ttk.Frame)):
for child in widget.winfo_children(): for child in widget.winfo_children():
self.register(child) self.register(child)
def register_alternate(self, pair: Tuple, gridopts: Dict) -> None: def register_alternate(self, pair: tuple, gridopts: dict) -> None:
self.widgets_pair.append((pair, gridopts)) self.widgets_pair.append((pair, gridopts))
def button_bind( def button_bind(
self, widget: tk.Widget, command: Callable, image: Optional[tk.BitmapImage] = None self, widget: tk.Widget, command: Callable, image: tk.BitmapImage | None = None
) -> None: ) -> None:
widget.bind('<Button-1>', command) widget.bind('<Button-1>', command)
widget.bind('<Enter>', lambda e: self._enter(e, image)) widget.bind('<Enter>', lambda e: self._enter(e, image))
widget.bind('<Leave>', lambda e: self._leave(e, image)) widget.bind('<Leave>', lambda e: self._leave(e, image))
def _enter(self, event: tk.Event, image: Optional[tk.BitmapImage]) -> None: def _enter(self, event: tk.Event, image: tk.BitmapImage | None) -> None:
widget = event.widget widget = event.widget
if widget and widget['state'] != tk.DISABLED: if widget and widget['state'] != tk.DISABLED:
try: try:
@ -231,7 +236,7 @@ class _Theme(object):
except Exception: except Exception:
logger.exception(f'Failure configuring image: {image=}') logger.exception(f'Failure configuring image: {image=}')
def _leave(self, event: tk.Event, image: Optional[tk.BitmapImage]) -> None: def _leave(self, event: tk.Event, image: tk.BitmapImage | None) -> None:
widget = event.widget widget = event.widget
if widget and widget['state'] != tk.DISABLED: if widget and widget['state'] != tk.DISABLED:
try: try:
@ -299,13 +304,13 @@ class _Theme(object):
Also, register it for future updates. Also, register it for future updates.
:param widget: Target widget. :param widget: Target widget.
""" """
assert isinstance(widget, tk.Widget) or isinstance(widget, tk.BitmapImage), widget assert isinstance(widget, (tk.BitmapImage, tk.Widget)), widget
if not self.current: if not self.current:
return # No need to call this for widgets created in plugin_app() return # No need to call this for widgets created in plugin_app()
self.register(widget) self.register(widget)
self._update_widget(widget) self._update_widget(widget)
if isinstance(widget, tk.Frame) or isinstance(widget, ttk.Frame): if isinstance(widget, (tk.Frame, ttk.Frame)):
for child in widget.winfo_children(): for child in widget.winfo_children():
self._update_widget(child) self._update_widget(child)
@ -314,7 +319,7 @@ class _Theme(object):
if widget not in self.widgets: if widget not in self.widgets:
if isinstance(widget, tk.Widget): if isinstance(widget, tk.Widget):
w_class = widget.winfo_class() w_class = widget.winfo_class()
w_keys: List[str] = widget.keys() w_keys: list[str] = widget.keys()
else: else:
# There is no tk.BitmapImage.winfo_class() # There is no tk.BitmapImage.winfo_class()
@ -325,7 +330,7 @@ class _Theme(object):
assert_str = f'{w_class} {widget} "{"text" in w_keys and widget["text"]}"' assert_str = f'{w_class} {widget} "{"text" in w_keys and widget["text"]}"'
raise AssertionError(assert_str) raise AssertionError(assert_str)
attribs: Set = self.widgets.get(widget, set()) attribs: set = self.widgets.get(widget, set())
try: try:
if isinstance(widget, tk.BitmapImage): if isinstance(widget, tk.BitmapImage):
@ -355,7 +360,7 @@ class _Theme(object):
# e.g. tk.Button, tk.Label, tk.Menu # e.g. tk.Button, tk.Label, tk.Menu
if 'fg' not in attribs: if 'fg' not in attribs:
widget['foreground'] = self.current['foreground'] widget['foreground'] = self.current['foreground']
widget['activeforeground'] = self.current['activeforeground'], widget['activeforeground'] = self.current['activeforeground']
widget['disabledforeground'] = self.current['disabledforeground'] widget['disabledforeground'] = self.current['disabledforeground']
if 'bg' not in attribs: if 'bg' not in attribs:
@ -419,9 +424,7 @@ class _Theme(object):
if self.active == theme: if self.active == theme:
return # Don't need to mess with the window manager return # Don't need to mess with the window manager
self.active = theme
else:
self.active = theme
if sys.platform == 'darwin': if sys.platform == 'darwin':
from AppKit import NSAppearance, NSApplication, NSMiniaturizableWindowMask, NSResizableWindowMask from AppKit import NSAppearance, NSApplication, NSMiniaturizableWindowMask, NSResizableWindowMask

View File

@ -1,7 +1,15 @@
"""A requests.session with a TimeoutAdapter.""" """
import requests timeout_session.py - requests session with timeout adapter.
from requests.adapters import HTTPAdapter
Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.
"""
from __future__ import annotations
import requests
from requests import Session
from requests.adapters import HTTPAdapter
from config import user_agent from config import user_agent
REQUEST_TIMEOUT = 10 # reasonable timeout that all HTTP requests should use REQUEST_TIMEOUT = 10 # reasonable timeout that all HTTP requests should use
@ -35,11 +43,9 @@ def new_session(
:param session: the Session object to attach the Adapter to, defaults to a new session :param session: the Session object to attach the Adapter to, defaults to a new session
:return: The created Session :return: The created Session
""" """
if session is None: with Session() as session:
session = requests.Session()
session.headers['User-Agent'] = user_agent session.headers['User-Agent'] = user_agent
adapter = TimeoutAdapter(timeout)
adapter = TimeoutAdapter(timeout) session.mount("http://", adapter)
session.mount("http://", adapter) session.mount("https://", adapter)
session.mount("https://", adapter) return session
return session

View File

@ -1,25 +1,32 @@
"""Checking for updates to this application.""" """
update.py - Checking for Program Updates.
Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.
"""
from __future__ import annotations
import os import os
import sys import sys
import threading import threading
from os.path import dirname, join from os.path import dirname, join
from traceback import print_exc from traceback import print_exc
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING
from xml.etree import ElementTree from xml.etree import ElementTree
import requests import requests
import semantic_version import semantic_version
from config import appname, appversion_nobuild, config, update_feed
from EDMCLogging import get_main_logger
if TYPE_CHECKING: if TYPE_CHECKING:
import tkinter as tk import tkinter as tk
from config import appname, appversion_nobuild, config, update_feed
from EDMCLogging import get_main_logger
logger = get_main_logger() logger = get_main_logger()
class EDMCVersion(object): class EDMCVersion:
""" """
Hold all the information about an EDMC version. Hold all the information about an EDMC version.
@ -39,7 +46,7 @@ class EDMCVersion(object):
self.sv: semantic_version.base.Version = sv self.sv: semantic_version.base.Version = sv
class Updater(object): class Updater:
""" """
Handle checking for updates. Handle checking for updates.
@ -63,16 +70,16 @@ class Updater(object):
return False return False
def __init__(self, tkroot: Optional['tk.Tk'] = None, provider: str = 'internal'): def __init__(self, tkroot: tk.Tk | None = None, provider: str = 'internal'):
""" """
Initialise an Updater instance. Initialise an Updater instance.
:param tkroot: reference to the root window of the GUI :param tkroot: reference to the root window of the GUI
:param provider: 'internal' or other string if not :param provider: 'internal' or other string if not
""" """
self.root: Optional['tk.Tk'] = tkroot self.root: tk.Tk | None = tkroot
self.provider: str = provider self.provider: str = provider
self.thread: Optional[threading.Thread] = None self.thread: threading.Thread | None = None
if self.use_internal(): if self.use_internal():
return return
@ -81,7 +88,7 @@ class Updater(object):
import ctypes import ctypes
try: try:
self.updater: Optional[ctypes.CDLL] = ctypes.cdll.WinSparkle self.updater: ctypes.CDLL | None = ctypes.cdll.WinSparkle
# Set the appcast URL # Set the appcast URL
self.updater.win_sparkle_set_appcast_url(update_feed.encode()) self.updater.win_sparkle_set_appcast_url(update_feed.encode())
@ -150,7 +157,7 @@ class Updater(object):
elif sys.platform == 'darwin' and self.updater: elif sys.platform == 'darwin' and self.updater:
self.updater.checkForUpdates_(None) self.updater.checkForUpdates_(None)
def check_appcast(self) -> Optional[EDMCVersion]: def check_appcast(self) -> EDMCVersion | None:
""" """
Manually (no Sparkle or WinSparkle) check the update_feed appcast file. Manually (no Sparkle or WinSparkle) check the update_feed appcast file.
@ -161,7 +168,7 @@ class Updater(object):
newversion = None newversion = None
items = {} items = {}
try: try:
r = requests.get(update_feed, timeout=10) request = requests.get(update_feed, timeout=10)
except requests.RequestException as ex: except requests.RequestException as ex:
logger.exception(f'Error retrieving update_feed file: {ex}') logger.exception(f'Error retrieving update_feed file: {ex}')
@ -169,7 +176,7 @@ class Updater(object):
return None return None
try: try:
feed = ElementTree.fromstring(r.text) feed = ElementTree.fromstring(request.text)
except SyntaxError as ex: except SyntaxError as ex:
logger.exception(f'Syntax error in update_feed file: {ex}') logger.exception(f'Syntax error in update_feed file: {ex}')
@ -196,12 +203,12 @@ class Updater(object):
continue continue
# This will change A.B.C.D to A.B.C+D # This will change A.B.C.D to A.B.C+D
sv = semantic_version.Version.coerce(ver) semver = semantic_version.Version.coerce(ver)
items[sv] = EDMCVersion( items[semver] = EDMCVersion(
version=str(ver), # sv might have mangled version version=str(ver), # sv might have mangled version
title=item.find('title').text, # type: ignore title=item.find('title').text, # type: ignore
sv=sv sv=semver
) )
# Look for any remaining version greater than appversion # Look for any remaining version greater than appversion