mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-04-13 17:52:28 +08:00
Merge pull request #2735 from InfinityPacer/feature/plugin
This commit is contained in:
@@ -3,7 +3,6 @@ import sys
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from dotenv import set_key
|
||||
from pydantic import BaseSettings, validator
|
||||
@@ -18,8 +17,6 @@ class Settings(BaseSettings):
|
||||
"""
|
||||
# 项目名称
|
||||
PROJECT_NAME = "MoviePilot"
|
||||
# 版本标识,用来区分重大版本,为空则为v1
|
||||
VERSION_FLAG = "v2"
|
||||
# 域名 格式;https://movie-pilot.org
|
||||
APP_DOMAIN: str = ""
|
||||
# API路径
|
||||
@@ -213,6 +210,13 @@ class Settings(BaseSettings):
|
||||
logger.warning("API_TOKEN 长度不足 16 个字符,存在安全隐患,建议尽快更换为更复杂的密钥!")
|
||||
return v
|
||||
|
||||
@property
|
||||
def VERSION_FLAG(self) -> str:
|
||||
"""
|
||||
版本标识,用来区分重大版本,为空则为v1,不允许外部修改
|
||||
"""
|
||||
return "v2"
|
||||
|
||||
@property
|
||||
def INNER_CONFIG_PATH(self):
|
||||
return self.ROOT_PATH / "config"
|
||||
|
||||
@@ -542,133 +542,59 @@ class PluginManager(metaclass=Singleton):
|
||||
"""
|
||||
获取所有在线插件信息
|
||||
"""
|
||||
|
||||
def __get_plugin_info(market: str, version: str = None) -> Optional[List[schemas.Plugin]]:
|
||||
"""
|
||||
获取插件信息
|
||||
"""
|
||||
online_plugins = self.pluginhelper.get_plugins(repo_url=market, version=version) or {}
|
||||
if not online_plugins:
|
||||
if not version:
|
||||
logger.warn(f"获取插件库失败:{market}")
|
||||
return
|
||||
ret_plugins = []
|
||||
add_time = len(online_plugins)
|
||||
for pid, plugin_info in online_plugins.items():
|
||||
# 版本兼容性控制
|
||||
if not version:
|
||||
if hasattr(settings, 'VERSION_FLAG') \
|
||||
and not plugin_info.get(settings.VERSION_FLAG):
|
||||
# 插件当前版本不兼容
|
||||
continue
|
||||
# 运行状插件
|
||||
plugin_obj = self._running_plugins.get(pid)
|
||||
# 非运行态插件
|
||||
plugin_static = self._plugins.get(pid)
|
||||
# 基本属性
|
||||
plugin = schemas.Plugin()
|
||||
# ID
|
||||
plugin.id = pid
|
||||
# 安装状态
|
||||
if pid in installed_apps and plugin_static:
|
||||
plugin.installed = True
|
||||
else:
|
||||
plugin.installed = False
|
||||
# 是否有新版本
|
||||
plugin.has_update = False
|
||||
if plugin_static:
|
||||
installed_version = getattr(plugin_static, "plugin_version")
|
||||
if StringUtils.compare_version(installed_version, plugin_info.get("version")) < 0:
|
||||
# 需要更新
|
||||
plugin.has_update = True
|
||||
# 运行状态
|
||||
if plugin_obj and hasattr(plugin_obj, "get_state"):
|
||||
try:
|
||||
state = plugin_obj.get_state()
|
||||
except Exception as e:
|
||||
logger.error(f"获取插件 {pid} 状态出错:{str(e)}")
|
||||
state = False
|
||||
plugin.state = state
|
||||
else:
|
||||
plugin.state = False
|
||||
# 是否有详情页面
|
||||
plugin.has_page = False
|
||||
if plugin_obj and hasattr(plugin_obj, "get_page"):
|
||||
if ObjectUtils.check_method(plugin_obj.get_page):
|
||||
plugin.has_page = True
|
||||
# 公钥
|
||||
if plugin_info.get("key"):
|
||||
plugin.plugin_public_key = plugin_info.get("key")
|
||||
# 权限
|
||||
if not self.__set_and_check_auth_level(plugin=plugin, source=plugin_info):
|
||||
continue
|
||||
# 名称
|
||||
if plugin_info.get("name"):
|
||||
plugin.plugin_name = plugin_info.get("name")
|
||||
# 描述
|
||||
if plugin_info.get("description"):
|
||||
plugin.plugin_desc = plugin_info.get("description")
|
||||
# 版本
|
||||
if plugin_info.get("version"):
|
||||
plugin.plugin_version = plugin_info.get("version")
|
||||
# 图标
|
||||
if plugin_info.get("icon"):
|
||||
plugin.plugin_icon = plugin_info.get("icon")
|
||||
# 标签
|
||||
if plugin_info.get("labels"):
|
||||
plugin.plugin_label = plugin_info.get("labels")
|
||||
# 作者
|
||||
if plugin_info.get("author"):
|
||||
plugin.plugin_author = plugin_info.get("author")
|
||||
# 更新历史
|
||||
if plugin_info.get("history"):
|
||||
plugin.history = plugin_info.get("history")
|
||||
# 仓库链接
|
||||
plugin.repo_url = market
|
||||
# 本地标志
|
||||
plugin.is_local = False
|
||||
# 添加顺序
|
||||
plugin.add_time = add_time
|
||||
# 汇总
|
||||
ret_plugins.append(plugin)
|
||||
add_time -= 1
|
||||
|
||||
return ret_plugins
|
||||
|
||||
if not settings.PLUGIN_MARKET:
|
||||
return []
|
||||
|
||||
# 返回值
|
||||
all_plugins = []
|
||||
# 已安装插件
|
||||
installed_apps = self.systemconfig.get(SystemConfigKey.UserInstalledPlugins) or []
|
||||
# 用于存储高于 v1 版本的插件(如 v2, v3 等)
|
||||
higher_version_plugins = []
|
||||
# 用于存储 v1 版本插件
|
||||
base_version_plugins = []
|
||||
|
||||
# 使用多线程获取线上插件
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
futures = []
|
||||
futures_to_version = {}
|
||||
for m in settings.PLUGIN_MARKET.split(","):
|
||||
if not m:
|
||||
continue
|
||||
# v1版本插件
|
||||
futures.append(executor.submit(__get_plugin_info, m, None))
|
||||
# v2+版本插件
|
||||
# 提交任务获取 v1 版本插件,存储 future 到 version 的映射
|
||||
base_future = executor.submit(self.get_plugins_from_market, m, None)
|
||||
futures_to_version[base_future] = "base_version"
|
||||
|
||||
# 提交任务获取高版本插件(如 v2、v3),存储 future 到 version 的映射
|
||||
if settings.VERSION_FLAG:
|
||||
futures.append(executor.submit(__get_plugin_info, m, settings.VERSION_FLAG))
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
higher_version_future = executor.submit(self.get_plugins_from_market, m, settings.VERSION_FLAG)
|
||||
futures_to_version[higher_version_future] = "higher_version"
|
||||
|
||||
# 按照完成顺序处理结果
|
||||
for future in concurrent.futures.as_completed(futures_to_version):
|
||||
plugins = future.result()
|
||||
version = futures_to_version[future]
|
||||
|
||||
if plugins:
|
||||
all_plugins.extend(plugins)
|
||||
if version == "higher_version":
|
||||
higher_version_plugins.extend(plugins) # 收集高版本插件
|
||||
else:
|
||||
base_version_plugins.extend(plugins) # 收集 v1 版本插件
|
||||
|
||||
# 优先处理高版本插件
|
||||
all_plugins.extend(higher_version_plugins)
|
||||
# 将未出现在高版本插件列表中的 v1 插件加入 all_plugins
|
||||
higher_plugin_ids = {f"{p.id}{p.plugin_version}" for p in higher_version_plugins}
|
||||
all_plugins.extend([p for p in base_version_plugins if f"{p.id}{p.plugin_version}" not in higher_plugin_ids])
|
||||
# 去重
|
||||
all_plugins = list({f"{p.id}{p.plugin_version}": p for p in all_plugins}.values())
|
||||
# 所有插件按repo在设置中的顺序排序
|
||||
# 所有插件按 repo 在设置中的顺序排序
|
||||
all_plugins.sort(
|
||||
key=lambda x: settings.PLUGIN_MARKET.split(",").index(x.repo_url) if x.repo_url else 0
|
||||
)
|
||||
# 相同ID的插件保留版本号最大版本
|
||||
# 相同 ID 的插件保留版本号最大的版本
|
||||
max_versions = {}
|
||||
for p in all_plugins:
|
||||
if p.id not in max_versions or StringUtils.compare_version(p.plugin_version, max_versions[p.id]) > 0:
|
||||
max_versions[p.id] = p.plugin_version
|
||||
result = [p for p in all_plugins if
|
||||
p.plugin_version == max_versions[p.id]]
|
||||
result = [p for p in all_plugins if p.plugin_version == max_versions[p.id]]
|
||||
logger.info(f"共获取到 {len(result)} 个线上插件")
|
||||
return result
|
||||
|
||||
@@ -764,6 +690,105 @@ class PluginManager(metaclass=Singleton):
|
||||
logger.debug(f"获取插件是否在本地包中存在失败,{e}")
|
||||
return False
|
||||
|
||||
def get_plugins_from_market(self, market: str, package_version: str = None) -> Optional[List[schemas.Plugin]]:
|
||||
"""
|
||||
从指定的市场获取插件信息
|
||||
:param market: 市场的 URL 或标识
|
||||
:param package_version: 首选插件版本 (如 "v2", "v3"),如果不指定则获取 v1 版本
|
||||
:return: 返回插件的列表,若获取失败返回 []
|
||||
"""
|
||||
if not market:
|
||||
return []
|
||||
# 已安装插件
|
||||
installed_apps = self.systemconfig.get(SystemConfigKey.UserInstalledPlugins) or []
|
||||
# 获取在线插件
|
||||
online_plugins = self.pluginhelper.get_plugins(repo_url=market, package_version=package_version) or {}
|
||||
if not online_plugins:
|
||||
if not package_version:
|
||||
logger.warning(f"获取插件库失败:{market},请检查 GitHub 网络连接")
|
||||
return []
|
||||
ret_plugins = []
|
||||
add_time = len(online_plugins)
|
||||
for pid, plugin_info in online_plugins.items():
|
||||
# 如 package_version 为空,则需要判断插件是否兼容当前版本
|
||||
if not package_version:
|
||||
if plugin_info.get(settings.VERSION_FLAG) is not True:
|
||||
# 插件当前版本不兼容
|
||||
continue
|
||||
# 运行状插件
|
||||
plugin_obj = self._running_plugins.get(pid)
|
||||
# 非运行态插件
|
||||
plugin_static = self._plugins.get(pid)
|
||||
# 基本属性
|
||||
plugin = schemas.Plugin()
|
||||
# ID
|
||||
plugin.id = pid
|
||||
# 安装状态
|
||||
if pid in installed_apps and plugin_static:
|
||||
plugin.installed = True
|
||||
else:
|
||||
plugin.installed = False
|
||||
# 是否有新版本
|
||||
plugin.has_update = False
|
||||
if plugin_static:
|
||||
installed_version = getattr(plugin_static, "plugin_version")
|
||||
if StringUtils.compare_version(installed_version, plugin_info.get("version")) < 0:
|
||||
# 需要更新
|
||||
plugin.has_update = True
|
||||
# 运行状态
|
||||
if plugin_obj and hasattr(plugin_obj, "get_state"):
|
||||
try:
|
||||
state = plugin_obj.get_state()
|
||||
except Exception as e:
|
||||
logger.error(f"获取插件 {pid} 状态出错:{str(e)}")
|
||||
state = False
|
||||
plugin.state = state
|
||||
else:
|
||||
plugin.state = False
|
||||
# 是否有详情页面
|
||||
plugin.has_page = False
|
||||
if plugin_obj and hasattr(plugin_obj, "get_page"):
|
||||
if ObjectUtils.check_method(plugin_obj.get_page):
|
||||
plugin.has_page = True
|
||||
# 公钥
|
||||
if plugin_info.get("key"):
|
||||
plugin.plugin_public_key = plugin_info.get("key")
|
||||
# 权限
|
||||
if not self.__set_and_check_auth_level(plugin=plugin, source=plugin_info):
|
||||
continue
|
||||
# 名称
|
||||
if plugin_info.get("name"):
|
||||
plugin.plugin_name = plugin_info.get("name")
|
||||
# 描述
|
||||
if plugin_info.get("description"):
|
||||
plugin.plugin_desc = plugin_info.get("description")
|
||||
# 版本
|
||||
if plugin_info.get("version"):
|
||||
plugin.plugin_version = plugin_info.get("version")
|
||||
# 图标
|
||||
if plugin_info.get("icon"):
|
||||
plugin.plugin_icon = plugin_info.get("icon")
|
||||
# 标签
|
||||
if plugin_info.get("labels"):
|
||||
plugin.plugin_label = plugin_info.get("labels")
|
||||
# 作者
|
||||
if plugin_info.get("author"):
|
||||
plugin.plugin_author = plugin_info.get("author")
|
||||
# 更新历史
|
||||
if plugin_info.get("history"):
|
||||
plugin.history = plugin_info.get("history")
|
||||
# 仓库链接
|
||||
plugin.repo_url = market
|
||||
# 本地标志
|
||||
plugin.is_local = False
|
||||
# 添加顺序
|
||||
plugin.add_time = add_time
|
||||
# 汇总
|
||||
ret_plugins.append(plugin)
|
||||
add_time -= 1
|
||||
|
||||
return ret_plugins
|
||||
|
||||
def __set_and_check_auth_level(self, plugin: Union[schemas.Plugin, Type[Any]],
|
||||
source: Optional[Union[dict, Type[Any]]] = None) -> bool:
|
||||
"""
|
||||
|
||||
@@ -34,11 +34,11 @@ class PluginHelper(metaclass=Singleton):
|
||||
self.systemconfig.set(SystemConfigKey.PluginInstallReport, "1")
|
||||
|
||||
@cached(cache=TTLCache(maxsize=1000, ttl=1800))
|
||||
def get_plugins(self, repo_url: str, version: str = None) -> Dict[str, dict]:
|
||||
def get_plugins(self, repo_url: str, package_version: str = None) -> Dict[str, dict]:
|
||||
"""
|
||||
获取Github所有最新插件列表
|
||||
:param repo_url: Github仓库地址
|
||||
:param version: 版本
|
||||
:param package_version: 首选插件版本 (如 "v2", "v3"),如果不指定则获取 v1 版本
|
||||
"""
|
||||
if not repo_url:
|
||||
return {}
|
||||
@@ -48,7 +48,7 @@ class PluginHelper(metaclass=Singleton):
|
||||
return {}
|
||||
|
||||
raw_url = self._base_url.format(user=user, repo=repo)
|
||||
package_url = f"{raw_url}package.{version}.json" if version else f"{raw_url}package.json"
|
||||
package_url = f"{raw_url}package.{package_version}.json" if package_version else f"{raw_url}package.json"
|
||||
|
||||
res = self.__request_with_fallback(package_url, headers=settings.REPO_GITHUB_HEADERS(repo=f"{user}/{repo}"))
|
||||
if res:
|
||||
@@ -58,6 +58,38 @@ class PluginHelper(metaclass=Singleton):
|
||||
logger.error(f"插件包数据解析失败:{res.text}")
|
||||
return {}
|
||||
|
||||
def get_plugin_package_version(self, pid: str, repo_url: str, package_version: str = None) -> Optional[str]:
|
||||
"""
|
||||
检查并获取指定插件的可用版本,支持多版本优先级加载和版本兼容性检测
|
||||
1. 如果未指定版本,则使用系统配置的默认版本(通过 settings.VERSION_FLAG 设置)
|
||||
2. 优先检查指定版本的插件(如 `package.v2.json`)
|
||||
3. 如果插件不存在于指定版本,检查 `package.json` 文件,查看该插件是否兼容指定版本
|
||||
4. 如果插件不存在或不兼容指定版本,返回 `None`
|
||||
:param pid: 插件 ID,用于在插件列表中查找
|
||||
:param repo_url: 插件仓库的 URL,指定用于获取插件信息的 GitHub 仓库地址
|
||||
:param package_version: 首选插件版本 (如 "v2", "v3"),如不指定则默认使用系统配置的版本
|
||||
:return: 返回可用的插件版本号 (如 "v2",如果指定版本不可用则返回空字符串表示 v1),如果插件不可用则返回 None
|
||||
"""
|
||||
# 如果没有指定版本,则使用当前系统配置的版本(如 "v2")
|
||||
if not package_version:
|
||||
package_version = settings.VERSION_FLAG
|
||||
|
||||
# 优先检查指定版本的插件,即 package.v(x).json 文件中是否存在该插件,如果存在,返回该版本号
|
||||
plugins = self.get_plugins(repo_url, package_version)
|
||||
if pid in plugins:
|
||||
return package_version
|
||||
|
||||
# 如果指定版本的插件不存在,检查全局 package.json 文件,查看插件是否兼容指定的版本
|
||||
global_plugins = self.get_plugins(repo_url)
|
||||
plugin = global_plugins.get(pid, None)
|
||||
|
||||
# 检查插件是否明确支持当前指定的版本(如 v2 或 v3),如果支持,返回空字符串表示使用 package.json(v1)
|
||||
if plugin and plugin.get(package_version) is True:
|
||||
return ""
|
||||
|
||||
# 如果所有版本都不存在或插件不兼容,返回 None,表示插件不可用
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_repo_info(repo_url: str) -> Tuple[Optional[str], Optional[str]]:
|
||||
"""
|
||||
@@ -116,16 +148,18 @@ class PluginHelper(metaclass=Singleton):
|
||||
json={"plugins": [{"plugin_id": plugin} for plugin in plugins]})
|
||||
return True if res else False
|
||||
|
||||
def install(self, pid: str, repo_url: str) -> Tuple[bool, str]:
|
||||
def install(self, pid: str, repo_url: str, package_version: str = None) -> Tuple[bool, str]:
|
||||
"""
|
||||
安装插件,包括依赖安装和文件下载,相关资源支持自动降级策略
|
||||
1. 从 GitHub 获取文件列表(包括 requirements.txt)
|
||||
2. 删除旧的插件目录
|
||||
3. 下载并预安装 requirements.txt 中的依赖(如果存在)
|
||||
4. 下载并安装插件的其他文件
|
||||
5. 再次尝试安装依赖(确保安装完整)
|
||||
1. 检查并获取插件的指定版本,确认版本兼容性。
|
||||
2. 从 GitHub 获取文件列表(包括 requirements.txt)
|
||||
3. 删除旧的插件目录
|
||||
4. 下载并预安装 requirements.txt 中的依赖(如果存在)
|
||||
5. 下载并安装插件的其他文件
|
||||
6. 再次尝试安装依赖(确保安装完整)
|
||||
:param pid: 插件 ID
|
||||
:param repo_url: 插件仓库地址
|
||||
:param package_version: 首选插件版本 (如 "v2", "v3"),如不指定则默认使用系统配置的版本
|
||||
:return: (是否成功, 错误信息)
|
||||
"""
|
||||
if SystemUtils.is_frozen():
|
||||
@@ -142,15 +176,31 @@ class PluginHelper(metaclass=Singleton):
|
||||
|
||||
user_repo = f"{user}/{repo}"
|
||||
|
||||
# 1. 获取插件文件列表(包括 requirements.txt)
|
||||
file_list, msg = self.__get_file_list(pid.lower(), user_repo)
|
||||
if not package_version:
|
||||
package_version = settings.VERSION_FLAG
|
||||
|
||||
# 1. 优先检查指定版本的插件
|
||||
package_version = self.get_plugin_package_version(pid, repo_url, package_version)
|
||||
# 如果 package_version 为None,说明没有找到匹配的插件
|
||||
if package_version is None:
|
||||
msg = f"{pid} 没有找到适用于当前版本的插件"
|
||||
logger.debug(msg)
|
||||
return False, msg
|
||||
# package_version 为空,表示从 package.json 中找到插件
|
||||
elif package_version == "":
|
||||
logger.debug(f"{pid} 从 package.json 中找到适用于当前版本的插件")
|
||||
else:
|
||||
logger.debug(f"{pid} 从 package.{package_version}.json 中找到适用于当前版本的插件")
|
||||
|
||||
# 2. 获取插件文件列表(包括 requirements.txt)
|
||||
file_list, msg = self.__get_file_list(pid.lower(), user_repo, package_version)
|
||||
if not file_list:
|
||||
return False, msg
|
||||
|
||||
# 2. 删除旧的插件目录
|
||||
# 3. 删除旧的插件目录
|
||||
self.__remove_old_plugin(pid.lower())
|
||||
|
||||
# 3. 查找并安装 requirements.txt 中的依赖,确保插件环境的依赖尽可能完整。依赖安装可能失败且不影响插件安装,目前只记录日志
|
||||
# 4. 查找并安装 requirements.txt 中的依赖,确保插件环境的依赖尽可能完整。依赖安装可能失败且不影响插件安装,目前只记录日志
|
||||
requirements_file_info = next((f for f in file_list if f.get("name") == "requirements.txt"), None)
|
||||
if requirements_file_info:
|
||||
logger.debug(f"{pid} 发现 requirements.txt,提前下载并预安装依赖")
|
||||
@@ -161,34 +211,41 @@ class PluginHelper(metaclass=Singleton):
|
||||
else:
|
||||
logger.debug(f"{pid} 依赖预安装成功")
|
||||
|
||||
# 4. 下载插件的其他文件
|
||||
# 5. 下载插件的其他文件
|
||||
logger.info(f"{pid} 准备开始下载插件文件")
|
||||
success, message = self.__download_files(pid.lower(), file_list, user_repo, True)
|
||||
success, message = self.__download_files(pid.lower(), file_list, user_repo, package_version, True)
|
||||
if not success:
|
||||
logger.error(f"{pid} 下载插件文件失败:{message}")
|
||||
return False, message
|
||||
else:
|
||||
logger.info(f"{pid} 下载插件文件成功")
|
||||
|
||||
# 5. 插件文件安装成功后,再次尝试安装依赖,避免因为遗漏依赖导致的插件运行问题,目前依旧只记录日志
|
||||
success, message = self.__install_dependencies_if_required(pid)
|
||||
if not success:
|
||||
logger.error(f"{pid} 依赖安装失败:{message}")
|
||||
else:
|
||||
logger.info(f"{pid} 依赖安装成功")
|
||||
# 6. 插件文件安装成功后,再次尝试安装依赖,避免因为遗漏依赖导致的插件运行问题,目前依旧只记录日志
|
||||
dependencies_exist, success, message = self.__install_dependencies_if_required(pid)
|
||||
if dependencies_exist:
|
||||
if not success:
|
||||
logger.error(f"{pid} 依赖安装失败:{message}")
|
||||
else:
|
||||
logger.info(f"{pid} 依赖安装成功")
|
||||
|
||||
# 插件安装成功后,统计安装信息
|
||||
self.install_reg(pid)
|
||||
return True, ""
|
||||
|
||||
def __get_file_list(self, pid: str, user_repo: str) -> Tuple[Optional[list], Optional[str]]:
|
||||
def __get_file_list(self, pid: str, user_repo: str, package_version: str = None) -> \
|
||||
Tuple[Optional[list], Optional[str]]:
|
||||
"""
|
||||
获取插件的文件列表
|
||||
:param pid: 插件 ID
|
||||
:param user_repo: GitHub 仓库的 user/repo 路径
|
||||
:return: (文件列表, 错误信息)
|
||||
"""
|
||||
file_api = f"https://api.github.com/repos/{user_repo}/contents/plugins/{pid}"
|
||||
file_api = f"https://api.github.com/repos/{user_repo}/contents/plugins"
|
||||
# 如果 package_version 存在(如 "v2"),则加上版本号
|
||||
if package_version:
|
||||
file_api += f".{package_version}"
|
||||
file_api += f"/{pid}"
|
||||
|
||||
res = self.__request_with_fallback(file_api,
|
||||
headers=settings.REPO_GITHUB_HEADERS(repo=user_repo),
|
||||
is_api=True,
|
||||
@@ -209,8 +266,8 @@ class PluginHelper(metaclass=Singleton):
|
||||
logger.error(f"插件数据解析失败:{res.text},{e}")
|
||||
return None, "插件数据解析失败"
|
||||
|
||||
def __download_files(self, pid: str, file_list: List[dict], user_repo: str, skip_requirements: bool = False) \
|
||||
-> Tuple[bool, str]:
|
||||
def __download_files(self, pid: str, file_list: List[dict], user_repo: str,
|
||||
package_version: str = None, skip_requirements: bool = False) -> Tuple[bool, str]:
|
||||
"""
|
||||
下载插件文件
|
||||
:param pid: 插件 ID
|
||||
@@ -242,15 +299,21 @@ class PluginHelper(metaclass=Singleton):
|
||||
elif res.status_code != 200:
|
||||
return False, f"下载文件 {item.get('path')} 失败:{res.status_code}"
|
||||
|
||||
# 确保文件路径不包含版本号(如 v2、v3),如果有 package_version,移除路径中的版本号
|
||||
relative_path = item.get("path")
|
||||
if package_version:
|
||||
relative_path = relative_path.replace(f"plugins.{package_version}", "plugins", 1)
|
||||
|
||||
# 创建插件文件夹并写入文件
|
||||
file_path = Path(settings.ROOT_PATH) / "app" / item.get("path")
|
||||
file_path = Path(settings.ROOT_PATH) / "app" / relative_path
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.write(res.text)
|
||||
logger.debug(f"文件 {item.get('path')} 下载成功,保存路径:{file_path}")
|
||||
else:
|
||||
# 如果是子目录,则将子目录内容加入栈中继续处理
|
||||
sub_list, msg = self.__get_file_list(f"{current_pid}/{item.get('name')}", user_repo)
|
||||
sub_list, msg = self.__get_file_list(f"{current_pid}/{item.get('name')}", user_repo,
|
||||
package_version)
|
||||
if not sub_list:
|
||||
return False, msg
|
||||
stack.append((f"{current_pid}/{item.get('name')}", sub_list))
|
||||
@@ -287,18 +350,26 @@ class PluginHelper(metaclass=Singleton):
|
||||
|
||||
return True, "" # 如果 requirements.txt 为空,视作成功
|
||||
|
||||
def __install_dependencies_if_required(self, pid: str) -> Tuple[bool, str]:
|
||||
def __install_dependencies_if_required(self, pid: str) -> Tuple[bool, bool, str]:
|
||||
"""
|
||||
安装插件依赖
|
||||
安装插件依赖。
|
||||
:param pid: 插件 ID
|
||||
:return: (是否成功, 错误信息)
|
||||
:return: (是否存在依赖,安装是否成功, 错误信息)
|
||||
"""
|
||||
# 定位插件目录和依赖文件
|
||||
plugin_dir = Path(settings.ROOT_PATH) / "app" / "plugins" / pid.lower()
|
||||
requirements_file = plugin_dir / "requirements.txt"
|
||||
|
||||
# 检查是否存在 requirements.txt 文件
|
||||
if requirements_file.exists():
|
||||
logger.info(f"{pid} 存在依赖项,开始尝试安装依赖")
|
||||
return self.__pip_install_with_fallback(requirements_file)
|
||||
return True, ""
|
||||
logger.info(f"{pid} 存在依赖,开始尝试安装依赖")
|
||||
success, error_message = self.__pip_install_with_fallback(requirements_file)
|
||||
if success:
|
||||
return True, True, ""
|
||||
else:
|
||||
return True, False, error_message
|
||||
|
||||
return False, False, "不存在依赖"
|
||||
|
||||
@staticmethod
|
||||
def __remove_old_plugin(pid: str):
|
||||
|
||||
Reference in New Issue
Block a user