import inspect import logging import threading from typing import Any, Literal, TYPE_CHECKING, Union from functools import wraps from typing import Callable from dzgui.const.constants import APP_NAME from dzgui.views.dialogs.generic import WaitDialog if TYPE_CHECKING: from dzgui.controllers.mc import Controller import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk, GLib # noqa E402 logger = logging.getLogger(APP_NAME) def call_on_thread( dialog_str: str, show_dialog: bool = True, show_cancel: bool = False ) -> Callable: def decorator(func: Callable) -> Callable: @wraps(func) def wrapper(*args: Any, **kwargs: Any) -> None: self = args[0] stored = StoredFunc(func, *args, **kwargs) if not hasattr(self, "thread_man"): raise AttributeError(f"Object '{self}' has no attribute 'thread_man'") if type(self.thread_man) is not ThreadingManager: raise TypeError( "Attribute 'thread_man' must be of type 'ThreadingManager'" ) self.thread_man.call_on_thread(dialog_str, stored, show_dialog, show_cancel) return wrapper return decorator class StoredFunc: def __init__(self, func: Callable, *args: Any, **kwargs: Any) -> None: sig = inspect.signature(func) self.func = func self.bindings = sig.bind(*args, **kwargs) def call(self) -> Any: return self.func(*self.bindings.args, *self.bindings.kwargs) class ThreadingManager: def __init__(self, controller: Union["Controller", None]) -> None: self.controller = controller self.jobs = 1 self.cleanup_func: StoredFunc | None = None self.destroy_first = False def call_on_thread( self, dialog_str: str, func: StoredFunc, show_dialog: bool = True, show_cancel: bool = False, ) -> None: def callback() -> None: func.call() GLib.idle_add(self._destroy_on_idle) if show_dialog is True and self.controller is not None: self.wait_dialog = WaitDialog( self.controller, dialog_str, jobs=self.jobs, show_cancel=show_cancel ) self.wait_dialog.show_all() thread = threading.Thread(target=callback) thread.start() def set_job_count(self, jobs: int) -> None: self.jobs = jobs def update_dialog(self, msg: str) -> None: GLib.idle_add(lambda: self.wait_dialog.update_text(msg)) def increment_dialog(self) -> None: GLib.idle_add(self.wait_dialog.increment) def increment_dialog_with_str(self, text: str) -> None: GLib.idle_add(lambda: self.wait_dialog.increment(text)) def set_cleanup_func( self, func: StoredFunc | None, destroy_first: bool = False ) -> None: if type(func) not in (StoredFunc, type(None)): msg = f"Callback function '{func}' is not of type StoredFunc or None" logger.critical(msg) raise TypeError(msg) self.destroy_first = destroy_first self.cleanup_func = func def get_cleanup_func(self) -> StoredFunc | None: return self.cleanup_func def destroy_dialog(self) -> None: if hasattr(self, "wait_dialog"): self.wait_dialog.destroy() def _destroy_on_idle(self) -> Literal[False]: if self.destroy_first: self.destroy_dialog() func = self.get_cleanup_func() if func is not None: self.set_cleanup_func(None) func.call() if not self.destroy_first: self.destroy_dialog() return False def get_wait_dialog(self) -> WaitDialog: return self.wait_dialog def show_cancel(self, state: bool) -> None: GLib.idle_add(self.wait_dialog.show_cancel, state)