[+] plugins command

This commit is contained in:
2024-07-29 03:09:02 +03:00
parent 2b4c0bf4d0
commit 2bf1c07041
2 changed files with 168 additions and 95 deletions
+19 -6
View File
@@ -96,6 +96,21 @@ class EventsSystem:
self.log.debug("used builtins_hook") self.log.debug("used builtins_hook")
builtins.ev = self builtins.ev = self
def unregister(self, func):
self.log.debug(f"unregister {func}")
s = a = 0
for k, funcs in self.__events.items():
for f in funcs:
if f is func:
s += 1
self.__events[k].remove(func)
for k, funcs in self.__async_events.items():
for f in funcs:
if f is func:
a += 1
self.__async_events[k].remove(func)
self.log.debug(f"unregister in {s+a} events; S:{s}; A:{a};")
def is_event(self, event_name): def is_event(self, event_name):
return (event_name in self.__async_events.keys() or return (event_name in self.__async_events.keys() or
event_name in self.__events.keys() or event_name in self.__events.keys() or
@@ -117,15 +132,13 @@ class EventsSystem:
return return
if async_event or inspect.iscoroutinefunction(event_func): if async_event or inspect.iscoroutinefunction(event_func):
if event_name not in self.__async_events: if event_name not in self.__async_events:
self.__async_events.update({str(event_name): [event_func]}) self.__async_events[event_name] = []
else: self.__async_events[event_name].append(event_func)
self.__async_events[event_name].append(event_func)
self.log.debug("Register ok") self.log.debug("Register ok")
else: else:
if event_name not in self.__events: if event_name not in self.__events:
self.__events.update({str(event_name): [event_func]}) self.__events[event_name] = []
else: self.__events[event_name].append(event_func)
self.__events[event_name].append(event_func)
self.log.debug("Register ok") self.log.debug("Register ok")
async def call_async_event(self, event_name, *args, **kwargs): async def call_async_event(self, event_name, *args, **kwargs):
+149 -89
View File
@@ -11,6 +11,7 @@ import inspect
import os import os
import subprocess import subprocess
import sys import sys
import time
import types import types
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path from pathlib import Path
@@ -21,6 +22,7 @@ from core import get_logger
class KuiToi: class KuiToi:
_plugins_dir = "" _plugins_dir = ""
_file = ""
def __init__(self, name): def __init__(self, name):
if not name: if not name:
@@ -29,6 +31,8 @@ class KuiToi:
self.__name = name self.__name = name
self.__dir = Path(self._plugins_dir) / self.__name self.__dir = Path(self._plugins_dir) / self.__name
os.makedirs(self.__dir, exist_ok=True) os.makedirs(self.__dir, exist_ok=True)
self.__events_funcs = []
self.register_event = self.register
@property @property
def log(self): def log(self):
@@ -63,8 +67,13 @@ class KuiToi:
def register(self, event_name, event_func): def register(self, event_name, event_func):
self.log.debug(f"Registering event {event_name}") self.log.debug(f"Registering event {event_name}")
self.__events_funcs.append(event_func)
ev.register(event_name, event_func) ev.register(event_name, event_func)
def _unload(self):
for f in self.__events_funcs:
ev.unregister(f)
def call_event(self, event_name, *args, **kwargs): def call_event(self, event_name, *args, **kwargs):
self.log.debug(f"Called event {event_name}") self.log.debug(f"Called event {event_name}")
return ev.call_event(event_name, *args, **kwargs) return ev.call_event(event_name, *args, **kwargs)
@@ -109,16 +118,45 @@ class PluginsLoader:
self.plugins_tasks = [] self.plugins_tasks = []
self.plugins_dir = plugins_dir self.plugins_dir = plugins_dir
self.log = get_logger("PluginsLoader") self.log = get_logger("PluginsLoader")
self.loaded_str = "Plugins: " self.loaded = []
ev.register("_plugins_start", self.start) ev.register("_plugins_start", self.start)
ev.register("_plugins_unload", self.unload) ev.register("_plugins_unload", self.unload)
ev.register("_plugins_get", lambda x: list(self.plugins.keys())) 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", lambda x: self.loaded_str[:-2]) console.add_command("plugins", self._parse_console, None, "Plugins manipulations", {"plugins": {"reload", "load", "unload", "list"}})
console.add_command("pl", lambda x: self.loaded_str[:-2]) console.add_command("pl", lambda _: ev.call_event("_plugins_get")[0])
sys.path.append(self._pip_dir) sys.path.append(self._pip_dir)
os.makedirs(self._pip_dir, exist_ok=True) os.makedirs(self._pip_dir, exist_ok=True)
console.add_command("install", self._pip_install) console.add_command("install", self._pip_install)
async def _parse_console(self, x):
usage = 'Usage: plugin [reload <name> | load <file.py> | unload <name> | list]'
if not x:
return usage
match x[0]:
case 'reload':
if len(x) == 2:
t1 = time.monotonic()
ok, _, file, _ = await self._unload_by_name(x[1], True)
if ok:
if await self._load_by_file(file):
return f"Plugin reloaded ({time.monotonic() - t1:.1f}sec)"
return "Plugin not found"
return usage
case 'load':
if len(x) == 2:
if await self._load_by_file(x[1]):
return "Plugin loaded"
return usage
case 'unload':
if len(x) == 2:
ok, _, _, _ = await self._unload_by_name(x[1], True)
if ok:
return "Plugin unloaded"
return usage
case 'list':
return ev.call_event("_plugins_get")[0]
return usage
def _pip_install(self, x): def _pip_install(self, x):
self.log.debug(f"_pip_install {x}") self.log.debug(f"_pip_install {x}")
if len(x) > 0: if len(x) > 0:
@@ -131,83 +169,111 @@ class PluginsLoader:
else: else:
return "Invalid syntax" return "Invalid syntax"
async def _load_by_file(self, file):
file_path = os.path.join(self.plugins_dir, file)
if os.path.isfile(file_path) and file.endswith(".py"):
try:
self.log.debug(f"Loading plugin: {file[:-3]}")
plugin = types.ModuleType(file[:-3])
plugin.KuiToi = KuiToi
plugin.KuiToi._plugins_dir = self.plugins_dir
plugin.KuiToi._file = file
plugin.print = print
plugin.__file__ = file_path
with open(f'{file_path}', 'r', encoding=config.enc) as f:
code = f.read()
exec(code, plugin.__dict__)
ok = True
try:
is_func = inspect.isfunction
if not is_func(plugin.load):
self.log.error(i18n.plugins_not_found_load)
ok = False
if not is_func(plugin.start):
self.log.error(i18n.plugins_not_found_start)
ok = False
if not is_func(plugin.unload):
self.log.error(i18n.plugins_not_found_unload)
ok = False
if type(plugin.kt) != KuiToi:
self.log.error(i18n.plugins_kt_invalid)
ok = False
except AttributeError:
ok = False
if not ok:
self.log.error(i18n.plugins_invalid.format(file_path))
return
pl_name = plugin.kt.name
if self.plugins.get(pl_name) is not None:
raise NameError(f'Having plugins with identical names is not allowed; '
f'Plugin name: "{pl_name}"; Plugin file "{file_path}"')
plugin.open = plugin.kt.open
is_coro_func = inspect.iscoroutinefunction
self.plugins.update(
{
pl_name: {
"plugin": plugin,
"load": {
"func": plugin.load,
"async": is_coro_func(plugin.load)
},
"start": {
"func": plugin.start,
"async": is_coro_func(plugin.start)
},
"unload": {
"func": plugin.unload,
"async": is_coro_func(plugin.unload)
}
}
}
)
if self.plugins[pl_name]["load"]['async']:
plugin.log.debug(f"I'm async")
await plugin.load()
else:
plugin.log.debug(f"I'm sync")
th = Thread(target=plugin.load, name=f"{pl_name}.load()")
th.start()
th.join()
self.loaded.append((pl_name, True))
self.log.debug(f"Plugin loaded: {file}. Settings: {self.plugins[pl_name]}")
return True
except Exception as e:
self.loaded.append((file, False))
self.log.error(i18n.plugins_error_loading.format(file, f"{e}"))
self.log.exception(e)
return False
async def load(self): async def load(self):
self.log.debug("Loading plugins...") self.log.debug("Loading plugins...")
for file in os.listdir(self.plugins_dir): for file in os.listdir(self.plugins_dir):
file_path = os.path.join(self.plugins_dir, file) await self._load_by_file(file)
if os.path.isfile(file_path) and file.endswith(".py"):
try:
self.log.debug(f"Loading plugin: {file[:-3]}")
plugin = types.ModuleType(file[:-3])
plugin.KuiToi = KuiToi
plugin.KuiToi._plugins_dir = self.plugins_dir
plugin.print = print
plugin.__file__ = file_path
with open(f'{file_path}', 'r', encoding=config.enc) as f:
code = f.read()
exec(code, plugin.__dict__)
ok = True async def _unload_by_name(self, name, reload=False):
try: t1 = time.monotonic()
is_func = inspect.isfunction data = self.plugins.get(name)
if not is_func(plugin.load): if not data:
self.log.error(i18n.plugins_not_found_load) return False, name, None, None
ok = False try:
if not is_func(plugin.start): if reload:
self.log.error(i18n.plugins_not_found_start) data['plugin'].kt._unload()
ok = False self.loaded.remove((name, True))
if not is_func(plugin.unload): self.plugins.pop(name)
self.log.error(i18n.plugins_not_found_unload) if data['unload']['async']:
ok = False self.log.debug(f"Unload async plugin: {name}")
if type(plugin.kt) != KuiToi: await data['unload']['func']()
self.log.error(i18n.plugins_kt_invalid) else:
ok = False self.log.debug(f"Unload sync plugin: {name}")
except AttributeError: th = Thread(target=data['unload']['func'], name=f"Thread {name}")
ok = False th.start()
if not ok: th.join()
self.log.error(i18n.plugins_invalid.format(file_path)) except Exception as e:
return self.log.exception(e)
return True, name, data['plugin'].kt._file, time.monotonic() - t1
pl_name = plugin.kt.name
if self.plugins.get(pl_name) is not None:
raise NameError(f'Having plugins with identical names is not allowed; '
f'Plugin name: "{pl_name}"; Plugin file "{file_path}"')
plugin.open = plugin.kt.open
is_coro_func = inspect.iscoroutinefunction
self.plugins.update(
{
pl_name: {
"plugin": plugin,
"load": {
"func": plugin.load,
"async": is_coro_func(plugin.load)
},
"start": {
"func": plugin.start,
"async": is_coro_func(plugin.start)
},
"unload": {
"func": plugin.unload,
"async": is_coro_func(plugin.unload)
}
}
}
)
if self.plugins[pl_name]["load"]['async']:
plugin.log.debug(f"I'm async")
await plugin.load()
else:
plugin.log.debug(f"I'm sync")
th = Thread(target=plugin.load, name=f"{pl_name}.load()")
th.start()
th.join()
self.loaded_str += f"{pl_name}:ok, "
self.log.debug(f"Plugin loaded: {file}. Settings: {self.plugins[pl_name]}")
except Exception as e:
self.loaded_str += f"{file}:no, "
self.log.error(i18n.plugins_error_loading.format(file, f"{e}"))
self.log.exception(e)
async def start(self, _): async def start(self, _):
for pl_name, pl_data in self.plugins.items(): for pl_name, pl_data in self.plugins.items():
@@ -225,15 +291,9 @@ class PluginsLoader:
self.log.exception(e) self.log.exception(e)
async def unload(self, _): async def unload(self, _):
for pl_name, pl_data in self.plugins.items(): t = []
try: for n in self.plugins.keys():
if pl_data['unload']['async']: await asyncio.sleep(0.01)
self.log.debug(f"Unload async plugin: {pl_name}") t.append(self._unload_by_name(n))
await pl_data['unload']['func']() self.log.debug(await asyncio.gather(*t))
else: self.log.debug("Plugins unloaded")
self.log.debug(f"Unload sync plugin: {pl_name}")
th = Thread(target=pl_data['unload']['func'], name=f"Thread {pl_name}")
th.start()
th.join()
except Exception as e:
self.log.exception(e)