From db5f5c53e7a7360d557c0d7f0db90dfff5b684a6 Mon Sep 17 00:00:00 2001 From: thsrite Date: Wed, 17 Apr 2024 09:24:05 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E6=8F=92=E4=BB=B6=E5=BD=BB=E5=BA=95?= =?UTF-8?q?=E5=8D=B8=E8=BD=BD=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + icons/uninstall.png | Bin 0 -> 5702 bytes package.json | 11 ++ plugins/pluginuninstall/__init__.py | 181 ++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 icons/uninstall.png create mode 100644 plugins/pluginuninstall/__init__.py diff --git a/README.md b/README.md index e996b9e..c5657e7 100644 --- a/README.md +++ b/README.md @@ -31,4 +31,5 @@ MoviePilot三方插件市场:https://github.com/thsrite/MoviePilot-Plugins/ - 订阅下载统计 1.5 - [自定义命令 1.5](docs%2FCustomCommand.md) - docker自定义任务 1.2 +- 插件彻底卸载 1.0 diff --git a/icons/uninstall.png b/icons/uninstall.png new file mode 100644 index 0000000000000000000000000000000000000000..5f368212f96394b4cd6e686aac64b44d545e0ae7 GIT binary patch literal 5702 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index c125b0e..0402f89 100644 --- a/package.json +++ b/package.json @@ -290,5 +290,16 @@ "v1.1": "修复多个任务立即运行一次", "v1.0": "init" } + }, + "PluginUnInstall": { + "name": "插件彻底卸载", + "description": "删除数据库中已安装插件记录、清理插件文件。", + "version": "1.0", + "icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/uninstall.png", + "author": "thsrite", + "level": 1, + "history": { + "v1.0": "init" + } } } diff --git a/plugins/pluginuninstall/__init__.py b/plugins/pluginuninstall/__init__.py new file mode 100644 index 0000000..d047957 --- /dev/null +++ b/plugins/pluginuninstall/__init__.py @@ -0,0 +1,181 @@ +import shutil +from pathlib import Path + +from app.core.config import settings +from app.db.systemconfig_oper import SystemConfigOper +from app.helper.plugin import PluginHelper +from app.plugins import _PluginBase +from typing import Any, List, Dict, Tuple +from app.log import logger +from app.schemas.types import SystemConfigKey +from app.utils.string import StringUtils + + +class PluginUnInstall(_PluginBase): + # 插件名称 + plugin_name = "插件彻底卸载" + # 插件描述 + plugin_desc = "删除数据库中已安装插件记录、清理插件文件。" + # 插件图标 + plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/uninstall.png" + # 插件版本 + plugin_version = "1.0" + # 插件作者 + plugin_author = "thsrite" + # 作者主页 + author_url = "https://github.com/thsrite" + # 插件配置项ID前缀 + plugin_config_prefix = "pluginuninstall_" + # 加载顺序 + plugin_order = 98 + # 可使用的用户级别 + auth_level = 1 + + # 私有属性 + _plugin_ids = [] + + def init_plugin(self, config: dict = None): + if config: + self._plugin_ids = config.get("plugin_ids") or [] + if not self._plugin_ids: + return + + # 已安装插件 + install_plugins = SystemConfigOper().get(SystemConfigKey.UserInstalledPlugins) or [] + + new_install_plugins = [] + for install_plugin in install_plugins: + if install_plugin in self._plugin_ids: + # 删除插件文件 + plugin_dir = Path(settings.ROOT_PATH) / "app" / "plugins" / install_plugin.lower() + if plugin_dir.exists(): + shutil.rmtree(plugin_dir, ignore_errors=True) + logger.info(f"插件 {install_plugin} 已卸载") + else: + new_install_plugins.append(install_plugin) + + # 保存已安装插件 + SystemConfigOper().set(SystemConfigKey.UserInstalledPlugins, new_install_plugins) + + def get_state(self) -> bool: + return False + + @staticmethod + def get_command() -> List[Dict[str, Any]]: + pass + + def get_api(self) -> List[Dict[str, Any]]: + pass + + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: + """ + 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 + """ + # 已安装插件 + local_plugins = self.get_local_plugins() + # 编历 local_plugins,生成插件类型选项 + pluginOptions = [] + + for plugin_id in list(local_plugins.keys()): + local_plugin = local_plugins.get(plugin_id) + pluginOptions.append({ + "title": f"{local_plugin.get('plugin_name')} v{local_plugin.get('plugin_version')}", + "value": local_plugin.get("id") + }) + return [ + { + 'component': 'VForm', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'multiple': True, + 'chips': True, + 'model': 'plugin_ids', + 'label': '重装插件', + 'items': pluginOptions + } + } + ] + }, + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VAlert', + 'props': { + 'type': 'info', + 'variant': 'tonal', + 'text': '删除数据库中已安装插件记录、清理插件文件。' + } + } + ] + } + ] + }, + ] + } + ], { + "plugin_ids": [] + } + + @staticmethod + def get_local_plugins(): + """ + 获取本地插件 + """ + # 已安装插件 + install_plugins = SystemConfigOper().get(SystemConfigKey.UserInstalledPlugins) or [] + + local_plugins = {} + # 线上插件列表 + markets = settings.PLUGIN_MARKET.split(",") + for market in markets: + online_plugins = PluginHelper().get_plugins(market) or {} + for pid, plugin in online_plugins.items(): + if pid in install_plugins: + local_plugin = local_plugins.get(pid) + if local_plugin: + if StringUtils.compare_version(local_plugin.get("plugin_version"), plugin.get("version")) < 0: + local_plugins[pid] = { + "id": pid, + "plugin_name": plugin.get("name"), + "repo_url": market, + "plugin_version": plugin.get("version") + } + else: + local_plugins[pid] = { + "id": pid, + "plugin_name": plugin.get("name"), + "repo_url": market, + "plugin_version": plugin.get("version") + } + + return local_plugins + + def get_page(self) -> List[dict]: + pass + + def stop_service(self): + """ + 退出插件 + """ + pass