Reworked plugin manifest; decoupled event system and ipc

This commit is contained in:
itdominator 2022-08-12 22:54:16 -05:00
parent b058dc3667
commit bff54995fd
22 changed files with 253 additions and 207 deletions

View File

@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@ -0,0 +1,12 @@
{
"manifest": {
"name": "Properties",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {
"ui_target": "context_menu",
"pass_fm_events": "true"
}
}
}

View File

@ -25,17 +25,6 @@ def daemon_threaded(fn):
class Manifest:
path: str = os.path.dirname(os.path.realpath(__file__))
name: str = "Properties"
author: str = "ITDominator"
version: str = "0.0.1"
support: str = ""
requests: {} = {
'ui_target': "context_menu",
'pass_fm_events': "true"
}
class Properties: class Properties:
file_uri: str = None file_uri: str = None
file_name: str = None file_name: str = None
@ -50,8 +39,11 @@ class Properties:
chmod_stat: str = None chmod_stat: str = None
class Plugin(Manifest): class Plugin:
def __init__(self): def __init__(self):
self.path = os.path.dirname(os.path.realpath(__file__))
self.name = "Properties" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
# where self.name should not be needed for message comms
self._GLADE_FILE = f"{self.path}/file_properties.glade" self._GLADE_FILE = f"{self.path}/file_properties.glade"
self._builder = None self._builder = None
self._properties_dialog = None self._properties_dialog = None

View File

@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@ -0,0 +1,13 @@
{
"manifest": {
"name": "Search",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {
"ui_target": "context_menu",
"pass_fm_events": "true",
"bind_keys": ["Search||_show_grep_list_page:<Control>f"]
}
}
}

View File

@ -25,21 +25,6 @@ def daemon_threaded(fn):
class Manifest:
path: str = os.path.dirname(os.path.realpath(__file__))
name: str = "Search"
author: str = "ITDominator"
version: str = "0.0.1"
support: str = ""
requests: {} = {
'ui_target': "context_menu",
'pass_fm_events': "true",
'bind_keys': [f"{name}||_show_grep_list_page:<Control>f"]
}
class FilePreviewWidget(Gtk.LinkButton): class FilePreviewWidget(Gtk.LinkButton):
def __init__(self, path, file): def __init__(self, path, file):
super(FilePreviewWidget, self).__init__() super(FilePreviewWidget, self).__init__()
@ -89,8 +74,11 @@ grep_result_set = manager.dict()
file_result_set = manager.list() file_result_set = manager.list()
class Plugin(Manifest): class Plugin:
def __init__(self): def __init__(self):
self.path = os.path.dirname(os.path.realpath(__file__))
self.name = "Search" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
# where self.name should not be needed for message comms
self._GLADE_FILE = f"{self.path}/search_dialog.glade" self._GLADE_FILE = f"{self.path}/search_dialog.glade"
self._builder = None self._builder = None
self._search_dialog = None self._search_dialog = None

View File

@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@ -0,0 +1,13 @@
{
"manifest": {
"name": "Example Plugin",
"author": "John Doe",
"version": "0.0.1",
"support": "",
"requests": {
"ui_target": "plugin_control_list",
"pass_fm_events": "true",
"bind_keys": ["Example Plugin||send_message:<Control>f"]
}
}
}

View File

@ -24,21 +24,10 @@ def daemon_threaded(fn):
class Manifest: class Plugin:
path: str = os.path.dirname(os.path.realpath(__file__))
name: str = "Example Plugin"
author: str = "John Doe"
version: str = "0.0.1"
support: str = ""
requests: {} = {
'ui_target': "plugin_control_list",
'pass_fm_events': "true",
'bind_keys': [f"{name}||send_message:<Control>f"]
}
class Plugin(Manifest):
def __init__(self): def __init__(self):
self.name = "Example Plugin" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
# where self.name should not be needed for message comms
self._event_system = None self._event_system = None
self._event_sleep_time = .5 self._event_sleep_time = .5
self._event_message = None self._event_message = None
@ -58,7 +47,6 @@ class Plugin(Manifest):
def send_message(self, widget=None, eve=None): def send_message(self, widget=None, eve=None):
message = "Hello, World!" message = "Hello, World!"
print("here")
self._event_system.push_gui_event([self.name, "display_message", ("warning", message, None)]) self._event_system.push_gui_event([self.name, "display_message", ("warning", message, None)])

View File

@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@ -0,0 +1,12 @@
{
"manifest": {
"name": "VOD Thumbnailer",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {
"ui_target": "context_menu",
"pass_fm_events": "true"
}
}
}

