mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
feat(plugin): add force install option and backup/restore on failure
This commit is contained in:
+1
-1
@@ -280,7 +280,7 @@ class PluginManager(metaclass=Singleton):
|
|||||||
|
|
||||||
def install_plugin(plugin):
|
def install_plugin(plugin):
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
state, msg = self.pluginhelper.install(pid=plugin.id, repo_url=plugin.repo_url)
|
state, msg = self.pluginhelper.install(pid=plugin.id, repo_url=plugin.repo_url, force_install=True)
|
||||||
elapsed_time = time.time() - start_time
|
elapsed_time = time.time() - start_time
|
||||||
if state:
|
if state:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
+61
-7
@@ -148,18 +148,20 @@ class PluginHelper(metaclass=Singleton):
|
|||||||
json={"plugins": [{"plugin_id": plugin} for plugin in plugins]})
|
json={"plugins": [{"plugin_id": plugin} for plugin in plugins]})
|
||||||
return True if res else False
|
return True if res else False
|
||||||
|
|
||||||
def install(self, pid: str, repo_url: str, package_version: str = None) -> Tuple[bool, str]:
|
def install(self, pid: str, repo_url: str, package_version: str = None, force_install: bool = False) \
|
||||||
|
-> Tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
安装插件,包括依赖安装和文件下载,相关资源支持自动降级策略
|
安装插件,包括依赖安装和文件下载,相关资源支持自动降级策略
|
||||||
1. 检查并获取插件的指定版本,确认版本兼容性
|
1. 检查并获取插件的指定版本,确认版本兼容性
|
||||||
2. 从 GitHub 获取文件列表(包括 requirements.txt)
|
2. 从 GitHub 获取文件列表(包括 requirements.txt)
|
||||||
3. 删除旧的插件目录
|
3. 删除旧的插件目录(如非强制安装则进行备份)
|
||||||
4. 下载并预安装 requirements.txt 中的依赖(如果存在)
|
4. 下载并预安装 requirements.txt 中的依赖(如果存在)
|
||||||
5. 下载并安装插件的其他文件
|
5. 下载并安装插件的其他文件
|
||||||
6. 再次尝试安装依赖(确保安装完整)
|
6. 再次尝试安装依赖(确保安装完整)
|
||||||
:param pid: 插件 ID
|
:param pid: 插件 ID
|
||||||
:param repo_url: 插件仓库地址
|
:param repo_url: 插件仓库地址
|
||||||
:param package_version: 首选插件版本 (如 "v2", "v3"),如不指定则默认使用系统配置的版本
|
:param package_version: 首选插件版本 (如 "v2", "v3"),如不指定则默认使用系统配置的版本
|
||||||
|
:param force_install: 是否强制安装插件,默认不启用,启用时不进行备份和恢复操作
|
||||||
:return: (是否成功, 错误信息)
|
:return: (是否成功, 错误信息)
|
||||||
"""
|
"""
|
||||||
if SystemUtils.is_frozen():
|
if SystemUtils.is_frozen():
|
||||||
@@ -197,7 +199,11 @@ class PluginHelper(metaclass=Singleton):
|
|||||||
if not file_list:
|
if not file_list:
|
||||||
return False, msg
|
return False, msg
|
||||||
|
|
||||||
# 3. 删除旧的插件目录
|
# 3. 删除旧的插件目录,如果不强制安装则备份
|
||||||
|
backup_dir = None
|
||||||
|
if not force_install:
|
||||||
|
backup_dir = self.__backup_plugin(pid.lower())
|
||||||
|
|
||||||
self.__remove_old_plugin(pid.lower())
|
self.__remove_old_plugin(pid.lower())
|
||||||
|
|
||||||
# 4. 查找并安装 requirements.txt 中的依赖,确保插件环境的依赖尽可能完整。依赖安装可能失败且不影响插件安装,目前只记录日志
|
# 4. 查找并安装 requirements.txt 中的依赖,确保插件环境的依赖尽可能完整。依赖安装可能失败且不影响插件安装,目前只记录日志
|
||||||
@@ -215,9 +221,14 @@ class PluginHelper(metaclass=Singleton):
|
|||||||
logger.info(f"{pid} 准备开始下载插件文件")
|
logger.info(f"{pid} 准备开始下载插件文件")
|
||||||
success, message = self.__download_files(pid.lower(), file_list, user_repo, package_version, True)
|
success, message = self.__download_files(pid.lower(), file_list, user_repo, package_version, True)
|
||||||
if not success:
|
if not success:
|
||||||
self.__remove_old_plugin(pid.lower())
|
|
||||||
logger.error(f"{pid} 下载插件文件失败:{message}")
|
logger.error(f"{pid} 下载插件文件失败:{message}")
|
||||||
logger.warning(f"{pid} 已清理对应插件目录,请尝试重新安装")
|
if backup_dir:
|
||||||
|
self.__restore_plugin(pid.lower(), backup_dir)
|
||||||
|
logger.warning(f"{pid} 插件安装失败,已还原备份插件")
|
||||||
|
else:
|
||||||
|
self.__remove_old_plugin(pid.lower())
|
||||||
|
logger.warning(f"{pid} 已清理对应插件目录,请尝试重新安装")
|
||||||
|
|
||||||
return False, message
|
return False, message
|
||||||
else:
|
else:
|
||||||
logger.info(f"{pid} 下载插件文件成功")
|
logger.info(f"{pid} 下载插件文件成功")
|
||||||
@@ -226,9 +237,13 @@ class PluginHelper(metaclass=Singleton):
|
|||||||
dependencies_exist, success, message = self.__install_dependencies_if_required(pid)
|
dependencies_exist, success, message = self.__install_dependencies_if_required(pid)
|
||||||
if dependencies_exist:
|
if dependencies_exist:
|
||||||
if not success:
|
if not success:
|
||||||
self.__remove_old_plugin(pid.lower())
|
|
||||||
logger.error(f"{pid} 依赖安装失败:{message}")
|
logger.error(f"{pid} 依赖安装失败:{message}")
|
||||||
logger.warning(f"{pid} 已清理对应插件目录,请尝试重新安装")
|
if backup_dir:
|
||||||
|
self.__restore_plugin(pid.lower(), backup_dir)
|
||||||
|
logger.warning(f"{pid} 插件安装失败,已还原备份插件")
|
||||||
|
else:
|
||||||
|
self.__remove_old_plugin(pid.lower())
|
||||||
|
logger.warning(f"{pid} 已清理对应插件目录,请尝试重新安装")
|
||||||
else:
|
else:
|
||||||
logger.info(f"{pid} 依赖安装成功")
|
logger.info(f"{pid} 依赖安装成功")
|
||||||
|
|
||||||
@@ -375,6 +390,45 @@ class PluginHelper(metaclass=Singleton):
|
|||||||
|
|
||||||
return False, False, "不存在依赖"
|
return False, False, "不存在依赖"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __backup_plugin(pid: str) -> str:
|
||||||
|
"""
|
||||||
|
备份旧插件目录
|
||||||
|
:param pid: 插件 ID
|
||||||
|
:return: 备份目录路径
|
||||||
|
"""
|
||||||
|
plugin_dir = Path(settings.ROOT_PATH) / "app" / "plugins" / pid
|
||||||
|
backup_dir = Path(settings.TEMP_PATH) / "plugins_backup" / pid
|
||||||
|
|
||||||
|
if plugin_dir.exists():
|
||||||
|
# 备份时清理已有的备份目录,防止残留文件影响
|
||||||
|
if backup_dir.exists():
|
||||||
|
shutil.rmtree(backup_dir, ignore_errors=True)
|
||||||
|
logger.debug(f"{pid} 旧的备份目录已清理 {backup_dir}")
|
||||||
|
|
||||||
|
shutil.copytree(plugin_dir, backup_dir, dirs_exist_ok=True)
|
||||||
|
logger.debug(f"{pid} 插件已备份到 {backup_dir}")
|
||||||
|
|
||||||
|
return str(backup_dir) if backup_dir.exists() else None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __restore_plugin(pid: str, backup_dir: str):
|
||||||
|
"""
|
||||||
|
还原旧插件目录
|
||||||
|
:param pid: 插件 ID
|
||||||
|
:param backup_dir: 备份目录路径
|
||||||
|
"""
|
||||||
|
plugin_dir = Path(settings.ROOT_PATH) / "app" / "plugins" / pid
|
||||||
|
if plugin_dir.exists():
|
||||||
|
shutil.rmtree(plugin_dir, ignore_errors=True)
|
||||||
|
logger.debug(f"{pid} 已清理插件目录 {plugin_dir}")
|
||||||
|
|
||||||
|
if Path(backup_dir).exists():
|
||||||
|
shutil.copytree(backup_dir, plugin_dir, dirs_exist_ok=True)
|
||||||
|
logger.debug(f"{pid} 已还原插件目录 {plugin_dir}")
|
||||||
|
shutil.rmtree(backup_dir, ignore_errors=True)
|
||||||
|
logger.debug(f"{pid} 已删除备份目录 {backup_dir}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __remove_old_plugin(pid: str):
|
def __remove_old_plugin(pid: str):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user