diff --git a/.vscode/settings.json b/.vscode/settings.json index de19fdfc..e5d1c852 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -52,6 +52,7 @@ "matroska", "mbed", "mccabe", + "MEIPASS", "Microformat", "microformats", "mkvtoolsnix", diff --git a/app.spec b/app.spec index 2ef14c1f..9857a34d 100644 --- a/app.spec +++ b/app.spec @@ -31,6 +31,7 @@ hidden = [ "engineio", "engineio.async_drivers.aiohttp", "socketio.async_drivers.aiohttp", + "app" ] hidden = [f.replace("-", "_") for f in hidden] @@ -41,7 +42,7 @@ a = Analysis( # noqa: F821 # type: ignore binaries=binaries, datas=[ ("ui/exported", "ui/exported"), - ("app/migrations", "migrations"), + ("app/", "app/"), ], hiddenimports=hidden, hookspath=[], @@ -62,7 +63,7 @@ exe = EXE( # type: ignore # noqa: F821 debug=False, strip=False, upx=True, - console=False, + console=True, # Turn on to True if you want a console window for debugging. icon="ui/public/favicon.ico", onefile=True, ) diff --git a/app/library/Utils.py b/app/library/Utils.py index 92505b47..f2be102f 100644 --- a/app/library/Utils.py +++ b/app/library/Utils.py @@ -94,8 +94,7 @@ def calc_download_path(base_path: str | Path, folder: str | None = None, create_ if not folder: return str(base_path) - if folder.startswith("/"): - folder = folder[1:] + folder = folder.removeprefix("/") realBasePath = base_path.resolve() download_path = Path(realBasePath).joinpath(folder).resolve(strict=False) @@ -179,8 +178,7 @@ def extract_info( else: log_wrapper.add_target(target=params["callback"], level=logging.ERROR, name="callback") - if "callback" in params: - del params["callback"] + params.pop("callback", None) if log_wrapper.has_targets(): if "logger" in params: diff --git a/app/native.py b/app/native.py index dc60dd5f..716b4e93 100644 --- a/app/native.py +++ b/app/native.py @@ -1,38 +1,50 @@ #!/usr/bin/env python3 +import asyncio +import json +import logging import os - -os.environ["PYTHONUTF8"] = "1" - +import queue +import socket import sys +import threading +import traceback from pathlib import Path +import platformdirs + +sys.path.insert(0, os.path.join(getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__))), "app")) + +APP_NAME = "YTPTube" APP_ROOT = str((Path(__file__).parent / "..").resolve()) if APP_ROOT not in sys.path: sys.path.insert(0, APP_ROOT) - -import json -import queue -import socket -import threading - -import platformdirs - -ready = threading.Event() -exception_holder = queue.Queue() - -APP_NAME = "YTPTube" - try: import webview # type: ignore except ImportError as e: - if "nt" == os.name: - msg = "Please run 'uv pip install pywebview[edgechromium]' package to run YTPTube in native mode." - else: - msg = "Please run 'uv pip install pywebview[qt]' package to run YTPTube in native mode." + pkgs = "pywebview[edgechromium]" if os.name == "nt" else "pywebview[qt]" + msg: str = f"Please run 'uv pip install {pkgs}' to run YTPTube in native mode." raise ImportError(msg) from e +def error_window(exc: Exception | str) -> None: + trace: str = "\n".join(traceback.format_exception(exc)) if isinstance(exc, Exception) else exc + webview.create_window( + f"{APP_NAME} - Error", + html=f"

An error occurred

{trace}
", + width=600, + height=400, + resizable=True, + ) + webview.start( + gui="edgechromium" if os.name == "nt" else "qt", + debug=False, + storage_path=str(Path(os.getenv("YTP_TEMP_PATH", os.getcwd())) / "webview"), + private_mode=False, + ) + sys.exit(1) + + def set_env(): dct = {} @@ -52,32 +64,31 @@ def set_env(): os.environ.update(dct) -def app_start(host: str, port: int) -> None: - import asyncio - - from main import Main - +def run_backend(host: str, port: int, ready_event: threading.Event, error_queue: queue.Queue): try: - loop = asyncio.get_running_loop() - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) - try: - Main(is_native=True).start(host, port, cb=lambda: ready.set()) + from app.main import Main + + Main(is_native=True).start(host, port, cb=lambda: ready_event.set()) except Exception as e: - exception_holder.put(e) - ready.set() + logging.exception(e) + error_queue.put(e) + ready_event.set() -if __name__ == "__main__": +def main(): host = "127.0.0.1" set_env() cfg_path: Path = Path(os.getenv("YTP_CONFIG_PATH")) / "webview.json" - port = None win_conf: dict[str, int] = {} + if cfg_path.exists(): data = json.loads(cfg_path.read_text()) port = data.get("port") @@ -91,18 +102,21 @@ if __name__ == "__main__": port = s.getsockname()[1] cfg_path.write_text(json.dumps({"port": port})) - threading.Thread(target=app_start, args=(host, port), daemon=True).start() + ready = threading.Event() + errors: queue.Queue = queue.Queue() + threading.Thread(target=run_backend, args=(host, port, ready, errors), daemon=False).start() - ready.wait() + ready.wait(timeout=5) - if not exception_holder.empty(): - raise exception_holder.get() + if not errors.empty(): + error_window(errors.get()) + return + from app.library.version import APP_VERSION + + gui = "edgechromium" if os.name == "nt" else "qt" create_kwargs = {**win_conf, "resizable": True} - - webview.settings["ALLOW_DOWNLOADS"] = True - webview.settings["OPEN_DEVTOOLS_IN_DEBUG"] = False - window = webview.create_window(APP_NAME, f"http://{host}:{port}", **create_kwargs) + window = webview.create_window(f"{APP_NAME} - {APP_VERSION}", f"http://{host}:{port}", **create_kwargs) def save_geometry(): cfg = { @@ -116,13 +130,22 @@ if __name__ == "__main__": window.events.resized += lambda *_: save_geometry() window.events.moved += lambda *_: save_geometry() + window.events.closing += lambda *_: os._exit(0) - gui = os.getenv("YTP_WV_GUI", None) - gui = "edgechromium" if os.name == "nt" else "qt" - + webview.settings["ALLOW_DOWNLOADS"] = True + webview.settings["OPEN_DEVTOOLS_IN_DEBUG"] = False webview.start( gui=gui, debug=True, storage_path=str(Path(os.getenv("YTP_TEMP_PATH", os.getcwd())) / "webview"), private_mode=False, ) + + +if __name__ == "__main__": + try: + main() + except Exception as e: + logging.exception(e) + error_window(e) + os._exit(1) diff --git a/ui/components/NewDownload.vue b/ui/components/NewDownload.vue index 581d714e..1bb75a0b 100644 --- a/ui/components/NewDownload.vue +++ b/ui/components/NewDownload.vue @@ -76,7 +76,8 @@ Opts -
+
{{ get_preset(form.preset)?.description }}