View File

@ -26,19 +26,11 @@ def daemon_threaded(fn):
class Manifest: class Plugin:
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): def __init__(self):
self.path = os.path.dirname(os.path.realpath(__file__))
self.name = "VOD Thumbnailer" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
# where self.name should not be needed for message comms
self._GLADE_FILE = f"{self.path}/re_thumbnailer.glade" self._GLADE_FILE = f"{self.path}/re_thumbnailer.glade"
self._builder = None self._builder = None
self._thumbnailer_dialog = None self._thumbnailer_dialog = None

View File

@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@ -0,0 +1,12 @@
{
"manifest": {
"name": "Youtube Download",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {
"ui_target": "plugin_control_list",
"pass_fm_events": "true"
}
}
}

View File

@ -24,21 +24,10 @@ def daemon_threaded(fn):
class Manifest: class Plugin:
path: str = os.path.dirname(os.path.realpath(__file__))
name: str = "Youtube Download"
author: str = "ITDominator"
version: str = "0.0.1"
support: str = ""
requests: {} = {
'ui_target': "plugin_control_list",
'pass_fm_events': "true"
}
class Plugin(Manifest):
def __init__(self): def __init__(self):
self.name = "Youtube Download" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
# where self.name should not be needed for message comms
self._event_system = None self._event_system = None
self._event_sleep_time = .5 self._event_sleep_time = .5
self._event_message = None self._event_message = None

View File

@ -4,16 +4,18 @@ import builtins, threading
# Lib imports # Lib imports
# Application imports # Application imports
from utils.ipc_server import IPCServer from utils.event_system import EventSystem
# NOTE: Threads will not die with parent's destruction
# NOTE: Threads WILL NOT die with parent's destruction.
def threaded_wrapper(fn): def threaded_wrapper(fn):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
return wrapper return wrapper
# NOTE: Insure threads die with parent's destruction # NOTE: Threads WILL die with parent's destruction.
def daemon_threaded_wrapper(fn): def daemon_threaded_wrapper(fn):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
@ -22,71 +24,12 @@ def daemon_threaded_wrapper(fn):
class EventSystem(IPCServer):
""" Inheret IPCServerMixin. Create an pub/sub systems. """
def __init__(self):
super(EventSystem, self).__init__()
# NOTE: The format used is list of ['who', target, (data,)] Where:
# who is the sender or target ID and is used for context and control flow,
# method_target is the method to call,
# data is the method parameters OR message data to give
# Where data may be any kind of data
self._gui_events = []
self._module_events = []
# Makeshift "events" system FIFO
def _pop_gui_event(self) -> None:
if len(self._gui_events) > 0:
return self._gui_events.pop(0)
return None
def _pop_module_event(self) -> None:
if len(self._module_events) > 0:
return self._module_events.pop(0)
return None
def push_gui_event(self, event: list) -> None:
if len(event) == 3:
self._gui_events.append(event)
return None
raise Exception("Invald event format! Please do: ['sender_id': str, method_target: method, (data,): any]")
def push_module_event(self, event: list) -> None:
if len(event) == 3:
self._module_events.append(event)
return None
raise Exception("Invald event format! Please do: ['target_id': str, method_target: method, (data,): any]")
def read_gui_event(self) -> list:
return self._gui_events[0] if self._gui_events else None
def read_module_event(self) -> list:
return self._module_events[0] if self._module_events else None
def consume_gui_event(self) -> list:
return self._pop_gui_event()
def consume_module_event(self) -> list:
return self._pop_module_event()
class EndpointRegistry(): class EndpointRegistry():
def __init__(self): def __init__(self):
self._endpoints = {} self._endpoints = {}
def register(self, rule, **options): def register(self, rule, **options):
def decorator(f): def decorator(f):
_endpoint = options.pop("endpoint", None)
self._endpoints[rule] = f self._endpoints[rule] = f
return f return f

View File

