diff options
| -rw-r--r-- | plugin_loader/loader.py | 77 | ||||
| -rw-r--r-- | plugin_loader/main.py | 5 | ||||
| -rw-r--r-- | plugin_template.py | 9 | ||||
| -rw-r--r-- | requirements.txt | 3 |
4 files changed, 73 insertions, 21 deletions
diff --git a/plugin_loader/loader.py b/plugin_loader/loader.py index 0ed58b39..3d234cae 100644 --- a/plugin_loader/loader.py +++ b/plugin_loader/loader.py @@ -1,17 +1,43 @@ from aiohttp import web from aiohttp_jinja2 import template +from watchdog.observers.polling import PollingObserver as Observer +from watchdog.events import FileSystemEventHandler from os import path, listdir from importlib.util import spec_from_file_location, module_from_spec from logging import getLogger -import injector +from injector import get_tabs + +class FileChangeHandler(FileSystemEventHandler): + def __init__(self, loader) -> None: + super().__init__() + self.loader : Loader = loader + + def on_created(self, event): + src_path = event.src_path + if "__pycache__" in src_path: + return + self.loader.import_plugin(src_path) + + def on_modified(self, event): + src_path = event.src_path + if "__pycache__" in src_path: + return + self.loader.import_plugin(src_path) class Loader: - def __init__(self, server_instance, plugin_path) -> None: + def __init__(self, server_instance, plugin_path, loop, live_reload=False) -> None: + self.loop = loop self.logger = getLogger("Loader") self.plugin_path = plugin_path - self.plugins = self.import_plugins() + self.plugins = {} + self.import_plugins() + + if live_reload: + self.observer = Observer() + self.observer.schedule(FileChangeHandler(self), self.plugin_path) + self.observer.start() server_instance.add_routes([ web.get("/plugins/iframe", self.plugin_iframe_route), @@ -21,29 +47,48 @@ class Loader: web.get("/steam_resource/{path:.+}", self.get_steam_resource) ]) + def import_plugin(self, file): + try: + spec = spec_from_file_location("_", file) + module = module_from_spec(spec) + spec.loader.exec_module(module) + if not hasattr(module.Plugin, "name"): + raise KeyError("Plugin {} has not defined a name".format(file)) + if module.Plugin.name in self.plugins: + if hasattr(module.Plugin, "hot_reload") and not module.Plugin.hot_reload: + self.logger.info("Plugin {} is already loaded and has requested to not be re-loaded" + .format(module.Plugin.name)) + else: + if hasattr(self.plugins[module.Plugin.name], "task"): + self.plugins[module.Plugin.name].task.cancel() + self.plugins.pop(module.Plugin.name, None) + self.plugins[module.Plugin.name] = module.Plugin() + if hasattr(module.Plugin, "__main"): + setattr(self.plugins[module.Plugin.name], "task", + self.loop.create_task(self.plugins[module.Plugin.name].__main())) + self.logger.info("Loaded {}".format(module.Plugin.name)) + except Exception as e: + self.logger.error("Could not load {}. {}".format(file, e)) + def import_plugins(self): files = [i for i in listdir(self.plugin_path) if i.endswith(".py")] - dc = {} for file in files: - try: - spec = spec_from_file_location("_", path.join(self.plugin_path, file)) - module = module_from_spec(spec) - spec.loader.exec_module(module) - dc[module.Plugin.name] = module.Plugin - self.logger.info("Loaded {}".format(module.Plugin.name)) - except Exception as e: - self.logger.error("Could not load {}. {}".format(file, e)) - return dc + self.import_plugin(path.join(self.plugin_path, file)) + + async def watch_for_file_change(self): + pass async def reload_plugins(self, request=None): - self.logger.info("Re-importing all plugins.") - self.plugins = self.import_plugins() + self.logger.info("Re-importing plugins.") + self.import_plugins() async def handle_plugin_method_call(self, plugin_name, method_name, **kwargs): + if method_name.startswith("__"): + raise RuntimeError("Tried to call private method") return await getattr(self.plugins[plugin_name], method_name)(**kwargs) async def get_steam_resource(self, request): - tab = (await injector.get_tabs())[0] + tab = (await get_tabs())[0] return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html") async def load_plugin(self, request): diff --git a/plugin_loader/main.py b/plugin_loader/main.py index 4ecb5158..da371310 100644 --- a/plugin_loader/main.py +++ b/plugin_loader/main.py @@ -12,14 +12,15 @@ from utilities import util_methods CONFIG = { "plugin_path": getenv("PLUGIN_PATH", "/home/deck/homebrew/plugins"), "server_host": getenv("SERVER_HOST", "127.0.0.1"), - "server_port": int(getenv("SERVER_PORT", "1337")) + "server_port": int(getenv("SERVER_PORT", "1337")), + "live_reload": getenv("LIVE_RELOAD", "1") == "1" } class PluginManager: def __init__(self) -> None: self.loop = get_event_loop() self.web_app = Application() - self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"]) + self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"]) jinja_setup(self.web_app, loader=FileSystemLoader(path.join(path.dirname(__file__), 'templates'))) self.web_app.on_startup.append(self.inject_javascript) diff --git a/plugin_template.py b/plugin_template.py index be477d37..43088b14 100644 --- a/plugin_template.py +++ b/plugin_template.py @@ -7,8 +7,13 @@ class Plugin: tile_view_html = "" - async def method_1(**kwargs): + hot_reload = False + + async def __main(self): + pass + + async def method_1(self, **kwargs): pass - async def method_2(**kwargs): + async def method_2(self, **kwargs): pass
\ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 513ec897..c77a53ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ aiohttp==3.8.1 -aiohttp-jinja2==1.5.0
\ No newline at end of file +aiohttp-jinja2==1.5.0 +watchdog==2.1.7
\ No newline at end of file |
