diff --git a/src/modules/ConsoleSystem/__init__.py b/src/modules/ConsoleSystem/__init__.py index 8ba2410..b948c22 100644 --- a/src/modules/ConsoleSystem/__init__.py +++ b/src/modules/ConsoleSystem/__init__.py @@ -144,6 +144,10 @@ class Console: rcon.console = self self.rcon = rcon + @property + def legacy_mode(self): + return self.__legacy_mode + def __debug(self, *x): self.__logger.debug(' '.join(x)) # if self.__is_debug: @@ -213,13 +217,14 @@ class Console: if v['f'] is func: keys.append(k) for key in keys: - self.__debug(f"Delete: key={key}") + self.__debug(f"{key=}") self.__alias.pop(key) self.__alias["man"].pop(key) self.__func.pop(key) self.__man.pop(key) self.__desc.pop(key) - self.__debug("Deleted.") + if keys: + self.__debug("Deleted.") self.completer.load(self.__alias) def add_command(self, key: str, func, man: str = None, desc: str = None, custom_completer: dict = None) -> dict: diff --git a/src/modules/EventsSystem/__init__.py b/src/modules/EventsSystem/__init__.py index 8a75e00..67cdb01 100644 --- a/src/modules/EventsSystem/__init__.py +++ b/src/modules/EventsSystem/__init__.py @@ -96,17 +96,34 @@ class EventsSystem: self.log.debug("used builtins_hook") builtins.ev = self - def unregister(self, func): - self.log.debug(f"unregister {func}") + def unregister_by_id(self, _id): + self.log.debug(f"unregister_by_id '{_id}'") + if not isinstance(_id, int): + return s = a = 0 for k, funcs in self.__events.items(): for f in funcs: - if f is func: + if id(f) == _id: + s += 1 + self.__events[k].remove(f) + for k, funcs in self.__async_events.items(): + for f in funcs: + if id(f) == _id: + a += 1 + self.__async_events[k].remove(f) + self.log.debug(f"unregister in {s + a} events; S:{s}; A:{a};") + + def unregister(self, func): + self.log.debug(f"unregister '{func.__name__}' id: {id(func)}") + s = a = 0 + for k, funcs in self.__events.items(): + for f in funcs: + if f == func: s += 1 self.__events[k].remove(func) for k, funcs in self.__async_events.items(): for f in funcs: - if f is func: + if f == func: a += 1 self.__async_events[k].remove(func) self.log.debug(f"unregister in {s + a} events; S:{s}; A:{a};") @@ -116,8 +133,8 @@ class EventsSystem: event_name in self.__events.keys() or event_name in self.__lua_events.keys()) - def register(self, event_name, event_func, async_event=False, lua=None): - self.log.debug(f"register(event_name='{event_name}', event_func='{event_func}', " + def register(self, event_name, event_func, async_event=False, lua=None, return_id=True): + self.log.debug(f"register(event_name='{event_name}', event_func='{event_func.__name__}'(id: {id(event_func)}), " f"async_event={async_event}, lua_event={lua}):") if lua: if event_name not in self.__lua_events: @@ -140,6 +157,8 @@ class EventsSystem: self.__events[event_name] = [] self.__events[event_name].append(event_func) self.log.debug("Register ok") + if return_id: + return id(event_func) async def call_as_events(self, *args, **kwargs): return await self.call_async_event(*args, **kwargs) + self.call_event(*args, **kwargs) diff --git a/src/modules/EventsSystem/readme.md b/src/modules/EventsSystem/readme.md index 00a88bf..d9e6704 100644 --- a/src/modules/EventsSystem/readme.md +++ b/src/modules/EventsSystem/readme.md @@ -3,12 +3,17 @@ ```python class EventsSystem: @staticmethod - def register(event_name, event_func, async_event: bool = False, lua: bool | object = None): ... + def unregister_by_id(_id: int) -> None: ... @staticmethod - async def call_async_event(event_name, *args, **kwargs) -> list[Any]: ... + def unregister(func: Callable | Awaitable): ... @staticmethod - def call_event(event_name, *data, **kwargs) -> list[Any]: ... + def register(event_name: str, event_func: Callable | Awaitable, async_event: bool = False, lua: bool | object = None) -> None | int: ... @staticmethod - def call_lua_event(event_name, *data) -> list[Any]: ... -class ev(EventsSystem): ... + async def call_as_events(event_name: str, *args, **kwargs) -> list[Any]: ... + @staticmethod + async def call_async_event(event_name: str, *args, **kwargs) -> list[Any]: ... + @staticmethod + def call_event(event_name: str, *data, **kwargs) -> list[Any]: ... + @staticmethod + def call_lua_event(event_name: str, *data) -> list[Any]: ... ``` \ No newline at end of file diff --git a/src/modules/PluginsLoader/__init__.py b/src/modules/PluginsLoader/__init__.py index c9a74a1..ca18823 100644 --- a/src/modules/PluginsLoader/__init__.py +++ b/src/modules/PluginsLoader/__init__.py @@ -11,12 +11,22 @@ import inspect import os import subprocess import sys +import textwrap import time import types from contextlib import contextmanager from pathlib import Path from threading import Thread +from prompt_toolkit import PromptSession, HTML +from prompt_toolkit.auto_suggest import AutoSuggestFromHistory +from prompt_toolkit.history import FileHistory +from prompt_toolkit.lexers import PygmentsLexer +try: + from pygments.lexers.python import Python3Lexer +except ImportError: + print("ImportError: Python3Lexer") + exit(1) from core import get_logger @@ -67,13 +77,13 @@ class KuiToi: def register(self, event_name, event_func): self.log.debug(f"Registering event {event_name}") - self.__funcs.append(event_func) - ev.register(event_name, event_func) + _id = ev.register(event_name, event_func) + self.__funcs.append(_id) def _unload(self): for f in self.__funcs: console.del_command(f) - ev.unregister(f) + ev.unregister_by_id(f) def call_event(self, event_name, *args, **kwargs): self.log.debug(f"Called event {event_name}") @@ -121,17 +131,88 @@ class PluginsLoader: self.plugins_dir = plugins_dir self.log = get_logger("PluginsLoader") self.loaded = [] + self.pl_completer = Completer({}) + self.pl_files_completer = Completer({}) + self._scan_dir(None) + ev.register("serverTick_5s", self._scan_dir) ev.register("_plugins_start", self.start) ev.register("_plugins_unload", self.unload) - ev.register("_plugins_get", lambda _: "Plugins: " + ", ".join(f"{i[0]}:{'on' if i[1] else 'off'}" for i in self.loaded)) - console.add_command("plugins", self._parse_console, None, "Plugins manipulations", {"plugins": {"reload", "load", "unload", "list"}}) - console.add_command("pl", lambda _: ev.call_event("_plugins_get")[0]) + ev.register("_plugins_get", + lambda _: "Plugins: " + ", ".join(f"{i[0]}:{'on' if i[1] else 'off'}" for i in self.loaded)) + console.add_command("plugins", self._parse_console, None, "Plugins manipulations", + {"plugins": { + "reload": self.pl_completer, + "load": self.pl_files_completer, + "unload": self.pl_completer, + "list": None, + }}) + console.add_command("plugin", self._plugin_console, None, "plugin console", {"plugin": self.pl_completer}) sys.path.append(self._pip_dir) os.makedirs(self._pip_dir, exist_ok=True) console.add_command("install", self._pip_install) + def _scan_dir(self, _): + _load = {} + for file in os.listdir(self.plugins_dir): + file_path = os.path.join(self.plugins_dir, file) + if os.path.isfile(file_path) and file.endswith(".py"): + _load[file] = None + self.pl_files_completer.load(_load) + + async def _plugin_console(self, x): + usage = 'Usage: plugin ' + if not x: + return usage + if x[0] in self.plugins: + plugin = self.plugins[x[0]]['plugin'] + kt: KuiToi = plugin.kt + work = True + session = None + if not console.legacy_mode: + session = PromptSession(history=FileHistory(f'{kt.dir}/.cmdhistory'), lexer=PygmentsLexer(Python3Lexer)) + + def bottom_toolbar(): + x = lambda x: f'' + c = lambda c: f'' + return HTML(f'[PluginConsole KuiToi@{x(kt.name)}] {c("^D")} Return to the main console {c("^C")} Exit ') + while work: + try: + if session: + inp = await session.prompt_async(">> ", auto_suggest=AutoSuggestFromHistory(), bottom_toolbar=bottom_toolbar) + else: + inp = input(f"@{kt.name} > ") + self.log.debug(f"[_plugin_console] {inp=}") + if inp == "exit": + return "Exited" + if not inp: + continue + if inp.split(' ')[0] in ['import', 'from']: + kt.log.warning("Imports not allowed here... Sorry bro.") + continue + code = textwrap.dedent(f"""\ + async def _console(): + try: + i = {inp} + if i: + print(f"{{i!r}}") + except Exception as e: + kt.log.exception(e)""") + exec(code, plugin.__dict__) + kt.log.debug(await plugin._console()) + except SyntaxError as e: + kt.log.error(f"SyntaxError: {e.msg}") + except EOFError: + return + except KeyboardInterrupt as e: + raise e + except UnicodeDecodeError as e: + raise e + except Exception as e: + kt.log.exception(e) + return "Plugin not found" + async def _parse_console(self, x): - usage = 'Usage: plugin [reload | load | unload | list]' + usage = 'Usage: plugins [reload | load | unload | list]' if not x: return usage match x[0]: @@ -244,6 +325,7 @@ class PluginsLoader: th = Thread(target=plugin.load, name=f"{pl_name}.load()") th.start() th.join() + self.pl_completer.options[pl_name] = None self.loaded.append((pl_name, True)) self.log.debug(f"Plugin loaded: {file}. Settings: {self.plugins[pl_name]}") return pl_name @@ -283,22 +365,20 @@ class PluginsLoader: async def start(self, _): for pl_name, pl_data in self.plugins.items(): try: + func = pl_data['start']['func'] if pl_data['start']['async']: self.log.debug(f"Start async plugin: {pl_name}") - t = self.loop.create_task(pl_data['start']['func']()) - self.plugins_tasks.append(t) + t = self.loop.create_task(func()) else: self.log.debug(f"Start sync plugin: {pl_name}") - th = Thread(target=pl_data['start']['func'], name=f"Thread {pl_name}") - th.start() - self.plugins_tasks.append(th) + t = self.loop.create_task(asyncio.to_thread(func)) + self.plugins_tasks.append(t) except Exception as e: self.log.exception(e) async def unload(self, _): t = [] for n in self.plugins.keys(): - await asyncio.sleep(0.01) t.append(self._unload_by_name(n)) self.log.debug(await asyncio.gather(*t)) self.log.debug("Plugins unloaded")