From c198108700df9aa6b9fa0438219e505e71c1afd5 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 10 Jun 2024 12:55:58 -0400 Subject: [PATCH 1/6] [748] Establish EDMC Shutdown Mechanism --- EDMarketConnector.py | 12 +++++++++++- prefs.py | 18 +++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index cd76ccc0..2582f570 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -845,9 +845,19 @@ class AppWindow: ) update_msg = update_msg.replace('\\n', '\n') 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: webbrowser.open("https://github.com/edCD/eDMarketConnector/releases/latest") + if postargs.get('Restart_Req'): + restart_msg = tr.tl('A restart of EDMC is required. EDMC will now shut down.') + restart_box = tk.messagebox.Message( + title=tr.tl('Restart Required'), + message=restart_msg, + type=tk.messagebox.OK + ) + restart_box.show() + if restart_box: + app.onexit() def set_labels(self): """Set main window labels, e.g. after language change.""" diff --git a/prefs.py b/prefs.py index 9f2062f4..dffe71b9 100644 --- a/prefs.py +++ b/prefs.py @@ -239,6 +239,7 @@ class PreferencesDialog(tk.Toplevel): self.parent = parent self.callback = callback + self.req_restart = False # LANG: File > Settings (macOS) self.title(tr.tl('Settings')) @@ -1274,19 +1275,22 @@ class PreferencesDialog(tk.Toplevel): config.set('dark_highlight', self.theme_colors[1]) theme.apply(self.parent) - # 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 if self.callback: - self.callback(**post_flags) + self.callback() plug.notify_prefs_changed(monitor.cmdr, monitor.is_beta) 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: """widget.destroy wrapper that does some extra cleanup.""" From 86b5556efbd41cb7bed0233dba40d580d1466480 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 25 Jun 2024 11:25:05 -0400 Subject: [PATCH 2/6] [748] Enable Plugin Change w/ App Restart --- EDMarketConnector.py | 3 +- L10n/en.template | 14 +++++++-- config/__init__.py | 6 ++++ config/linux.py | 6 +++- config/windows.py | 34 ++++++++++++---------- prefs.py | 69 ++++++++++++++++++++++++++++++++------------ 6 files changed, 93 insertions(+), 39 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 2582f570..390841cc 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -849,9 +849,10 @@ class AppWindow: if stable_popup: 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 shut down.') restart_box = tk.messagebox.Message( - title=tr.tl('Restart Required'), + title=tr.tl('Restart Required'), # LANG: Title of Notification Popup for EDMC Restart message=restart_msg, type=tk.messagebox.OK ) diff --git a/L10n/en.template b/L10n/en.template index 1c1e110f..a7b3ec29 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -468,8 +468,11 @@ /* prefs.py: Label for location of third-party plugins folder; In files: prefs.py:908; */ "Plugins folder" = "Plugins folder"; -/* prefs.py: Label on button used to open a filesystem folder; In files: prefs.py:915; */ -"Open" = "Open"; +/* prefs.py: Label on button used to open the Plugin Folder; */ +"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; */ "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,10 +807,15 @@ /* EDMarketConnector.py: Inform the user the Update Track has changed; */ "Update Track Changed to {TRACK}" = "Update Track Changed to {TRACK}"; - /* 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?"; +/* 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 shut down." = "A restart of EDMC is required. EDMC will now shut down."; + /* myNotebook.py: Can't Paste Images or Files in Text; */ "Cannot paste non-text content." = "Cannot paste non-text content."; diff --git a/config/__init__.py b/config/__init__.py index 8dfcf46b..9a00b9a9 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -189,6 +189,7 @@ class AbstractConfig(abc.ABC): app_dir_path: pathlib.Path plugin_dir_path: pathlib.Path + default_plugin_dir_path: pathlib.Path internal_plugin_dir_path: pathlib.Path respath_path: pathlib.Path home_path: pathlib.Path @@ -279,6 +280,11 @@ class AbstractConfig(abc.ABC): """Return a string version of plugin_dir.""" 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 def internal_plugin_dir(self) -> str: """Return a string version of internal_plugin_dir.""" diff --git a/config/linux.py b/config/linux.py index 51a40626..5900fe98 100644 --- a/config/linux.py +++ b/config/linux.py @@ -31,7 +31,11 @@ class LinuxConfig(AbstractConfig): self.app_dir_path = xdg_data_home / appname 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' + 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.respath_path = pathlib.Path(__file__).parent.parent diff --git a/config/windows.py b/config/windows.py index 75e3f697..c0d25b71 100644 --- a/config/windows.py +++ b/config/windows.py @@ -49,10 +49,28 @@ class WinConfig(AbstractConfig): def __init__(self) -> None: 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.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) if getattr(sys, 'frozen', False): @@ -68,20 +86,6 @@ class WinConfig(AbstractConfig): 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 - 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 if (outdir_str := self.get_str('outdir')) is None or not pathlib.Path(outdir_str).is_dir(): docs = known_folder_path(FOLDERID_Documents) diff --git a/prefs.py b/prefs.py index dffe71b9..be989601 100644 --- a/prefs.py +++ b/prefs.py @@ -912,20 +912,15 @@ class PreferencesDialog(tk.Toplevel): # Plugin settings and info plugins_frame = nb.Frame(notebook) plugins_frame.columnconfigure(0, weight=1) - plugdir = tk.StringVar() - plugdir.set(config.plugin_dir) row = AutoInc(start=0) - - # Section heading in settings + self.plugdir = tk.StringVar() + self.plugdir.set(str(config.get_str('plugin_dir'))) # LANG: Label for location of third-party plugins folder - nb.Label(plugins_frame, text=tr.tl('Plugins folder') + ':').grid( - padx=self.PADX, pady=self.PADY, sticky=tk.W, row=row.get() - ) - - plugdirentry = ttk.Entry(plugins_frame, justify=tk.LEFT) - self.displaypath(plugdir, plugdirentry) - plugdirentry.grid(columnspan=2, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) - + self.plugdir_label = nb.Label(plugins_frame, text=tr.tl('Plugins folder') + ':') + 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 + self.plugdir_entry.grid(columnspan=4, padx=self.PADX, pady=self.BOXY, sticky=tk.EW, row=row.get()) with row as cur_row: nb.Label( plugins_frame, @@ -933,19 +928,41 @@ class PreferencesDialog(tk.Toplevel): # LANG: Tip/label about how to disable plugins text=tr.tl( "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, - # LANG: Label on button used to open a filesystem folder - text=tr.tl('Open'), # Button that opens a folder in Explorer/Finder + # LANG: Label on button used to open the Plugin Folder + text=tr.tl('Open Plugins Folder'), 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)) if enabled_plugins: 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( plugins_frame, @@ -1123,6 +1140,13 @@ class PreferencesDialog(tk.Toplevel): 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: """Save out the auto update check in game config.""" config.set('disable_autoappupdatecheckingame', self.disable_autoappupdatecheckingame.get()) @@ -1218,7 +1242,7 @@ class PreferencesDialog(tk.Toplevel): 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.""" config.set('PrefsVersion', prefsVersion.stringToSerial(appversion_nobuild())) config.set( @@ -1274,6 +1298,13 @@ class PreferencesDialog(tk.Toplevel): config.set('dark_text', self.theme_colors[0]) config.set('dark_highlight', self.theme_colors[1]) 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 # Notify if self.callback: From fee05d3aa5a6993259f3517ef8148dcdb43b3707 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 25 Jun 2024 12:01:29 -0400 Subject: [PATCH 3/6] [748] Enable Automatic Restart --- EDMarketConnector.py | 8 +++++--- L10n/en.template | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 390841cc..f905ee29 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -850,7 +850,7 @@ class AppWindow: 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 shut down.') + 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, @@ -858,7 +858,7 @@ class AppWindow: ) restart_box.show() if restart_box: - app.onexit() + app.onexit(restart=True) def set_labels(self): """Set main window labels, e.g. after language change.""" @@ -1862,7 +1862,7 @@ class AppWindow: ) exit_thread.start() - def onexit(self, event=None) -> None: + def onexit(self, event=None, restart: bool=False) -> None: """Application shutdown procedure.""" if sys.platform == 'win32': shutdown_thread = threading.Thread( @@ -1925,6 +1925,8 @@ class AppWindow: self.w.destroy() logger.info('Done.') + if restart: + return os.execv(sys.executable, ['python'] + sys.argv) def drag_start(self, event) -> None: """Initiate dragging the window.""" diff --git a/L10n/en.template b/L10n/en.template index a7b3ec29..74b9728f 100644 --- a/L10n/en.template +++ b/L10n/en.template @@ -814,7 +814,7 @@ "Restart Required" = "Restart Required"; /* EDMarketConnector.py: Text of Notification Popup for EDMC Restart; */ -"A restart of EDMC is required. EDMC will now shut down." = "A restart of EDMC is required. EDMC will now shut down."; +"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; */ "Cannot paste non-text content." = "Cannot paste non-text content."; From 6643f838dcbfaab6ea1c5bdb0e6ff595178c7c58 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 25 Jun 2024 12:07:49 -0400 Subject: [PATCH 4/6] [748] Update URL --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index f905ee29..b64dc1c8 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -847,7 +847,7 @@ class AppWindow: update_msg = update_msg.replace('\\r', '\r') stable_popup = tk.messagebox.askyesno(title=title, message=update_msg) 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.') From b47c2bddde07ebbf4d0fdea7e057bfa8a16c050e Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 25 Jun 2024 12:11:49 -0400 Subject: [PATCH 5/6] [Minor] Flake8 Fix --- EDMarketConnector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index b64dc1c8..b2b7582e 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1862,7 +1862,7 @@ class AppWindow: ) exit_thread.start() - def onexit(self, event=None, restart: bool=False) -> None: + def onexit(self, event=None, restart: bool = False) -> None: """Application shutdown procedure.""" if sys.platform == 'win32': shutdown_thread = threading.Thread( From d22f39496b972bab3b4a042e2fb84965f96c4d4b Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 25 Jun 2024 12:18:52 -0400 Subject: [PATCH 6/6] [748] Reorder Linux Settings --- EDMarketConnector.py | 2 +- config/linux.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index b2b7582e..843b076a 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -1926,7 +1926,7 @@ class AppWindow: logger.info('Done.') if restart: - return os.execv(sys.executable, ['python'] + sys.argv) + os.execv(sys.executable, ['python'] + sys.argv) def drag_start(self, event) -> None: """Initiate dragging the window.""" diff --git a/config/linux.py b/config/linux.py index 5900fe98..a7a472f4 100644 --- a/config/linux.py +++ b/config/linux.py @@ -32,12 +32,6 @@ class LinuxConfig(AbstractConfig): self.app_dir_path.mkdir(exist_ok=True, parents=True) 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.respath_path = pathlib.Path(__file__).parent.parent self.internal_plugin_dir_path = self.respath_path / 'plugins' @@ -66,6 +60,12 @@ class LinuxConfig(AbstractConfig): 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(): self.set('outdir', self.home)