فهرست منبع

plugin: support priority to decide trigger order

lanvent 3 سال پیش
والد
کامیت
1dc3f85a66
4فایلهای تغییر یافته به همراه120 افزوده شده و 19 حذف شده
  1. 65 0
      common/sorted_dict.py
  2. 17 3
      plugins/godcmd/godcmd.py
  3. 1 1
      plugins/hello/hello.py
  4. 37 15
      plugins/plugin_manager.py

+ 65 - 0
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})'

+ 17 - 3
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, "请提供插件名"

+ 1 - 1
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__()

+ 37 - 15
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