mirror of
https://github.com/kuitoi/kuitoi-Server.git
synced 2026-04-24 00:56:36 +00:00
[+] plugins command
This commit is contained in:
@@ -96,6 +96,21 @@ class EventsSystem:
|
||||
self.log.debug("used builtins_hook")
|
||||
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):
|
||||
return (event_name in self.__async_events.keys() or
|
||||
event_name in self.__events.keys() or
|
||||
@@ -117,15 +132,13 @@ class EventsSystem:
|
||||
return
|
||||
if async_event or inspect.iscoroutinefunction(event_func):
|
||||
if event_name not in self.__async_events:
|
||||
self.__async_events.update({str(event_name): [event_func]})
|
||||
else:
|
||||
self.__async_events[event_name].append(event_func)
|
||||
self.__async_events[event_name] = []
|
||||
self.__async_events[event_name].append(event_func)
|
||||
self.log.debug("Register ok")
|
||||
else:
|
||||
if event_name not in self.__events:
|
||||
self.__events.update({str(event_name): [event_func]})
|
||||
else:
|
||||
self.__events[event_name].append(event_func)
|
||||
self.__events[event_name] = []
|
||||
self.__events[event_name].append(event_func)
|
||||
self.log.debug("Register ok")
|
||||
|
||||
async def call_async_event(self, event_name, *args, **kwargs):
|
||||
|
||||
@@ -11,6 +11,7 @@ import inspect
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import types
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
@@ -21,6 +22,7 @@ from core import get_logger
|
||||
|
||||
class KuiToi:
|
||||
_plugins_dir = ""
|
||||
_file = ""
|
||||
|
||||
def __init__(self, name):
|
||||
if not name:
|
||||
@@ -29,6 +31,8 @@ class KuiToi:
|
||||
self.__name = name
|
||||
self.__dir = Path(self._plugins_dir) / self.__name
|
||||
os.makedirs(self.__dir, exist_ok=True)
|
||||
self.__events_funcs = []
|
||||
self.register_event = self.register
|
||||
|
||||
@property
|
||||
def log(self):
|
||||
@@ -63,8 +67,13 @@ class KuiToi:
|
||||
|
||||
def register(self, event_name, event_func):
|
||||
self.log.debug(f"Registering event {event_name}")
|
||||
self.__events_funcs.append(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):
|
||||
self.log.debug(f"Called event {event_name}")
|
||||
return ev.call_event(event_name, *args, **kwargs)
|
||||
@@ -109,16 +118,45 @@ class PluginsLoader:
|
||||
self.plugins_tasks = []
|
||||
self.plugins_dir = plugins_dir
|
||||
self.log = get_logger("PluginsLoader")
|
||||
self.loaded_str = "Plugins: "
|
||||
self.loaded = []
|
||||
ev.register("_plugins_start", self.start)
|
||||
ev.register("_plugins_unload", self.unload)
|
||||
ev.register("_plugins_get", lambda x: list(self.plugins.keys()))
|
||||
console.add_command("plugins", lambda x: self.loaded_str[:-2])
|
||||
console.add_command("pl", lambda x: self.loaded_str[:-2])
|
||||
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])
|
||||
sys.path.append(self._pip_dir)
|
||||
os.makedirs(self._pip_dir, exist_ok=True)
|
||||
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):
|
||||
self.log.debug(f"_pip_install {x}")
|
||||
if len(x) > 0:
|
||||
@@ -131,83 +169,111 @@ class PluginsLoader:
|
||||
else:
|
||||
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):
|
||||
self.log.debug("Loading plugins...")
|
||||
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"):
|
||||
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__)
|
||||
await self._load_by_file(file)
|
||||
|
||||
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_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 _unload_by_name(self, name, reload=False):
|
||||
t1 = time.monotonic()
|
||||
data = self.plugins.get(name)
|
||||
if not data:
|
||||
return False, name, None, None
|
||||
try:
|
||||
if reload:
|
||||
data['plugin'].kt._unload()
|
||||
self.loaded.remove((name, True))
|
||||
self.plugins.pop(name)
|
||||
if data['unload']['async']:
|
||||
self.log.debug(f"Unload async plugin: {name}")
|
||||
await data['unload']['func']()
|
||||
else:
|
||||
self.log.debug(f"Unload sync plugin: {name}")
|
||||
th = Thread(target=data['unload']['func'], name=f"Thread {name}")
|
||||
th.start()
|
||||
th.join()
|
||||
except Exception as e:
|
||||
self.log.exception(e)
|
||||
return True, name, data['plugin'].kt._file, time.monotonic() - t1
|
||||
|
||||
async def start(self, _):
|
||||
for pl_name, pl_data in self.plugins.items():
|
||||
@@ -225,15 +291,9 @@ class PluginsLoader:
|
||||
self.log.exception(e)
|
||||
|
||||
async def unload(self, _):
|
||||
for pl_name, pl_data in self.plugins.items():
|
||||
try:
|
||||
if pl_data['unload']['async']:
|
||||
self.log.debug(f"Unload async plugin: {pl_name}")
|
||||
await pl_data['unload']['func']()
|
||||
else:
|
||||
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)
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user