From b058dc3667b21897de8e70ab6cac7d96b4f4dffb Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Tue, 9 Aug 2022 20:10:25 -0500 Subject: [PATCH] Global threading decorators; Endpoint registry creation --- plugins/vod_thumbnailer/__init__.py | 3 + plugins/vod_thumbnailer/plugin.py | 173 ++++++++++++++ plugins/vod_thumbnailer/re_thumbnailer.glade | 219 ++++++++++++++++++ .../SolarFM/solarfm/__builtins__.py | 39 +++- .../solarfm-0.0.1/SolarFM/solarfm/app.py | 2 +- .../SolarFM/solarfm/core/controller.py | 22 +- .../core/mixins/exception_hook_mixin.py | 6 +- .../solarfm/core/mixins/ui/grid_mixin.py | 8 +- .../mixins/ui/widget_file_action_mixin.py | 6 +- 9 files changed, 444 insertions(+), 34 deletions(-) create mode 100644 plugins/vod_thumbnailer/__init__.py create mode 100644 plugins/vod_thumbnailer/plugin.py create mode 100644 plugins/vod_thumbnailer/re_thumbnailer.glade diff --git a/plugins/vod_thumbnailer/__init__.py b/plugins/vod_thumbnailer/__init__.py new file mode 100644 index 0000000..d36fa8c --- /dev/null +++ b/plugins/vod_thumbnailer/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module +""" diff --git a/plugins/vod_thumbnailer/plugin.py b/plugins/vod_thumbnailer/plugin.py new file mode 100644 index 0000000..44deeb2 --- /dev/null +++ b/plugins/vod_thumbnailer/plugin.py @@ -0,0 +1,173 @@ +# Python imports +import os, threading, subprocess, time, inspect, hashlib +from datetime import datetime + +# Gtk imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('GdkPixbuf', '2.0') +from gi.repository import Gtk, GLib, Gio, GdkPixbuf + +# Application imports + + +# NOTE: Threads WILL NOT die with parent's destruction. +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Threads WILL die with parent's destruction. +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + + + +class Manifest: + path: str = os.path.dirname(os.path.realpath(__file__)) + name: str = "VOD Thumbnailer" + author: str = "ITDominator" + version: str = "0.0.1" + support: str = "" + requests: {} = { + 'ui_target': "context_menu", + 'pass_fm_events': "true" + } + +class Plugin(Manifest): + def __init__(self): + self._GLADE_FILE = f"{self.path}/re_thumbnailer.glade" + self._builder = None + self._thumbnailer_dialog = None + self._thumbnail_preview_img = None + self._file_name = None + self._file_location = None + self._file_hash = None + self._state = None + + self._event_system = None + self._event_sleep_time = .5 + self._event_message = None + + + + def get_ui_element(self): + self._builder = Gtk.Builder() + self._builder.add_from_file(self._GLADE_FILE) + + classes = [self] + handlers = {} + for c in classes: + methods = None + try: + methods = inspect.getmembers(c, predicate=inspect.ismethod) + handlers.update(methods) + except Exception as e: + print(repr(e)) + + self._builder.connect_signals(handlers) + + self._thumbnailer_dialog = self._builder.get_object("thumbnailer_dialog") + self._file_name = self._builder.get_object("file_name") + self._file_location = self._builder.get_object("file_location") + self._thumbnail_preview_img = self._builder.get_object("thumbnail_preview_img") + self._file_hash = self._builder.get_object("file_hash") + + button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._show_thumbnailer_page) + return button + + def set_fm_event_system(self, fm_event_system): + self._event_system = fm_event_system + + def run(self): + self._module_event_observer() + + + + + @threaded + def _show_thumbnailer_page(self, widget=None, eve=None): + self._event_system.push_gui_event([self.name, "get_current_state", ()]) + self.wait_for_fm_message() + + state = self._event_message + self._event_message = None + + GLib.idle_add(self._process_changes, (state)) + + def _process_changes(self, state): + self._state = None + + if len(state.selected_files) == 1: + if state.selected_files[0].lower().endswith(state.tab.fvideos): + self._state = state + self._set_ui_data() + response = self._thumbnailer_dialog.run() + if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]: + self._thumbnailer_dialog.hide() + + + def _regenerate_thumbnail(self, widget=None, eve=None): + print("Regenerating thumbnail...") + file = self._file_name.get_text() + dir = self._file_location.get_text() + file_hash = self._file_hash.get_text() + + hash_img_pth = f"{self._state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg" + try: + if os.path.isfile(hash_img_pth): + os.remove(hash_img_pth) + + img_pixbuf = self._state.tab.create_icon(dir, file) + self._thumbnail_preview_img.set_from_pixbuf(img_pixbuf) + except Exception as e: + print("Couldn't regenerate thumbnail!") + + def _use_selected_thumbnail(self, widget=None, eve=None): + print("_use_selected_thumbnail stub...") + + + def _set_ui_data(self): + uri = self._state.selected_files[0] + path = self._state.tab.get_current_directory() + parts = uri.split("/") + + file_hash = hashlib.sha256(str.encode(uri)).hexdigest() + hash_img_pth = f"{self._state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg" + img_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth) + + self._thumbnail_preview_img.set_from_pixbuf(img_pixbuf) + self._file_name.set_text(parts[ len(parts) - 1 ]) + self._file_location.set_text(path) + self._file_hash.set_text(file_hash) + + + + def wait_for_fm_message(self): + while not self._event_message: + pass + + @daemon_threaded + def _module_event_observer(self): + while True: + time.sleep(self._event_sleep_time) + event = self._event_system.read_module_event() + if event: + try: + if event[0] == self.name: + target_id, method_target, data = self._event_system.consume_module_event() + + if not method_target: + self._event_message = data + else: + method = getattr(self.__class__, f"{method_target}") + if data: + data = method(*(self, *data)) + else: + method(*(self,)) + except Exception as e: + print(repr(e)) diff --git a/plugins/vod_thumbnailer/re_thumbnailer.glade b/plugins/vod_thumbnailer/re_thumbnailer.glade new file mode 100644 index 0000000..1198dc4 --- /dev/null +++ b/plugins/vod_thumbnailer/re_thumbnailer.glade @@ -0,0 +1,219 @@ + + + + + + False + 6 + VOD Thumbnailer + True + center-on-parent + 420 + True + dialog + True + True + center + + + True + False + 12 + + + True + False + end + + + gtk-cancel + True + True + True + False + True + + + True + True + 0 + + + + + False + False + end + 0 + + + + + True + False + vertical + + + True + False + gtk-missing-image + 6 + + + True + True + 0 + + + + + + + + True + False + 4 + 4 + 2 + 12 + 6 + + + True + False + <b>File _Name:</b> + True + True + file_name + 0 + + + GTK_FILL + + + + + + True + True + False + + + 1 + 2 + + + + + + True + False + <b>_Location:</b> + True + True + file_location + 0 + + + 1 + 2 + GTK_FILL + + + + + + True + True + False + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + + Regenerate + True + True + True + + + + 1 + 2 + 3 + 4 + + + + + Use Selected + True + True + True + + + + 3 + 4 + + + + + True + False + <b>_Thumbnail Hash:</b> + True + True + file_location + 0 + + + 2 + 3 + GTK_FILL + + + + + + True + True + False + + + 1 + 2 + 2 + 3 + GTK_FILL + + + + + + False + True + 2 + + + + + True + True + 1 + + + + + + cancel_button + + + diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index ae1c305..15a4f9d 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -1,5 +1,5 @@ # Python imports -import builtins +import builtins, threading # Lib imports @@ -7,6 +7,21 @@ import builtins from utils.ipc_server import IPCServer +# NOTE: Threads will not die with parent's destruction +def threaded_wrapper(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Insure threads die with parent's destruction +def daemon_threaded_wrapper(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + + + class EventSystem(IPCServer): @@ -65,10 +80,32 @@ class EventSystem(IPCServer): +class EndpointRegistry(): + def __init__(self): + self._endpoints = {} + + def register(self, rule, **options): + def decorator(f): + _endpoint = options.pop("endpoint", None) + self._endpoints[rule] = f + return f + + return decorator + + def get_endpoints(self): + return self._endpoints + + + + # NOTE: Just reminding myself we can add to builtins two different ways... # __builtins__.update({"event_system": Builtins()}) builtins.app_name = "SolarFM" builtins.event_system = EventSystem() +builtins.endpoint_registry = EndpointRegistry() +builtins.threaded = threaded_wrapper +builtins.daemon_threaded = daemon_threaded_wrapper builtins.event_sleep_time = 0.05 builtins.trace_debug = False builtins.debug = False +builtins.app_settings = None diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py index bc414b5..e7b0d5f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py @@ -4,9 +4,9 @@ import os, inspect, time # Lib imports # Application imports +from __builtins__ import EventSystem from utils.settings import Settings from core.controller import Controller -from __builtins__ import EventSystem diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index 3b7ba50..6ddb4c9 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -1,5 +1,5 @@ # Python imports -import os, gc, threading, time +import os, gc, time # Lib imports import gi @@ -14,19 +14,6 @@ from .signals.keyboard_signals_mixin import KeyboardSignalsMixin from .controller_data import Controller_Data -# NOTE: Threads will not die with parent's destruction -def threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() - return wrapper - -# NOTE: Insure threads die with parent's destruction -def daemon_threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() - return wrapper - - class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data): @@ -177,23 +164,28 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi - + @endpoint_registry.register(rule="go_home") def go_home(self, widget=None, eve=None): self.builder.get_object("go_home").released() + @endpoint_registry.register(rule="refresh_tab") def refresh_tab(self, widget=None, eve=None): self.builder.get_object("refresh_tab").released() + @endpoint_registry.register(rule="go_up") def go_up(self, widget=None, eve=None): self.builder.get_object("go_up").released() + @endpoint_registry.register(rule="grab_focus_path_entry") def grab_focus_path_entry(self, widget=None, eve=None): self.builder.get_object("path_entry").grab_focus() + @endpoint_registry.register(rule="tggl_top_main_menubar") def tggl_top_main_menubar(self, widget=None, eve=None): top_main_menubar = self.builder.get_object("top_main_menubar") top_main_menubar.hide() if top_main_menubar.is_visible() else top_main_menubar.show() + @endpoint_registry.register(rule="open_terminal") def open_terminal(self, widget=None, eve=None): wid, tid = self.fm_controller.get_active_wid_and_tid() tab = self.get_fm_window(wid).get_tab_by_id(tid) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py index 81a0b95..660b9b6 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py @@ -1,5 +1,5 @@ # Python imports -import traceback, threading, time +import traceback, time # Lib imports import gi @@ -9,10 +9,6 @@ from gi.repository import Gtk, GLib # Application imports -def threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() - return wrapper class ExceptionHookMixin: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py index 01ff051..2d3afeb 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py @@ -1,5 +1,5 @@ # Python imports -import os, threading, subprocess, time +import os # Lib imports import gi @@ -11,12 +11,6 @@ from gi.repository import Gtk, Gdk, GLib, Gio, GdkPixbuf # Application imports -def threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() - return wrapper - - # NOTE: Consider trying to use Gtk.TreeView with css that turns it into a grid... diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py index 9fc5a83..71a3cc3 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py @@ -1,5 +1,5 @@ # Python imports -import os, time, threading, shlex +import os, time, shlex # Lib imports import gi @@ -9,10 +9,6 @@ from gi.repository import Gtk, GObject, GLib, Gio # Application imports -def threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() - return wrapper class WidgetFileActionMixin: