diff --git a/common/sorted_dict.py b/common/sorted_dict.py new file mode 100644 index 0000000..a918a0c --- /dev/null +++ b/common/sorted_dict.py @@ -0,0 +1,65 @@ +import heapq + + +class SortedDict(dict): + def __init__(self, sort_func=lambda k, v: k, init_dict=None, reverse=False): + if init_dict is None: + init_dict = [] + if isinstance(init_dict, dict): + init_dict = init_dict.items() + self.sort_func = sort_func + self.sorted_keys = None + self.reverse = reverse + self.heap = [] + for k, v in init_dict: + self[k] = v + + def __setitem__(self, key, value): + if key in self: + super().__setitem__(key, value) + for i, (priority, k) in enumerate(self.heap): + if k == key: + self.heap[i] = (self.sort_func(key, value), key) + heapq.heapify(self.heap) + break + self.sorted_keys = None + else: + super().__setitem__(key, value) + heapq.heappush(self.heap, (self.sort_func(key, value), key)) + self.sorted_keys = None + + def __delitem__(self, key): + super().__delitem__(key) + for i, (priority, k) in enumerate(self.heap): + if k == key: + del self.heap[i] + heapq.heapify(self.heap) + break + self.sorted_keys = None + + def keys(self): + if self.sorted_keys is None: + self.sorted_keys = [k for _, k in sorted(self.heap, reverse=self.reverse)] + return self.sorted_keys + + def items(self): + if self.sorted_keys is None: + self.sorted_keys = [k for _, k in sorted(self.heap, reverse=self.reverse)] + sorted_items = [(k, self[k]) for k in self.sorted_keys] + return sorted_items + + def _update_heap(self, key): + for i, (priority, k) in enumerate(self.heap): + if k == key: + new_priority = self.sort_func(key, self[key]) + if new_priority != priority: + self.heap[i] = (new_priority, key) + heapq.heapify(self.heap) + self.sorted_keys = None + break + + def __iter__(self): + return iter(self.keys()) + + def __repr__(self): + return f'{type(self).__name__}({dict(self)}, sort_func={self.sort_func.__name__}, reverse={self.reverse})' diff --git a/plugins/godcmd/godcmd.py b/plugins/godcmd/godcmd.py index 2f57d32..3bd24dd 100644 --- a/plugins/godcmd/godcmd.py +++ b/plugins/godcmd/godcmd.py @@ -56,6 +56,11 @@ ADMIN_COMMANDS = { "alias": ["plist", "插件"], "desc": "打印当前插件列表", }, + "setpri": { + "alias": ["setpri", "设置插件优先级"], + "args": ["插件名", "优先级"], + "desc": "设置指定插件的优先级,越大越优先", + }, "enablep": { "alias": ["enablep", "启用插件"], "args": ["插件名"], @@ -92,7 +97,7 @@ def get_help_text(isadmin, isgroup): help_text += f": {info['desc']}\n" return help_text -@plugins.register(name="Godcmd", desc="为你的机器人添加指令集,有用户和管理员两种角色,加载顺序请放在首位,初次运行后插件目录会生成配置文件, 填充管理员密码后即可认证", version="1.0", author="lanvent") +@plugins.register(name="Godcmd", desc="为你的机器人添加指令集,有用户和管理员两种角色,加载顺序请放在首位,初次运行后插件目录会生成配置文件, 填充管理员密码后即可认证", version="1.0", author="lanvent", desire_priority= 999) class Godcmd(Plugin): def __init__(self): @@ -186,7 +191,7 @@ class Godcmd(Plugin): ok = True result = "插件列表:\n" for name,plugincls in plugins.items(): - result += f"{name}_v{plugincls.version} - " + result += f"{name}_v{plugincls.version} {plugincls.priority} - " if plugincls.enabled: result += "已启用\n" else: @@ -194,12 +199,21 @@ class Godcmd(Plugin): elif cmd == "scanp": new_plugins = PluginManager().scan_plugins() ok, result = True, "插件扫描完成" + PluginManager().activate_plugins() if len(new_plugins) >0 : - PluginManager().activate_plugins() result += "\n发现新插件:\n" result += "\n".join([f"{p.name}_v{p.version}" for p in new_plugins]) else : result +=", 未发现新插件" + elif cmd == "setpri": + if len(args) != 2: + ok, result = False, "请提供插件名和优先级" + else: + ok = PluginManager().set_plugin_priority(args[0], int(args[1])) + if ok: + result = "插件" + args[0] + "优先级已设置为" + args[1] + else: + result = "插件不存在" elif cmd == "enablep": if len(args) != 1: ok, result = False, "请提供插件名" diff --git a/plugins/hello/hello.py b/plugins/hello/hello.py index ca1d257..1eb409e 100644 --- a/plugins/hello/hello.py +++ b/plugins/hello/hello.py @@ -5,7 +5,7 @@ from plugins import * from common.log import logger -@plugins.register(name="Hello", desc="A simple plugin that says hello", version="0.1", author="lanvent") +@plugins.register(name="Hello", desc="A simple plugin that says hello", version="0.1", author="lanvent", desire_priority= -1) class Hello(Plugin): def __init__(self): super().__init__() diff --git a/plugins/plugin_manager.py b/plugins/plugin_manager.py index 630041a..107c63b 100644 --- a/plugins/plugin_manager.py +++ b/plugins/plugin_manager.py @@ -4,6 +4,7 @@ import importlib import json import os from common.singleton import singleton +from common.sorted_dict import SortedDict from .event import * from .plugin import * from common.log import logger @@ -12,19 +13,20 @@ from common.log import logger @singleton class PluginManager: def __init__(self): - self.plugins = {} + self.plugins = SortedDict(lambda k,v: v.priority,reverse=True) self.listening_plugins = {} self.instances = {} self.pconf = {} - def register(self, name: str, desc: str, version: str, author: str): + def register(self, name: str, desc: str, version: str, author: str, desire_priority: int = 0): def wrapper(plugincls): - self.plugins[name] = plugincls plugincls.name = name plugincls.desc = desc plugincls.version = version plugincls.author = author + plugincls.priority = desire_priority plugincls.enabled = True + self.plugins[name] = plugincls logger.info("Plugin %s_v%s registered" % (name, version)) return plugincls return wrapper @@ -40,9 +42,10 @@ class PluginManager: if os.path.exists("plugins/plugins.json"): with open("plugins/plugins.json", "r", encoding="utf-8") as f: pconf = json.load(f) + pconf['plugins'] = SortedDict(lambda k,v: v["priority"],pconf['plugins'],reverse=True) else: modified = True - pconf = {"plugins": []} + pconf = {"plugins": SortedDict(lambda k,v: v["priority"],reverse=True)} self.pconf = pconf if modified: self.save_config() @@ -64,16 +67,24 @@ class PluginManager: new_plugins = [] modified = False for name, plugincls in self.plugins.items(): - if name not in [plugin["name"] for plugin in pconf["plugins"]]: + if name not in pconf["plugins"]: new_plugins.append(plugincls) modified = True logger.info("Plugin %s not found in pconfig, adding to pconfig..." % name) - pconf["plugins"].append({"name": name, "enabled": True}) + pconf["plugins"][name] = {"enabled": plugincls.enabled, "priority": plugincls.priority} + else: + self.plugins[name].enabled = pconf["plugins"][name]["enabled"] + self.plugins[name].priority = pconf["plugins"][name]["priority"] + self.plugins._update_heap(name) # 更新下plugins中的顺序 if modified: self.save_config() return new_plugins - def activate_plugins(self): + def refresh_order(self): + for event in self.listening_plugins.keys(): + self.listening_plugins[event].sort(key=lambda name: self.plugins[name].priority, reverse=True) + + def activate_plugins(self): # 生成新开启的插件实例 for name, plugincls in self.plugins.items(): if plugincls.enabled: if name not in self.instances: @@ -83,16 +94,16 @@ class PluginManager: if event not in self.listening_plugins: self.listening_plugins[event] = [] self.listening_plugins[event].append(name) + self.refresh_order() def load_plugins(self): self.load_config() self.scan_plugins() pconf = self.pconf logger.debug("plugins.json config={}".format(pconf)) - for plugin in pconf["plugins"]: - name = plugin["name"] - enabled = plugin["enabled"] - self.plugins[name].enabled = enabled + for name,plugin in pconf["plugins"].items(): + if name not in self.plugins: + logger.error("Plugin %s not found, but found in plugins.json" % name) self.activate_plugins() def emit_event(self, e_context: EventContext, *args, **kwargs): @@ -104,13 +115,25 @@ class PluginManager: instance.handlers[e_context.event](e_context, *args, **kwargs) return e_context + def set_plugin_priority(self,name,priority): + if name not in self.plugins: + return False + if self.plugins[name].priority == priority: + return True + self.plugins[name].priority = priority + self.plugins._update_heap(name) + self.pconf["plugins"][name]["priority"] = priority + self.pconf["plugins"]._update_heap(name) + self.save_config() + self.refresh_order() + return True + def enable_plugin(self,name): if name not in self.plugins: return False if not self.plugins[name].enabled : self.plugins[name].enabled = True - idx = next(i for i in range(len(self.pconf['plugins'])) if self.pconf["plugins"][i]['name'] == name) - self.pconf["plugins"][idx]["enabled"] = True + self.pconf["plugins"][name]["enabled"] = True self.save_config() self.activate_plugins() return True @@ -121,8 +144,7 @@ class PluginManager: return False if self.plugins[name].enabled : self.plugins[name].enabled = False - idx = next(i for i in range(len(self.pconf['plugins'])) if self.pconf["plugins"][i]['name'] == name) - self.pconf["plugins"][idx]["enabled"] = False + self.pconf["plugins"][name]["enabled"] = False self.save_config() return True return True