@ -4,31 +4,33 @@ import os, inspect, time
# Lib imports # Lib imports
# Application imports # Application imports
from __builtins__ import EventSystem from __builtins__ import *
from utils.ipc_server import IPCServer
from utils.settings import Settings from utils.settings import Settings
from core.controller import Controller from core.controller import Controller
class Application(IPCServer):
class Application(EventSystem):
""" Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """
def __init__(self, args, unknownargs): def __init__(self, args, unknownargs):
super(Application, self).__init__()
if not trace_debug: if not trace_debug:
event_system.create_ipc_listener() self.create_ipc_listener()
time.sleep(0.05) time.sleep(0.05)
if not event_system.is_ipc_alive: if not self.is_ipc_alive:
if unknownargs: if unknownargs:
for arg in unknownargs: for arg in unknownargs:
if os.path.isdir(arg): if os.path.isdir(arg):
message = f"FILE|{arg}" message = f"FILE|{arg}"
event_system.send_ipc_message(message) self.send_ipc_message(message)
if args.new_tab and os.path.isdir(args.new_tab): if args.new_tab and os.path.isdir(args.new_tab):
message = f"FILE|{args.new_tab}" message = f"FILE|{args.new_tab}"
event_system.send_ipc_message(message) self.send_ipc_message(message)
raise Exception("IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/solarfm-ipc.sock") raise Exception("IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/solarfm-ipc.sock")

View File

@ -43,7 +43,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi
def tear_down(self, widget=None, eve=None): def tear_down(self, widget=None, eve=None):
event_system.send_ipc_message("close server")
self.fm_controller.save_state() self.fm_controller.save_state()
time.sleep(event_sleep_time) time.sleep(event_sleep_time)
Gtk.main_quit() Gtk.main_quit()

View File

@ -0,0 +1,76 @@
# Python imports
import os, json
from os.path import join
# Lib imports
# Application imports
class Plugin:
path: str = None
name: str = None
author: str = None
version: str = None
support: str = None
requests:{} = None
reference: type = None
class ManifestProcessor:
def __init__(self, path, builder):
manifest = join(path, "manifest.json")
if not os.path.exists(manifest):
raise Exception("Invalid Plugin Structure: Plugin doesn't have 'manifest.json'. Aboarting load...")
self._path = path
self._builder = builder
with open(manifest) as f:
data = json.load(f)
self._manifest = data["manifest"]
self._plugin = self.collect_info()
def collect_info(self) -> Plugin:
plugin = Plugin()
plugin.path = self._path
plugin.name = self._manifest["name"]
plugin.author = self._manifest["author"]
plugin.version = self._manifest["version"]
plugin.support = self._manifest["support"]
plugin.requests = self._manifest["requests"]
return plugin
def get_loading_data(self):
loading_data = {}
requests = self._plugin.requests
keys = requests.keys()
if "ui_target" in keys:
if requests["ui_target"] in [
"none", "other", "main_Window", "main_menu_bar", "path_menu_bar", "plugin_control_list",
"context_menu", "window_1", "window_2", "window_3", "window_4"
]:
if requests["ui_target"] == "other":
if "ui_target_id" in keys:
loading_data["ui_target"] = self._builder.get_object(requests["ui_target_id"])
if loading_data["ui_target"] == None:
raise Exception('Invalid "ui_target_id" given in requests. Must have one if setting "ui_target" to "other"...')
else:
raise Exception('Invalid "ui_target_id" given in requests. Must have one if setting "ui_target" to "other"...')
else:
loading_data["ui_target"] = self._builder.get_object(requests["ui_target"])
else:
raise Exception('Unknown "ui_target" given in requests.')
if "pass_fm_events" in keys:
if requests["pass_fm_events"] in ["true"]:
loading_data["pass_fm_events"] = True
if "bind_keys" in keys:
if isinstance(requests["bind_keys"], list):
loading_data["bind_keys"] = requests["bind_keys"]
return self._plugin, loading_data

View File

