Merge pull request #2073 from Raven95676/fix/register_star
fix: 提升兼容性,并尽可能避免数据竞争
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
from .star import StarMetadata, star_map
|
from .star import StarMetadata, star_map, star_registry
|
||||||
from .star_manager import PluginManager
|
from .star_manager import PluginManager
|
||||||
from .context import Context
|
from .context import Context
|
||||||
from astrbot.core.provider import Provider
|
from astrbot.core.provider import Provider
|
||||||
@@ -16,11 +16,16 @@ class Star(CommandParserMixin):
|
|||||||
|
|
||||||
def __init_subclass__(cls, **kwargs):
|
def __init_subclass__(cls, **kwargs):
|
||||||
super().__init_subclass__(**kwargs)
|
super().__init_subclass__(**kwargs)
|
||||||
metadata = StarMetadata(
|
if not star_map.get(cls.__module__):
|
||||||
star_cls_type=cls,
|
metadata = StarMetadata(
|
||||||
module_path=cls.__module__,
|
star_cls_type=cls,
|
||||||
)
|
module_path=cls.__module__,
|
||||||
star_map[cls.__module__] = metadata
|
)
|
||||||
|
star_map[cls.__module__] = metadata
|
||||||
|
star_registry.append(metadata)
|
||||||
|
else:
|
||||||
|
star_map[cls.__module__].star_cls_type = cls
|
||||||
|
star_map[cls.__module__].module_path = cls.__module__
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def text_to_image(text: str, return_url=True) -> str:
|
async def text_to_image(text: str, return_url=True) -> str:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
from astrbot.core.star import StarMetadata, star_map
|
||||||
|
|
||||||
_warned_register_star = False
|
_warned_register_star = False
|
||||||
|
|
||||||
|
|
||||||
@@ -37,6 +39,22 @@ def register_star(name: str, author: str, desc: str, version: str, repo: str = N
|
|||||||
)
|
)
|
||||||
|
|
||||||
def decorator(cls):
|
def decorator(cls):
|
||||||
|
if not star_map.get(cls.__module__):
|
||||||
|
metadata = StarMetadata(
|
||||||
|
name=name,
|
||||||
|
author=author,
|
||||||
|
desc=desc,
|
||||||
|
version=version,
|
||||||
|
repo=repo,
|
||||||
|
)
|
||||||
|
star_map[cls.__module__] = metadata
|
||||||
|
else:
|
||||||
|
star_map[cls.__module__].name = name
|
||||||
|
star_map[cls.__module__].author = author
|
||||||
|
star_map[cls.__module__].desc = desc
|
||||||
|
star_map[cls.__module__].version = version
|
||||||
|
star_map[cls.__module__].repo = repo
|
||||||
|
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|||||||
@@ -56,7 +56,10 @@ class StarMetadata:
|
|||||||
"""插件支持的平台ID字典,key为平台ID,value为是否支持"""
|
"""插件支持的平台ID字典,key为平台ID,value为是否支持"""
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"StarMetadata({self.name}, {self.desc}, {self.version}, {self.repo})"
|
return f"Plugin {self.name} ({self.version}) by {self.author}: {self.desc}"
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"Plugin {self.name} ({self.version}) by {self.author}: {self.desc}"
|
||||||
|
|
||||||
def update_platform_compatibility(self, plugin_enable_config: dict) -> None:
|
def update_platform_compatibility(self, plugin_enable_config: dict) -> None:
|
||||||
"""更新插件支持的平台列表
|
"""更新插件支持的平台列表
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ class PluginManager:
|
|||||||
"""保留插件的路径。在 packages 目录下"""
|
"""保留插件的路径。在 packages 目录下"""
|
||||||
self.conf_schema_fname = "_conf_schema.json"
|
self.conf_schema_fname = "_conf_schema.json"
|
||||||
"""插件配置 Schema 文件名"""
|
"""插件配置 Schema 文件名"""
|
||||||
|
self._pm_lock = asyncio.Lock()
|
||||||
|
"""StarManager操作互斥锁"""
|
||||||
|
|
||||||
self.failed_plugin_info = ""
|
self.failed_plugin_info = ""
|
||||||
if os.getenv("ASTRBOT_RELOAD", "0") == "1":
|
if os.getenv("ASTRBOT_RELOAD", "0") == "1":
|
||||||
@@ -186,9 +188,9 @@ class PluginManager:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _load_plugin_metadata(plugin_path: str, plugin_obj=None) -> StarMetadata:
|
def _load_plugin_metadata(plugin_path: str, plugin_obj=None) -> StarMetadata:
|
||||||
"""v3.4.0 以前的方式载入插件元数据
|
"""先寻找 metadata.yaml 文件,如果不存在,则使用插件对象的 info() 函数获取元数据。
|
||||||
|
|
||||||
先寻找 metadata.yaml 文件,如果不存在,则使用插件对象的 info() 函数获取元数据。
|
Notes: 旧版本 AstrBot 插件可能使用的是 info() 函数来获取元数据。
|
||||||
"""
|
"""
|
||||||
metadata = None
|
metadata = None
|
||||||
|
|
||||||
@@ -200,7 +202,7 @@ class PluginManager:
|
|||||||
os.path.join(plugin_path, "metadata.yaml"), "r", encoding="utf-8"
|
os.path.join(plugin_path, "metadata.yaml"), "r", encoding="utf-8"
|
||||||
) as f:
|
) as f:
|
||||||
metadata = yaml.safe_load(f)
|
metadata = yaml.safe_load(f)
|
||||||
elif plugin_obj:
|
elif plugin_obj and hasattr(plugin_obj, "info"):
|
||||||
# 使用 info() 函数
|
# 使用 info() 函数
|
||||||
metadata = plugin_obj.info()
|
metadata = plugin_obj.info()
|
||||||
|
|
||||||
@@ -293,50 +295,51 @@ class PluginManager:
|
|||||||
- success (bool): 重载是否成功
|
- success (bool): 重载是否成功
|
||||||
- error_message (str|None): 错误信息,成功时为 None
|
- error_message (str|None): 错误信息,成功时为 None
|
||||||
"""
|
"""
|
||||||
specified_module_path = None
|
async with self._pm_lock:
|
||||||
if specified_plugin_name:
|
specified_module_path = None
|
||||||
for smd in star_registry:
|
if specified_plugin_name:
|
||||||
if smd.name == specified_plugin_name:
|
for smd in star_registry:
|
||||||
specified_module_path = smd.module_path
|
if smd.name == specified_plugin_name:
|
||||||
break
|
specified_module_path = smd.module_path
|
||||||
|
break
|
||||||
|
|
||||||
# 终止插件
|
# 终止插件
|
||||||
if not specified_module_path:
|
if not specified_module_path:
|
||||||
# 重载所有插件
|
# 重载所有插件
|
||||||
for smd in star_registry:
|
for smd in star_registry:
|
||||||
try:
|
try:
|
||||||
await self._terminate_plugin(smd)
|
await self._terminate_plugin(smd)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(traceback.format_exc())
|
logger.warning(traceback.format_exc())
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"插件 {smd.name} 未被正常终止: {str(e)}, 可能会导致该插件运行不正常。"
|
f"插件 {smd.name} 未被正常终止: {str(e)}, 可能会导致该插件运行不正常。"
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._unbind_plugin(smd.name, smd.module_path)
|
await self._unbind_plugin(smd.name, smd.module_path)
|
||||||
|
|
||||||
star_handlers_registry.clear()
|
star_handlers_registry.clear()
|
||||||
star_map.clear()
|
star_map.clear()
|
||||||
star_registry.clear()
|
star_registry.clear()
|
||||||
else:
|
else:
|
||||||
# 只重载指定插件
|
# 只重载指定插件
|
||||||
smd = star_map.get(specified_module_path)
|
smd = star_map.get(specified_module_path)
|
||||||
if smd:
|
if smd:
|
||||||
try:
|
try:
|
||||||
await self._terminate_plugin(smd)
|
await self._terminate_plugin(smd)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(traceback.format_exc())
|
logger.warning(traceback.format_exc())
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"插件 {smd.name} 未被正常终止: {str(e)}, 可能会导致该插件运行不正常。"
|
f"插件 {smd.name} 未被正常终止: {str(e)}, 可能会导致该插件运行不正常。"
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._unbind_plugin(smd.name, specified_module_path)
|
await self._unbind_plugin(smd.name, specified_module_path)
|
||||||
|
|
||||||
result = await self.load(specified_module_path)
|
result = await self.load(specified_module_path)
|
||||||
|
|
||||||
# 更新所有插件的平台兼容性
|
# 更新所有插件的平台兼容性
|
||||||
await self.update_all_platform_compatibility()
|
await self.update_all_platform_compatibility()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def update_all_platform_compatibility(self):
|
async def update_all_platform_compatibility(self):
|
||||||
"""更新所有插件的平台兼容性设置"""
|
"""更新所有插件的平台兼容性设置"""
|
||||||
@@ -435,7 +438,7 @@ class PluginManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if path in star_map:
|
if path in star_map:
|
||||||
# 通过__init__subclass__注册插件
|
# 通过 __init__subclass__ 注册插件
|
||||||
metadata = star_map[path]
|
metadata = star_map[path]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -450,9 +453,10 @@ class PluginManager:
|
|||||||
metadata.version = metadata_yaml.version
|
metadata.version = metadata_yaml.version
|
||||||
metadata.repo = metadata_yaml.repo
|
metadata.repo = metadata_yaml.repo
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.warning(
|
||||||
f"插件 {root_dir_name} 元数据载入失败: {str(e)}。使用默认元数据。"
|
f"插件 {root_dir_name} 元数据载入失败: {str(e)}。使用默认元数据。"
|
||||||
)
|
)
|
||||||
|
logger.info(metadata)
|
||||||
metadata.config = plugin_config
|
metadata.config = plugin_config
|
||||||
if path not in inactivated_plugins:
|
if path not in inactivated_plugins:
|
||||||
# 只有没有禁用插件时才实例化插件类
|
# 只有没有禁用插件时才实例化插件类
|
||||||
@@ -506,8 +510,6 @@ class PluginManager:
|
|||||||
if func_tool.name in inactivated_llm_tools:
|
if func_tool.name in inactivated_llm_tools:
|
||||||
func_tool.active = False
|
func_tool.active = False
|
||||||
|
|
||||||
star_registry.append(metadata)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# v3.4.0 以前的方式注册插件
|
# v3.4.0 以前的方式注册插件
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@@ -626,42 +628,45 @@ class PluginManager:
|
|||||||
- readme: README.md 文件的内容(如果存在)
|
- readme: README.md 文件的内容(如果存在)
|
||||||
如果找不到插件元数据则返回 None。
|
如果找不到插件元数据则返回 None。
|
||||||
"""
|
"""
|
||||||
plugin_path = await self.updator.install(repo_url, proxy)
|
async with self._pm_lock:
|
||||||
# reload the plugin
|
plugin_path = await self.updator.install(repo_url, proxy)
|
||||||
dir_name = os.path.basename(plugin_path)
|
# reload the plugin
|
||||||
await self.load(specified_dir_name=dir_name)
|
dir_name = os.path.basename(plugin_path)
|
||||||
|
await self.load(specified_dir_name=dir_name)
|
||||||
|
|
||||||
# Get the plugin metadata to return repo info
|
# Get the plugin metadata to return repo info
|
||||||
plugin = self.context.get_registered_star(dir_name)
|
plugin = self.context.get_registered_star(dir_name)
|
||||||
if not plugin:
|
if not plugin:
|
||||||
# Try to find by other name if directory name doesn't match plugin name
|
# Try to find by other name if directory name doesn't match plugin name
|
||||||
for star in self.context.get_all_stars():
|
for star in self.context.get_all_stars():
|
||||||
if star.root_dir_name == dir_name:
|
if star.root_dir_name == dir_name:
|
||||||
plugin = star
|
plugin = star
|
||||||
break
|
break
|
||||||
|
|
||||||
# Extract README.md content if exists
|
# Extract README.md content if exists
|
||||||
readme_content = None
|
readme_content = None
|
||||||
readme_path = os.path.join(plugin_path, "README.md")
|
readme_path = os.path.join(plugin_path, "README.md")
|
||||||
if not os.path.exists(readme_path):
|
if not os.path.exists(readme_path):
|
||||||
readme_path = os.path.join(plugin_path, "readme.md")
|
readme_path = os.path.join(plugin_path, "readme.md")
|
||||||
|
|
||||||
if os.path.exists(readme_path):
|
if os.path.exists(readme_path):
|
||||||
try:
|
try:
|
||||||
with open(readme_path, "r", encoding="utf-8") as f:
|
with open(readme_path, "r", encoding="utf-8") as f:
|
||||||
readme_content = f.read()
|
readme_content = f.read()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"读取插件 {dir_name} 的 README.md 文件失败: {str(e)}")
|
logger.warning(
|
||||||
|
f"读取插件 {dir_name} 的 README.md 文件失败: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
plugin_info = None
|
plugin_info = None
|
||||||
if plugin:
|
if plugin:
|
||||||
plugin_info = {
|
plugin_info = {
|
||||||
"repo": plugin.repo,
|
"repo": plugin.repo,
|
||||||
"readme": readme_content,
|
"readme": readme_content,
|
||||||
"name": plugin.name,
|
"name": plugin.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
return plugin_info
|
return plugin_info
|
||||||
|
|
||||||
async def uninstall_plugin(self, plugin_name: str):
|
async def uninstall_plugin(self, plugin_name: str):
|
||||||
"""卸载指定的插件。
|
"""卸载指定的插件。
|
||||||
@@ -672,32 +677,33 @@ class PluginManager:
|
|||||||
Raises:
|
Raises:
|
||||||
Exception: 当插件不存在、是保留插件时,或删除插件文件夹失败时抛出异常
|
Exception: 当插件不存在、是保留插件时,或删除插件文件夹失败时抛出异常
|
||||||
"""
|
"""
|
||||||
plugin = self.context.get_registered_star(plugin_name)
|
async with self._pm_lock:
|
||||||
if not plugin:
|
plugin = self.context.get_registered_star(plugin_name)
|
||||||
raise Exception("插件不存在。")
|
if not plugin:
|
||||||
if plugin.reserved:
|
raise Exception("插件不存在。")
|
||||||
raise Exception("该插件是 AstrBot 保留插件,无法卸载。")
|
if plugin.reserved:
|
||||||
root_dir_name = plugin.root_dir_name
|
raise Exception("该插件是 AstrBot 保留插件,无法卸载。")
|
||||||
ppath = self.plugin_store_path
|
root_dir_name = plugin.root_dir_name
|
||||||
|
ppath = self.plugin_store_path
|
||||||
|
|
||||||
# 终止插件
|
# 终止插件
|
||||||
try:
|
try:
|
||||||
await self._terminate_plugin(plugin)
|
await self._terminate_plugin(plugin)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(traceback.format_exc())
|
logger.warning(traceback.format_exc())
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"插件 {plugin_name} 未被正常终止 {str(e)}, 可能会导致资源泄露等问题。"
|
f"插件 {plugin_name} 未被正常终止 {str(e)}, 可能会导致资源泄露等问题。"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 从 star_registry 和 star_map 中删除
|
# 从 star_registry 和 star_map 中删除
|
||||||
await self._unbind_plugin(plugin_name, plugin.module_path)
|
await self._unbind_plugin(plugin_name, plugin.module_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
remove_dir(os.path.join(ppath, root_dir_name))
|
remove_dir(os.path.join(ppath, root_dir_name))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"移除插件成功,但是删除插件文件夹失败: {str(e)}。您可以手动删除该文件夹,位于 addons/plugins/ 下。"
|
f"移除插件成功,但是删除插件文件夹失败: {str(e)}。您可以手动删除该文件夹,位于 addons/plugins/ 下。"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _unbind_plugin(self, plugin_name: str, plugin_module_path: str):
|
async def _unbind_plugin(self, plugin_name: str, plugin_module_path: str):
|
||||||
"""解绑并移除一个插件。
|
"""解绑并移除一个插件。
|
||||||
@@ -750,33 +756,34 @@ class PluginManager:
|
|||||||
将插件的 module_path 加入到 data/shared_preferences.json 的 inactivated_plugins 列表中。
|
将插件的 module_path 加入到 data/shared_preferences.json 的 inactivated_plugins 列表中。
|
||||||
并且同时将插件启用的 llm_tool 禁用。
|
并且同时将插件启用的 llm_tool 禁用。
|
||||||
"""
|
"""
|
||||||
plugin = self.context.get_registered_star(plugin_name)
|
async with self._pm_lock:
|
||||||
if not plugin:
|
plugin = self.context.get_registered_star(plugin_name)
|
||||||
raise Exception("插件不存在。")
|
if not plugin:
|
||||||
|
raise Exception("插件不存在。")
|
||||||
|
|
||||||
# 调用插件的终止方法
|
# 调用插件的终止方法
|
||||||
await self._terminate_plugin(plugin)
|
await self._terminate_plugin(plugin)
|
||||||
|
|
||||||
# 加入到 shared_preferences 中
|
# 加入到 shared_preferences 中
|
||||||
inactivated_plugins: list = sp.get("inactivated_plugins", [])
|
inactivated_plugins: list = sp.get("inactivated_plugins", [])
|
||||||
if plugin.module_path not in inactivated_plugins:
|
if plugin.module_path not in inactivated_plugins:
|
||||||
inactivated_plugins.append(plugin.module_path)
|
inactivated_plugins.append(plugin.module_path)
|
||||||
|
|
||||||
inactivated_llm_tools: list = list(
|
inactivated_llm_tools: list = list(
|
||||||
set(sp.get("inactivated_llm_tools", []))
|
set(sp.get("inactivated_llm_tools", []))
|
||||||
) # 后向兼容
|
) # 后向兼容
|
||||||
|
|
||||||
# 禁用插件启用的 llm_tool
|
# 禁用插件启用的 llm_tool
|
||||||
for func_tool in llm_tools.func_list:
|
for func_tool in llm_tools.func_list:
|
||||||
if func_tool.handler_module_path == plugin.module_path:
|
if func_tool.handler_module_path == plugin.module_path:
|
||||||
func_tool.active = False
|
func_tool.active = False
|
||||||
if func_tool.name not in inactivated_llm_tools:
|
if func_tool.name not in inactivated_llm_tools:
|
||||||
inactivated_llm_tools.append(func_tool.name)
|
inactivated_llm_tools.append(func_tool.name)
|
||||||
|
|
||||||
sp.put("inactivated_plugins", inactivated_plugins)
|
sp.put("inactivated_plugins", inactivated_plugins)
|
||||||
sp.put("inactivated_llm_tools", inactivated_llm_tools)
|
sp.put("inactivated_llm_tools", inactivated_llm_tools)
|
||||||
|
|
||||||
plugin.activated = False
|
plugin.activated = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _terminate_plugin(star_metadata: StarMetadata):
|
async def _terminate_plugin(star_metadata: StarMetadata):
|
||||||
|
|||||||
Reference in New Issue
Block a user