From 4937b922466d62720b21c13694b5e4be14db44ae Mon Sep 17 00:00:00 2001 From: Laurent Schwitter Date: Wed, 17 Jun 2026 10:05:32 +0200 Subject: [PATCH 1/7] setup debugger for vscode --- .gitignore | 3 +++ README.md | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/.gitignore b/.gitignore index feca4b1..5029135 100644 --- a/.gitignore +++ b/.gitignore @@ -209,3 +209,6 @@ cython_debug/ marimo/_static/ marimo/_lsp/ __marimo__/ + +# VSCode +.vscode diff --git a/README.md b/README.md index d7bbcf8..621f241 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,14 @@ Metadata is sourced from [one-pace-metadata](https://github.com/ladyisatis/one-p Contributions via Pull Requests are always welcome if I miss a bug or want another feature added in, want to contribute a program icon, etc. +## Running from source + +Details are available [on the wiki](https://github.com/ladyisatis/OnePaceOrganizer/wiki/Frequently-Asked-Questions#how-do-i-run-this-from-source), but in short you can run this from source by running: + +```bash + uv run main.py gui +``` + ## Thanks - Craigy (@verywittyname on Discord) for Episode Descriptions spreadsheets and updates From 4d86b85b12c384d2d521974de1cb54933985fb64 Mon Sep 17 00:00:00 2001 From: Laurent Schwitter Date: Wed, 17 Jun 2026 10:35:28 +0200 Subject: [PATCH 2/7] added scan_show_folder framework --- main.py | 4 ++++ src/console.py | 1 + src/gui.py | 12 ++++++++++++ src/organizer.py | 11 ++++++++++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 7c258fd..094bedb 100644 --- a/main.py +++ b/main.py @@ -80,6 +80,7 @@ def main(): parser.add_argument("--plex-library", help="Plex Library Key", default=None) parser.add_argument("--plex-show", help="Plex Show GUID", default=None) parser.add_argument("--plex-set-show-edits", help="Overwrite Plex show information", default=None, type=strbool) + parser.add_argument("--plex-scan-show-folder", help="Tells Plex to scan show folder before updating metadata", default=None, type=strbool) parser.add_argument("--plex-code", help="Plex 2-Factor Auth Code (headless mode only)", default=None) parser.add_argument("--plex-remember", help="Remember Plex Credentials", default=None, type=strbool) parser.add_argument("--plex-retry-times", help="How many times Organizer should retry to fetch a Plex season/episode", default=None) @@ -145,6 +146,9 @@ def main(): if args.plex_set_show_edits is not None: opo.plex_set_show_edits = args.plex_set_show_edits + if args.plex_scan_show_folder is not None: + opo.plex_scan_show_folder = args.plex_scan_show_folder + if args.plex_retry_times is not None: opo.plex_retry_times = int(args.plex_retry_times) diff --git a/src/console.py b/src/console.py index 719e9b0..e7b6b00 100644 --- a/src/console.py +++ b/src/console.py @@ -324,6 +324,7 @@ async def run(self): ("Set It Myself", False) ] ).run_async() + #TODO plex_scan_show_folder if plex_config_enabled: success = await self.run_plex_wizard() diff --git a/src/gui.py b/src/gui.py index 86e6a13..e5e00e3 100644 --- a/src/gui.py +++ b/src/gui.py @@ -298,6 +298,13 @@ def __init__(self, organizer=None, log_level="info"): self.action_edit_plex_url.triggered.connect(self.edit_plex_url) menu_configuration.addAction(self.action_edit_plex_url) + self.action_scan_show_folder = QAction("Tell Plex to scan the series folder before updating metadata", self) + self.action_scan_show_folder.setCheckable(True) + self.action_scan_show_folder.setChecked(self.organizer.plex_scan_show_folder) + self.action_scan_show_folder.setVisible(self.organizer.mode != 0) + self.action_scan_show_folder.triggered.connect(self.scan_show_folder) + menu_configuration.addAction(self.action_scan_show_folder) + self.action_overwrite_nfo = QAction("Overwrite .nfo Files", self) self.action_overwrite_nfo.setCheckable(True) self.action_overwrite_nfo.setChecked(self.organizer.overwrite_nfo) @@ -620,6 +627,7 @@ def set_method(self, mode): self.action_edit_plex_retry_secs.setVisible(plex_enabled) self.action_overwrite_nfo.setVisible(not plex_enabled) self.action_set_show.setVisible(plex_enabled) + self.action_scan_show_folder.setVisible(plex_enabled) self.output.setVisible(self.organizer.file_action != 4) self.input.label.setText(self.input_metadata_str if self.organizer.file_action == 4 else self.input_nometadata_str) self.input.setVisible(self.organizer.file_action != 4 if plex_enabled else True) @@ -1225,6 +1233,10 @@ def set_show_edits(self): self.organizer.plex_set_show_edits = not self.organizer.plex_set_show_edits self.action_set_show.setChecked(self.organizer.plex_set_show_edits) + def scan_show_folder(self): + self.organizer.plex_scan_show_folder = not self.organizer.plex_scan_show_folder + self.action_scan_show_folder.setChecked(self.organizer.plex_scan_show_folder) + @asyncSlot() async def start(self): self.start_button.setEnabled(False) diff --git a/src/organizer.py b/src/organizer.py index 1e683a2..76ec2af 100644 --- a/src/organizer.py +++ b/src/organizer.py @@ -98,6 +98,7 @@ def __init__(self): self.plex_retry_secs = utils.get_env("plex_retry_secs", 30) self.plex_retry_times = utils.get_env("plex_retry_times", 3) self.plex_set_show_edits = utils.get_env("plex_set_show_edits", True) + self.plex_scan_show_folder = utils.get_env("plex_scan_show_folder", True) self.progress_bar_func = None self.message_dialog_func = None @@ -228,6 +229,9 @@ async def load_config(self): if "set_show_edits" in config["plex"] and config["plex"]["set_show_edits"] is not None: self.plex_set_show_edits = config["plex"]["set_show_edits"] + if "scan_show_folder" in config["plex"] and config["plex"]["scan_show_folder"] is not None: + self.plex_scan_show_folder = config["plex"]["scan_show_folder"] + if "mode" in config and config["mode"] is not None and isinstance(config["mode"], int): self.mode = config["mode"] @@ -279,7 +283,8 @@ async def save_config(self): }, "retry_secs": self.plex_retry_secs, "retry_times": self.plex_retry_times, - "set_show_edits": self.plex_set_show_edits + "set_show_edits": self.plex_set_show_edits, + "scan_show_folder": self.plex_scan_show_folder } } @@ -1085,6 +1090,10 @@ async def process_plex(self, files): await utils.run(show.editOriginallyAvailable, str(tvshow["premiered"].isoformat()).split("T")[0]) else: await utils.run(show.editOriginallyAvailable, tvshow["premiered"]) + + if self.plex_scan_show_folder: + self.logger.info("Asking plex to scan folder") + # Poster src = await utils.run(utils.find_from_list, self.base_path, [ From 66218e7d332d9889de75c194535d59efee1b5ee3 Mon Sep 17 00:00:00 2001 From: Laurent Schwitter Date: Wed, 17 Jun 2026 10:59:47 +0200 Subject: [PATCH 3/7] added metadata to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 5029135..8d38a8a 100644 --- a/.gitignore +++ b/.gitignore @@ -212,3 +212,6 @@ __marimo__/ # VSCode .vscode + +# Metadata +metadata From 1cdbf11d19006a9e062a055a1d279eac0868ae8a Mon Sep 17 00:00:00 2001 From: Laurent Schwitter Date: Wed, 17 Jun 2026 11:15:53 +0200 Subject: [PATCH 4/7] fixed loop and default to scan_show_folder = False --- src/gui.py | 41 ++++++++++++++++++++--------------------- src/organizer.py | 2 +- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/gui.py b/src/gui.py index e5e00e3..93445b0 100644 --- a/src/gui.py +++ b/src/gui.py @@ -1146,10 +1146,12 @@ async def select_plex_show(self): self.plex_status.prop.setText(self.plex_status_text()) @asyncClose - async def exit(self, event=None): + async def closeEvent(self, event): await self.organizer.save_config() - if event is None: - self.close() + event.accept() + + def exit(self): + self.close() # delegates to closeEvent above def set_action(self, action): self.organizer.file_action = action @@ -1355,8 +1357,9 @@ async def start(self): def main(organizer, log_level): try: app = QApplication(sys.argv) - close_event = asyncio.Event() - app.aboutToQuit.connect(close_event.set) + + loop = QEventLoop(app) + asyncio.set_event_loop(loop) async def _run(): await organizer.load_config() @@ -1364,7 +1367,7 @@ async def _run(): gui = GUI(organizer, log_level) gui.setWindowTitle(organizer.window_title) - if "gui_maximized" in organizer.extra_fields and isinstance(organizer.extra_fields["gui_maximized"], bool) and organizer.extra_fields["gui_maximized"]: + if organizer.extra_fields.get("gui_maximized") is True: gui.showMaximized() else: gui.show() @@ -1372,27 +1375,23 @@ async def _run(): is_latest, latest_vers = await utils.run(utils.is_up_to_date, organizer.toml["version"], organizer.base_path) if not is_latest: do_update = await asyncWrap( - QMessageBox.question( + lambda: QMessageBox.question( None, - self.organizer.window_title, - "There is a newer version of this application. Do you wish to open up " + - "GitHub in order to download the new version?" + - "\n\n" + - f"Installed: v{organizer.toml['version']}\n" + + organizer.window_title, + "There is a newer version of this application. Do you wish to open up " + "GitHub in order to download the new version?" + "\n\n" + f"Installed: v{organizer.toml['version']}\n" f"Latest: v{latest_vers}" ) == QMessageBox.StandardButton.Yes ) - if do_update: await utils.run(webbrowser.open_new_tab, "https://github.com/ladyisatis/OnePaceOrganizer/releases/latest") - await close_event.wait() + with loop: + loop.run_until_complete(_run()) + loop.run_forever() # keeps running until app.quit() / window close exits the loop - asyncio.run(_run(), loop_factory=QEventLoop) - - except: + except Exception: print(traceback.format_exc()) - QMessageBox.critical(None, organizer.window_title, traceback.format_exc()) - - finally: - asyncio.run(organizer.save_config()) + QMessageBox.critical(None, organizer.window_title, traceback.format_exc()) \ No newline at end of file diff --git a/src/organizer.py b/src/organizer.py index 76ec2af..4b3b4e5 100644 --- a/src/organizer.py +++ b/src/organizer.py @@ -98,7 +98,7 @@ def __init__(self): self.plex_retry_secs = utils.get_env("plex_retry_secs", 30) self.plex_retry_times = utils.get_env("plex_retry_times", 3) self.plex_set_show_edits = utils.get_env("plex_set_show_edits", True) - self.plex_scan_show_folder = utils.get_env("plex_scan_show_folder", True) + self.plex_scan_show_folder = utils.get_env("plex_scan_show_folder", False) self.progress_bar_func = None self.message_dialog_func = None From 6db26e98660c7b4b5932afa92f6c51985c992447 Mon Sep 17 00:00:00 2001 From: Laurent Schwitter Date: Wed, 17 Jun 2026 14:05:51 +0200 Subject: [PATCH 5/7] fixed bug with db being closed when processing plex episodes and added plex scan update option --- src/organizer.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/organizer.py b/src/organizer.py index 4b3b4e5..37bc608 100644 --- a/src/organizer.py +++ b/src/organizer.py @@ -1091,10 +1091,6 @@ async def process_plex(self, files): else: await utils.run(show.editOriginallyAvailable, tvshow["premiered"]) - if self.plex_scan_show_folder: - self.logger.info("Asking plex to scan folder") - - # Poster src = await utils.run(utils.find_from_list, self.base_path, [ ("posters", "poster.*"), @@ -1245,8 +1241,18 @@ async def process_plex_episodes(self, queue, metadata_only=False): plex_episode = None try: + data_file = Path(self.base_path, "metadata", "data.db") + if await utils.is_file(data_file): + await self.open_db(data_file) + section = await utils.run(self.plexapi_server.library.sectionByID, int(self.plex_config_library_key)) + if self.plex_scan_show_folder: + self.logger.info(f"Triggering Plex Library Scan") + section.update() + self.logger.debug(f"Waiting 5 seconds for library update to finish") + await asyncio.sleep(5) + self.logger.trace(f"Looking up show with GUID: {self.plex_config_show_guid}") if self.plex_config_show_guid.startswith("local://"): rating_key = self.plex_config_show_guid.replace("local://", "") From 16e1e55ce68219b043fad5060a0dd010f1f454c4 Mon Sep 17 00:00:00 2001 From: Laurent Schwitter Date: Wed, 17 Jun 2026 14:08:59 +0200 Subject: [PATCH 6/7] moved scan_show_folder under advanced and default to true --- src/gui.py | 14 +++++++------- src/organizer.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gui.py b/src/gui.py index 93445b0..612b825 100644 --- a/src/gui.py +++ b/src/gui.py @@ -298,13 +298,6 @@ def __init__(self, organizer=None, log_level="info"): self.action_edit_plex_url.triggered.connect(self.edit_plex_url) menu_configuration.addAction(self.action_edit_plex_url) - self.action_scan_show_folder = QAction("Tell Plex to scan the series folder before updating metadata", self) - self.action_scan_show_folder.setCheckable(True) - self.action_scan_show_folder.setChecked(self.organizer.plex_scan_show_folder) - self.action_scan_show_folder.setVisible(self.organizer.mode != 0) - self.action_scan_show_folder.triggered.connect(self.scan_show_folder) - menu_configuration.addAction(self.action_scan_show_folder) - self.action_overwrite_nfo = QAction("Overwrite .nfo Files", self) self.action_overwrite_nfo.setCheckable(True) self.action_overwrite_nfo.setChecked(self.organizer.overwrite_nfo) @@ -348,6 +341,13 @@ def __init__(self, organizer=None, log_level="info"): self.action_edit_plex_retry_secs.triggered.connect(self.edit_plex_retry_secs) menu_advanced.addAction(self.action_edit_plex_retry_secs) + self.action_scan_show_folder = QAction("(Plex) Scan Library after move/copy", self) + self.action_scan_show_folder.setCheckable(True) + self.action_scan_show_folder.setChecked(self.organizer.plex_scan_show_folder) + self.action_scan_show_folder.setVisible(self.organizer.mode != 0) + self.action_scan_show_folder.triggered.connect(self.scan_show_folder) + menu_advanced.addAction(self.action_scan_show_folder) + action_edit_workers = QAction("Edit Number of Workers", self) action_edit_workers.triggered.connect(self.edit_workers) menu_advanced.addAction(action_edit_workers) diff --git a/src/organizer.py b/src/organizer.py index 37bc608..83283a7 100644 --- a/src/organizer.py +++ b/src/organizer.py @@ -98,7 +98,7 @@ def __init__(self): self.plex_retry_secs = utils.get_env("plex_retry_secs", 30) self.plex_retry_times = utils.get_env("plex_retry_times", 3) self.plex_set_show_edits = utils.get_env("plex_set_show_edits", True) - self.plex_scan_show_folder = utils.get_env("plex_scan_show_folder", False) + self.plex_scan_show_folder = utils.get_env("plex_scan_show_folder", True) self.progress_bar_func = None self.message_dialog_func = None From 4c618cbe11edb1917560c5aa452647873a37668e Mon Sep 17 00:00:00 2001 From: Laurent Schwitter Date: Wed, 17 Jun 2026 14:12:59 +0200 Subject: [PATCH 7/7] added prompt for console mode --- main.py | 2 +- src/console.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 094bedb..05bff39 100644 --- a/main.py +++ b/main.py @@ -80,7 +80,7 @@ def main(): parser.add_argument("--plex-library", help="Plex Library Key", default=None) parser.add_argument("--plex-show", help="Plex Show GUID", default=None) parser.add_argument("--plex-set-show-edits", help="Overwrite Plex show information", default=None, type=strbool) - parser.add_argument("--plex-scan-show-folder", help="Tells Plex to scan show folder before updating metadata", default=None, type=strbool) + parser.add_argument("--plex-scan-show-folder", help="Trigger Plex Library scan after moving/copying", default=None, type=strbool) parser.add_argument("--plex-code", help="Plex 2-Factor Auth Code (headless mode only)", default=None) parser.add_argument("--plex-remember", help="Remember Plex Credentials", default=None, type=strbool) parser.add_argument("--plex-retry-times", help="How many times Organizer should retry to fetch a Plex season/episode", default=None) diff --git a/src/console.py b/src/console.py index e7b6b00..3be644b 100644 --- a/src/console.py +++ b/src/console.py @@ -324,7 +324,15 @@ async def run(self): ("Set It Myself", False) ] ).run_async() - #TODO plex_scan_show_folder + + self.organizer.plex_scan_show_folder = await button_dialog( + title=self.window_title, + text="Do you want to trigger Plex to Scan the Library files after import or there's no need? (eg if Plex settings do that automatically already)", + buttons=[ + ("Tell Plex to Scan", True), + ("No need", False) + ] + ).run_async() if plex_config_enabled: success = await self.run_plex_wizard()