@ -8,17 +8,9 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio from gi.repository import Gtk, Gio
# Application imports # Application imports
from .manifest import Plugin, ManifestProcessor
class Plugin:
path: str = None
name: str = None
author: str = None
version: str = None
support: str = None
requests:{} = None
reference: type = None
class Plugins: class Plugins:
@ -28,7 +20,7 @@ class Plugins:
self._settings = settings self._settings = settings
self._builder = self._settings.get_builder() self._builder = self._settings.get_builder()
self._plugins_path = self._settings.get_plugins_path() self._plugins_path = self._settings.get_plugins_path()
self._keybindings = self._settings.get_keybindings() self._keybindings = self._settings.get_keybindings()
self._plugins_dir_watcher = None self._plugins_dir_watcher = None
self._plugin_collection = [] self._plugin_collection = []
@ -55,20 +47,18 @@ class Plugins:
for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]: for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]:
try: try:
target = join(path, "plugin.py") target = join(path, "plugin.py")
manifest = ManifestProcessor(path, self._builder)
if not os.path.exists(target): if not os.path.exists(target):
raise Exception("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...") raise Exception("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
module = self.load_plugin_module(path, folder, target) plugin, loading_data = manifest.get_loading_data()
plugin = self.collect_info(module, path) module = self.load_plugin_module(path, folder, target)
loading_data = self.parse_requests(plugin)
self.execute_plugin(module, plugin, loading_data) self.execute_plugin(module, plugin, loading_data)
except Exception as e: except Exception as e:
print(f"Malformed Plugin: Not loading -->: '{folder}' !") print(f"Malformed Plugin: Not loading -->: '{folder}' !")
traceback.print_exc() traceback.print_exc()
# if debug:
# traceback.print_exc()
os.chdir(parent_path) os.chdir(parent_path)
@ -82,49 +72,6 @@ class Plugins:
return module return module
def collect_info(self, module, path) -> Plugin:
plugin = Plugin()
plugin.path = module.Manifest.path
plugin.name = module.Manifest.name
plugin.author = module.Manifest.author
plugin.version = module.Manifest.version
plugin.support = module.Manifest.support
plugin.requests = module.Manifest.requests
return plugin
def parse_requests(self, plugin):
loading_data = {}
requests = plugin.requests
keys = requests.keys()
if "ui_target" in keys:
if requests["ui_target"] in [
"none", "other", "main_Window", "main_menu_bar", "path_menu_bar", "plugin_control_list",
"context_menu", "window_1", "window_2", "window_3", "window_4"
]:
if requests["ui_target"] == "other":
if "ui_target_id" in keys:
loading_data["ui_target"] = self._builder.get_object(requests["ui_target_id"])
if loading_data["ui_target"] == None:
raise Exception('Invalid "ui_target_id" given in requests. Must have one if setting "ui_target" to "other"...')
else:
raise Exception('Invalid "ui_target_id" given in requests. Must have one if setting "ui_target" to "other"...')
else:
loading_data["ui_target"] = self._builder.get_object(requests["ui_target"])
else:
raise Exception('Unknown "ui_target" given in requests.')
if "pass_fm_events" in keys:
if requests["pass_fm_events"] in ["true"]:
loading_data["pass_fm_events"] = True
if "bind_keys" in keys:
if isinstance(requests["bind_keys"], list):
loading_data["bind_keys"] = requests["bind_keys"]
return loading_data
def execute_plugin(self, module: type, plugin: Plugin, loading_data: []): def execute_plugin(self, module: type, plugin: Plugin, loading_data: []):
plugin.reference = module.Plugin() plugin.reference = module.Plugin()

View File

@ -0,0 +1,59 @@
# Python imports
# Lib imports
# Application imports
class EventSystem:
""" Inheret IPCServerMixin. Create an pub/sub systems. """
def __init__(self):
# NOTE: The format used is list of ['who', target, (data,)] Where:
# who is the sender or target ID and is used for context and control flow,
# method_target is the method to call,
# data is the method parameters OR message data to give
# Where data may be any kind of data
self._gui_events = []
self._module_events = []
# Makeshift "events" system FIFO
def _pop_gui_event(self) -> None:
if len(self._gui_events) > 0:
return self._gui_events.pop(0)
return None
def _pop_module_event(self) -> None:
if len(self._module_events) > 0:
return self._module_events.pop(0)
return None
def push_gui_event(self, event: list) -> None:
if len(event) == 3:
self._gui_events.append(event)
return None
raise Exception("Invald event format! Please do: ['sender_id': str, method_target: method, (data,): any]")
def push_module_event(self, event: list) -> None:
if len(event) == 3:
self._module_events.append(event)
return None
raise Exception("Invald event format! Please do: ['target_id': str, method_target: method, (data,): any]")
def read_gui_event(self) -> list:
return self._gui_events[0] if self._gui_events else None
def read_module_event(self) -> list:
return self._module_events[0] if self._module_events else None
def consume_gui_event(self) -> list:
return self._pop_gui_event()
def consume_module_event(self) -> list:
return self._pop_module_event()

View File

@ -7,12 +7,6 @@ from multiprocessing.connection import Listener, Client
# Application imports # Application imports
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
class IPCServer: class IPCServer:
@ -36,7 +30,7 @@ class IPCServer:
self._ipc_authkey = None self._ipc_authkey = None
@threaded @daemon_threaded
def create_ipc_listener(self) -> None: def create_ipc_listener(self) -> None:
if self._conn_type == "socket": if self._conn_type == "socket":
if os.path.exists(self._ipc_address): if os.path.exists(self._ipc_address):