diff --git a/.gitignore b/.gitignore index feca4b1..8d38a8a 100644 --- a/.gitignore +++ b/.gitignore @@ -209,3 +209,9 @@ cython_debug/ marimo/_static/ marimo/_lsp/ __marimo__/ + +# VSCode +.vscode + +# Metadata +metadata 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 diff --git a/main.py b/main.py index 7c258fd..05bff39 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="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) @@ -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..3be644b 100644 --- a/src/console.py +++ b/src/console.py @@ -325,6 +325,15 @@ async def run(self): ] ).run_async() + 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() if not success: diff --git a/src/gui.py b/src/gui.py index 86e6a13..612b825 100644 --- a/src/gui.py +++ b/src/gui.py @@ -341,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) @@ -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) @@ -1138,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 @@ -1225,6 +1235,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) @@ -1343,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() @@ -1352,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() @@ -1360,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() - - asyncio.run(_run(), loop_factory=QEventLoop) + with loop: + loop.run_until_complete(_run()) + loop.run_forever() # keeps running until app.quit() / window close exits the loop - 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 1e683a2..83283a7 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,7 +1090,7 @@ 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"]) - + # Poster src = await utils.run(utils.find_from_list, self.base_path, [ ("posters", "poster.*"), @@ -1236,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://", "")