You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

240 line
9.8KB

  1. # encoding:utf-8
  2. import importlib
  3. import json
  4. import os
  5. from common.singleton import singleton
  6. from common.sorted_dict import SortedDict
  7. from .event import *
  8. from common.log import logger
  9. from config import conf
  10. @singleton
  11. class PluginManager:
  12. def __init__(self):
  13. self.plugins = SortedDict(lambda k,v: v.priority,reverse=True)
  14. self.listening_plugins = {}
  15. self.instances = {}
  16. self.pconf = {}
  17. self.current_plugin_path = None
  18. def register(self, name: str, desire_priority: int = 0, **kwargs):
  19. def wrapper(plugincls):
  20. plugincls.name = name
  21. plugincls.priority = desire_priority
  22. plugincls.desc = kwargs.get('desc')
  23. plugincls.author = kwargs.get('author')
  24. plugincls.path = self.current_plugin_path
  25. plugincls.version = kwargs.get('version') if kwargs.get('version') != None else "1.0"
  26. plugincls.namecn = kwargs.get('namecn') if kwargs.get('namecn') != None else name
  27. plugincls.hidden = kwargs.get('hidden') if kwargs.get('hidden') != None else False
  28. plugincls.enabled = True
  29. self.plugins[name.upper()] = plugincls
  30. logger.info("Plugin %s_v%s registered, path=%s" % (name, plugincls.version, plugincls.path))
  31. return plugincls
  32. return wrapper
  33. def save_config(self):
  34. with open("./plugins/plugins.json", "w", encoding="utf-8") as f:
  35. json.dump(self.pconf, f, indent=4, ensure_ascii=False)
  36. def load_config(self):
  37. logger.info("Loading plugins config...")
  38. modified = False
  39. if os.path.exists("./plugins/plugins.json"):
  40. with open("./plugins/plugins.json", "r", encoding="utf-8") as f:
  41. pconf = json.load(f)
  42. pconf['plugins'] = SortedDict(lambda k,v: v["priority"],pconf['plugins'],reverse=True)
  43. else:
  44. modified = True
  45. pconf = {"plugins": SortedDict(lambda k,v: v["priority"],reverse=True)}
  46. self.pconf = pconf
  47. if modified:
  48. self.save_config()
  49. return pconf
  50. def scan_plugins(self):
  51. logger.info("Scaning plugins ...")
  52. plugins_dir = "./plugins"
  53. for plugin_name in os.listdir(plugins_dir):
  54. plugin_path = os.path.join(plugins_dir, plugin_name)
  55. if os.path.isdir(plugin_path):
  56. # 判断插件是否包含同名__init__.py文件
  57. main_module_path = os.path.join(plugin_path,"__init__.py")
  58. if os.path.isfile(main_module_path):
  59. # 导入插件
  60. import_path = "plugins.{}".format(plugin_name)
  61. try:
  62. self.current_plugin_path = plugin_path
  63. main_module = importlib.import_module(import_path)
  64. except Exception as e:
  65. logger.warn("Failed to import plugin %s: %s" % (plugin_name, e))
  66. continue
  67. pconf = self.pconf
  68. new_plugins = []
  69. modified = False
  70. for name, plugincls in self.plugins.items():
  71. rawname = plugincls.name
  72. if rawname not in pconf["plugins"]:
  73. new_plugins.append(plugincls)
  74. modified = True
  75. logger.info("Plugin %s not found in pconfig, adding to pconfig..." % name)
  76. pconf["plugins"][rawname] = {"enabled": plugincls.enabled, "priority": plugincls.priority}
  77. else:
  78. self.plugins[name].enabled = pconf["plugins"][rawname]["enabled"]
  79. self.plugins[name].priority = pconf["plugins"][rawname]["priority"]
  80. self.plugins._update_heap(name) # 更新下plugins中的顺序
  81. if modified:
  82. self.save_config()
  83. return new_plugins
  84. def refresh_order(self):
  85. for event in self.listening_plugins.keys():
  86. self.listening_plugins[event].sort(key=lambda name: self.plugins[name].priority, reverse=True)
  87. def activate_plugins(self): # 生成新开启的插件实例
  88. for name, plugincls in self.plugins.items():
  89. if plugincls.enabled:
  90. if name not in self.instances:
  91. try:
  92. instance = plugincls()
  93. except Exception as e:
  94. logger.warn("Failed to create init %s, diabled. %s" % (name, e))
  95. self.disable_plugin(name)
  96. continue
  97. self.instances[name] = instance
  98. for event in instance.handlers:
  99. if event not in self.listening_plugins:
  100. self.listening_plugins[event] = []
  101. self.listening_plugins[event].append(name)
  102. self.refresh_order()
  103. def reload_plugin(self, name:str):
  104. name = name.upper()
  105. if name in self.instances:
  106. for event in self.listening_plugins:
  107. if name in self.listening_plugins[event]:
  108. self.listening_plugins[event].remove(name)
  109. del self.instances[name]
  110. self.activate_plugins()
  111. return True
  112. return False
  113. def load_plugins(self):
  114. self.load_config()
  115. self.scan_plugins()
  116. pconf = self.pconf
  117. logger.debug("plugins.json config={}".format(pconf))
  118. for name,plugin in pconf["plugins"].items():
  119. if name.upper() not in self.plugins:
  120. logger.error("Plugin %s not found, but found in plugins.json" % name)
  121. self.activate_plugins()
  122. def emit_event(self, e_context: EventContext, *args, **kwargs):
  123. if e_context.event in self.listening_plugins:
  124. for name in self.listening_plugins[e_context.event]:
  125. if self.plugins[name].enabled and e_context.action == EventAction.CONTINUE:
  126. logger.debug("Plugin %s triggered by event %s" % (name,e_context.event))
  127. instance = self.instances[name]
  128. instance.handlers[e_context.event](e_context, *args, **kwargs)
  129. return e_context
  130. def set_plugin_priority(self, name:str, priority:int):
  131. name = name.upper()
  132. if name not in self.plugins:
  133. return False
  134. if self.plugins[name].priority == priority:
  135. return True
  136. self.plugins[name].priority = priority
  137. self.plugins._update_heap(name)
  138. rawname = self.plugins[name].name
  139. self.pconf["plugins"][rawname]["priority"] = priority
  140. self.pconf["plugins"]._update_heap(rawname)
  141. self.save_config()
  142. self.refresh_order()
  143. return True
  144. def enable_plugin(self, name:str):
  145. name = name.upper()
  146. if name not in self.plugins:
  147. return False
  148. if not self.plugins[name].enabled :
  149. self.plugins[name].enabled = True
  150. rawname = self.plugins[name].name
  151. self.pconf["plugins"][rawname]["enabled"] = True
  152. self.save_config()
  153. self.activate_plugins()
  154. return True
  155. return True
  156. def disable_plugin(self, name:str):
  157. name = name.upper()
  158. if name not in self.plugins:
  159. return False
  160. if self.plugins[name].enabled :
  161. self.plugins[name].enabled = False
  162. rawname = self.plugins[name].name
  163. self.pconf["plugins"][rawname]["enabled"] = False
  164. self.save_config()
  165. return True
  166. return True
  167. def list_plugins(self):
  168. return self.plugins
  169. def install_plugin(self, repo:str):
  170. try:
  171. import common.package_manager as pkgmgr
  172. pkgmgr.check_dulwich()
  173. except Exception as e:
  174. logger.error("Failed to install plugin, {}".format(e))
  175. return False, "无法导入dulwich,安装插件失败"
  176. import re
  177. from dulwich import porcelain
  178. logger.info("clone git repo: {}".format(repo))
  179. match = re.match(r"^(https?:\/\/|git@)([^\/:]+)[\/:]([^\/:]+)\/(.+).git$", repo)
  180. if not match:
  181. try:
  182. with open("./plugins/source.json","r") as f:
  183. source = json.load(f)
  184. if repo in source["repo"]:
  185. repo = source["repo"][repo]["url"]
  186. match = re.match(r"^(https?:\/\/|git@)([^\/:]+)[\/:]([^\/:]+)\/(.+).git$", repo)
  187. if not match:
  188. return False, "source中的仓库地址不合法"
  189. else:
  190. return False, "仓库地址不合法"
  191. except Exception as e:
  192. logger.error("Failed to install plugin, {}".format(e))
  193. return False, "安装插件失败"
  194. dirname = os.path.join("./plugins",match.group(4))
  195. try:
  196. repo = porcelain.clone(repo, dirname, checkout=True)
  197. if os.path.exists(os.path.join(dirname,"requirements.txt")):
  198. logger.info("detect requirements.txt,installing...")
  199. pkgmgr.install_requirements(os.path.join(dirname,"requirements.txt"))
  200. return True, "安装插件成功,请扫描插件或重启程序"
  201. except Exception as e:
  202. logger.error("Failed to install plugin, {}".format(e))
  203. return False, "安装插件失败"
  204. def uninstall_plugin(self, name:str):
  205. name = name.upper()
  206. if name not in self.plugins:
  207. return False, "插件不存在"
  208. if name in self.instances:
  209. self.disable_plugin(name)
  210. dirname = self.plugins[name].path
  211. try:
  212. import shutil
  213. shutil.rmtree(dirname)
  214. del self.plugins[name]
  215. return True, "卸载插件成功"
  216. except Exception as e:
  217. logger.error("Failed to uninstall plugin, {}".format(e))
  218. return False, "卸载插件失败"