From de1d78839d715bb53da6f80dd3a0b75e51455c40 Mon Sep 17 00:00:00 2001 From: Allen Date: Wed, 29 May 2024 18:31:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BB=AA=E8=A1=A8=E6=9D=BF?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E9=80=9F=E7=8E=87=E7=BB=84=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8D=95=E7=8B=AC=E5=B1=95=E7=A4=BAqb?= =?UTF-8?q?=E5=92=8Ctr=E7=9A=84=E5=AE=9E=E6=97=B6=E9=80=9F=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- plugins/downloaderhelper/__init__.py | 559 +++++++++++++++++++++++---- plugins/downloaderhelper/module.py | 17 + 3 files changed, 509 insertions(+), 70 deletions(-) diff --git a/package.json b/package.json index 47ceb39..04a4bf6 100644 --- a/package.json +++ b/package.json @@ -640,11 +640,12 @@ "name": "下载器助手", "description": "自动做种、站点标签、自动删种。", "labels": "下载管理,仪表板", - "version": "2.5", + "version": "2.6", "icon": "DownloaderHelper.png", "author": "hotlcc", "level": 1, "history": { + "v2.6": "新增仪表板实时速率组件,支持单独展示qb和tr的实时速率(tr未测试,有问题提Issue并@hotlcc)。", "v2.5": "优化通知类型;降低认证级别要求,使MP非认证用户可用,但无法使用【站点名称优先】功能。主程序需升级至v1.9.2及以上版本,否则插件功能异常!", "v2.4": "修复tr活动种子仪表板的种子排序的bug;优化插件的消息发送。", "v2.3": "仪表板支持多个下载器活动种子组件(主程序版本需大于v1.9.1)。", diff --git a/plugins/downloaderhelper/__init__.py b/plugins/downloaderhelper/__init__.py index c1fc0b4..38bedc8 100644 --- a/plugins/downloaderhelper/__init__.py +++ b/plugins/downloaderhelper/__init__.py @@ -9,6 +9,7 @@ from urllib.parse import urlparse import pytz from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger +from cachetools import TTLCache from qbittorrentapi import TorrentDictionary, TorrentState from transmission_rpc.torrent import Torrent, Status as TorrentStatus from ruamel.yaml.comments import CommentedMap @@ -21,7 +22,7 @@ from app.log import logger from app.modules.qbittorrent.qbittorrent import Qbittorrent from app.modules.transmission.transmission import Transmission from app.plugins import _PluginBase -from app.plugins.downloaderhelper.module import TaskContext, TaskResult, Downloader, TorrentField, TorrentFieldMap, DownloaderMap +from app.plugins.downloaderhelper.module import TaskContext, TaskResult, Downloader, TorrentField, TorrentFieldMap, DownloaderMap, DownloaderTransferInfo from app.schemas import NotificationType from app.schemas.types import EventType from app.utils.string import StringUtils @@ -35,7 +36,7 @@ class DownloaderHelper(_PluginBase): # 插件图标 plugin_icon = "DownloaderHelper.png" # 插件版本 - plugin_version = "2.5" + plugin_version = "2.6" # 插件作者 plugin_author = "hotlcc" # 作者主页 @@ -57,6 +58,8 @@ class DownloaderHelper(_PluginBase): __exit_event: ThreadEvent = ThreadEvent() # 任务锁 __task_lock: RLock = RLock() + # 缓存 + __ttl_cache = TTLCache(maxsize=128, ttl=1800) # 配置相关 # 插件缺省配置 @@ -77,7 +80,8 @@ class DownloaderHelper(_PluginBase): TorrentField.TAGS.name, TorrentField.ADD_TIME.name, TorrentField.UPLOADED.name, - ] + ], + 'dashboard_speed_widget_target_downloaders': ['default'], } # 插件用户配置 __config: Dict[str, Any] = {} @@ -93,6 +97,12 @@ class DownloaderHelper(_PluginBase): __exclude_tags: Set[str] = set() # 多级根域名,用于在打标时做特殊处理 __multi_level_root_domain: List[str] = ['edu.cn', 'com.cn', 'net.cn', 'org.cn'] + # vuetifyjs mdi 图标 svg path 值 + __mdi_icon_svg_path = { + 'mdi-cloud-upload': 'M11 20H6.5q-2.28 0-3.89-1.57Q1 16.85 1 14.58q0-1.95 1.17-3.48q1.18-1.53 3.08-1.95q.63-2.3 2.5-3.72Q9.63 4 12 4q2.93 0 4.96 2.04Q19 8.07 19 11q1.73.2 2.86 1.5q1.14 1.28 1.14 3q0 1.88-1.31 3.19T18.5 20H13v-7.15l1.6 1.55L16 13l-4-4l-4 4l1.4 1.4l1.6-1.55Z', + 'mdi-download-box': 'M5 3h14a2 2 0 0 1 2 2v14c0 1.11-.89 2-2 2H5a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2m3 14h8v-2H8zm8-7h-2.5V7h-3v3H8l4 4z', + 'mdi-content-save': 'M15 9H5V5h10m-3 14a3 3 0 0 1-3-3a3 3 0 0 1 3-3a3 3 0 0 1 3 3a3 3 0 0 1-3 3m5-16H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V7z', + } def init_plugin(self, config: dict = None): """ @@ -143,7 +153,8 @@ class DownloaderHelper(_PluginBase): ) and self.__check_enable_any_task() ) - or self.__check_enable_dashboard_widget() + or self.__check_enable_dashboard_active_torrent_widget() + or self.__check_enable_dashboard_speed_widget() ) else False return state @@ -439,9 +450,23 @@ class DownloaderHelper(_PluginBase): 'content': [{ 'component': 'VSwitch', 'props': { - 'model': '_config_dashboard_dialog_closed', + 'model': '_config_dashboard_active_torrent_dialog_closed', 'label': '配置仪表板活动种子组件', - 'hint': '点击展开仪表板组件配置窗口。' + 'hint': '点击展开仪表板活动种子组件配置窗口。' + } + }] + }, { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 4, 'xl': 4, 'lg': 4, 'md': 4, 'sm': 6, 'xs': 12 + }, + 'content': [{ + 'component': 'VSwitch', + 'props': { + 'model': '_config_dashboard_speed_dialog_closed', + 'label': '配置仪表板实时速率组件', + 'hint': '点击展开仪表板实时速率组件配置窗口。' } }] }] @@ -489,7 +514,7 @@ class DownloaderHelper(_PluginBase): }, { 'component': 'VDialog', 'props': { - 'model': '_config_dashboard_dialog_closed', + 'model': '_config_dashboard_active_torrent_dialog_closed', 'max-width': '40rem' }, 'content': [{ @@ -503,7 +528,7 @@ class DownloaderHelper(_PluginBase): 'content': [{ 'component': 'VDialogCloseBtn', 'props': { - 'model': '_config_dashboard_dialog_closed' + 'model': '_config_dashboard_active_torrent_dialog_closed' } }, { 'component': 'VRow', @@ -517,8 +542,8 @@ class DownloaderHelper(_PluginBase): 'component': 'VSwitch', 'props': { 'model': 'enable_dashboard_widget', - 'label': '启用仪表板组件', - 'hint': '是否启用仪表板组件。' + 'label': '启用组件', + 'hint': '是否启用仪表板活动种子组件。' } }] }, { @@ -597,6 +622,83 @@ class DownloaderHelper(_PluginBase): }] }] }] + }, { + 'component': 'VDialog', + 'props': { + 'model': '_config_dashboard_speed_dialog_closed', + 'max-width': '40rem' + }, + 'content': [{ + 'component': 'VCard', + 'props': { + 'title': '配置仪表板实时速率组件', + 'style': { + 'padding': '0 20px 20px 20px' + } + }, + 'content': [{ + 'component': 'VDialogCloseBtn', + 'props': { + 'model': '_config_dashboard_speed_dialog_closed' + } + }, { + 'component': 'VRow', + 'content': [{ + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 6, 'xl': 6, 'lg': 6, 'md': 6, 'sm': 6, 'xs': 12 + }, + 'content': [{ + 'component': 'VSwitch', + 'props': { + 'model': 'enable_dashboard_speed_widget', + 'label': '启用组件', + 'hint': '是否启用仪表板实时速率组件。' + } + }] + }] + }, { + 'component': 'VRow', + 'content': [{ + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 6, 'xl': 6, 'lg': 6, 'md': 6, 'sm': 6, 'xs': 12 + }, + 'content': [{ + 'component': 'VTextField', + 'props': { + 'model': 'dashboard_speed_widget_refresh', + 'label': '刷新间隔(秒)', + 'placeholder': '5', + 'type': 'number', + 'hint': '组件刷新时间间隔,单位为秒,缺省时不刷新。请合理配置,间隔太短可能会导致下载器假死。' + } + }] + }, { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'xxl': 6, 'xl': 6, 'lg': 6, 'md': 6, 'sm': 6, 'xs': 12 + }, + 'content': [{ + 'component': 'VSelect', + 'props': { + 'model': 'dashboard_speed_widget_target_downloaders', + 'label': '目标下载器', + 'multiple': True, + 'items': [ + {'title': '系统默认下载器', 'value': 'default'}, + {'title': Downloader.QB.name_, 'value': Downloader.QB.id}, + {'title': Downloader.TR.name_, 'value': Downloader.TR.id} + ], + 'hint': '选择要展示的目标下载器。' + } + }] + }] + }] + }] }, { 'component': 'VRow', 'content': [{ @@ -668,15 +770,28 @@ class DownloaderHelper(_PluginBase): }] """ dashboard_meta = [] - target_downloader_ids = self.__get_target_downloader_ids() - for target_downloader_id in target_downloader_ids: - downloader = self.__get_downloader_enum_by_id(downloader_id=target_downloader_id) - if not downloader: - continue - dashboard_meta.append({ - "key": downloader.id, - "name": f"活动种子 #{downloader.short_name}", - }) + if not self.get_state(): + return dashboard_meta + if self.__check_enable_dashboard_active_torrent_widget(): + target_downloader_ids = self.__get_dashboard_active_torrent_widget_target_downloader_ids() + for target_downloader_id in target_downloader_ids: + downloader = self.__get_downloader_enum_by_id(downloader_id=target_downloader_id) + if not downloader: + continue + dashboard_meta.append({ + "key": downloader.id, + "name": f"活动种子 #{downloader.short_name}", + }) + if self.__check_enable_dashboard_speed_widget(): + target_downloader_ids = self.__get_dashboard_speed_widget_target_downloader_ids() + for target_downloader_id in target_downloader_ids: + downloader = self.__get_downloader_enum_by_id(downloader_id=target_downloader_id) + if not downloader: + continue + dashboard_meta.append({ + "key": f"{downloader.id}_speed", + "name": f"实时速率 #{downloader.short_name}", + }) return dashboard_meta def get_dashboard(self, key: str = None, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: @@ -696,21 +811,35 @@ class DownloaderHelper(_PluginBase): :param key: 仪表盘key,根据指定的key返回相应的仪表盘数据,缺省时返回一个固定的仪表盘数据(兼容旧版) """ - if not self.get_state() or not self.__check_enable_dashboard_widget(): + if not self.get_state(): return None - target_downloader_ids = self.__get_target_downloader_ids() + enable_dashboard_active_torrent_widget = self.__check_enable_dashboard_active_torrent_widget() + enable_dashboard_speed_widget = self.__check_enable_dashboard_speed_widget() + if not enable_dashboard_active_torrent_widget and not enable_dashboard_speed_widget: + return None + # 无key兼容历史 + dashboard_active_torrent_widget_target_downloader_ids = self.__get_dashboard_active_torrent_widget_target_downloader_ids() if not key: - if not target_downloader_ids: + if enable_dashboard_active_torrent_widget and dashboard_active_torrent_widget_target_downloader_ids: + return self.__get_dashboard_active_torrent_widget(downloader_id=dashboard_active_torrent_widget_target_downloader_ids[0]) + else: return None - return self.__get_active_torrent_dashboard(downloader_id=target_downloader_ids[0]) - if key in target_downloader_ids: - return self.__get_active_torrent_dashboard(downloader_id=key) + # 有key + dashboard_speed_widget_target_downloader_ids = self.__get_dashboard_speed_widget_target_downloader_ids() + if key == Downloader.QB.id and enable_dashboard_active_torrent_widget and Downloader.QB.id in dashboard_active_torrent_widget_target_downloader_ids: + return self.__get_dashboard_active_torrent_widget(downloader_id=Downloader.QB.id) + if key == Downloader.TR.id and enable_dashboard_active_torrent_widget and Downloader.TR.id in dashboard_active_torrent_widget_target_downloader_ids: + return self.__get_dashboard_active_torrent_widget(downloader_id=Downloader.TR.id) + if key == f"{Downloader.QB.id}_speed" and enable_dashboard_speed_widget and Downloader.QB.id in dashboard_speed_widget_target_downloader_ids: + return self.__get_dashboard_speed_widget(downloader_id=Downloader.QB.id) + if key == f"{Downloader.TR.id}_speed" and enable_dashboard_speed_widget and Downloader.TR.id in dashboard_speed_widget_target_downloader_ids: + return self.__get_dashboard_speed_widget(downloader_id=Downloader.TR.id) return None - def __get_active_torrent_dashboard(self, - downloader_id: str) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: + def __get_dashboard_active_torrent_widget(self, + downloader_id: str) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: """ - 获取活动种子仪表板 + 获取仪表板活动种子组件 """ downloader = self.__get_downloader_enum_by_id(downloader_id=downloader_id) if not downloader: @@ -739,7 +868,42 @@ class DownloaderHelper(_PluginBase): attrs['refresh'] = self.__get_config_item('dashboard_widget_refresh') # 页面元素 - elements = self.__get_dashboard_elememts(downloader_id=downloader_id) + elements = self.__get_dashboard_active_torrent_widget_elememts(downloader_id=downloader_id) + + return cols, attrs, elements + + def __get_dashboard_speed_widget(self, + downloader_id: str) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: + """ + 获取仪表板实时速率组件 + """ + downloader = self.__get_downloader_enum_by_id(downloader_id=downloader_id) + if not downloader: + return None + if self.__exit_event.is_set(): + logger.warn('插件服务正在退出,操作取消') + return None + + # 列配置 + cols = { + 'cols': 12, + 'xxl': 4, + 'xl': 4, + 'lg': 4, + 'md': 4, + 'sm': 12, + 'xs': 12 + } + + # 全局配置 + attrs = { + 'title': f'实时速率 #{downloader.short_name}' + } + if self.__check_target_downloader(downloader_id=downloader_id): + attrs['refresh'] = self.__get_config_item('dashboard_speed_widget_refresh') + + # 页面元素 + elements = self.__get_dashboard_speed_widget_elememts(downloader_id=downloader_id) return cols, attrs, elements @@ -751,6 +915,7 @@ class DownloaderHelper(_PluginBase): logger.info('尝试停止插件服务...') self.__exit_event.set() self.__stop_scheduler() + self.__clear_cache() logger.info('插件服务停止完成') except Exception as e: logger.error(f"插件服务停止异常: {str(e)}", exc_info=True) @@ -855,6 +1020,20 @@ class DownloaderHelper(_PluginBase): except Exception as e: logger.error(f"插件服务调度器停止异常: {str(e)}", exc_info=True) + def __clear_cache(self): + """ + 清除缓存 + """ + try: + logger.info('尝试清除插件缓存...') + if self.__ttl_cache: + self.__ttl_cache.clear() + logger.info('插件缓存清除成功') + else: + logger.info('插件未启用缓存,无须清除') + except Exception as e: + logger.error(f"插件缓存清除异常: {str(e)}", exc_info=True) + def __fix_config(self, config: dict) -> dict: """ 修正配置 @@ -993,13 +1172,20 @@ class DownloaderHelper(_PluginBase): return True if self.__check_enable_qb_task() \ or self.__check_enable_tr_task() else False - def __check_enable_dashboard_widget(self) -> bool: + def __check_enable_dashboard_active_torrent_widget(self) -> bool: """ - 判断是否启用了仪表板组件 - :return: 是否启用了仪表板组件 + 判断是否启用了仪表板活动种子组件 + :return: 是否启用了仪表板活动种子组件 """ return True if self.__get_config_item('enable_dashboard_widget') else False + def __check_enable_dashboard_speed_widget(self) -> bool: + """ + 判断是否启用了仪表板实时速率组件 + :return: 是否启用了仪表板实时速率组件 + """ + return True if self.__get_config_item('enable_dashboard_speed_widget') else False + @classmethod def __parse_tracker_for_qbittorrent(cls, torrent: TorrentDictionary) -> Optional[str]: """ @@ -1868,13 +2054,13 @@ class DownloaderHelper(_PluginBase): result.append(field) return result - def __build_dashboard_widget_table_head_content(self, + def __build_dashboard_widget_torrent_table_head_content(self, fields: List[TorrentField] = None) -> list: """ - 构造仪表板组件表头内容 + 构造仪表板组件种子表头内容 """ if not fields: - fields = self.__get_dashboard_widget_display_fields() + fields = self.__get_dashboard_active_torrent_widget_display_fields() if not fields: return [] return [{ @@ -1885,22 +2071,22 @@ class DownloaderHelper(_PluginBase): 'text': field.name_ } for field in fields if field] - def __build_dashboard_widget_table_head(self, + def __build_dashboard_widget_torrent_table_head(self, fields: List[TorrentField] = None) -> dict: """ - 构造仪表板组件表头 + 构造仪表板组件种子表头 """ return { 'component': 'thead', - 'content': self.__build_dashboard_widget_table_head_content(fields=fields) + 'content': self.__build_dashboard_widget_torrent_table_head_content(fields=fields) } - def __build_dashboard_widget_table_body_content(self, + def __build_dashboard_widget_torrent_table_body_content(self, data: List[List[Any]], field_count: int, downloader_id: str) -> list: """ - 构造仪表板组件表体内容 + 构造仪表板组件种子表体内容 :param downloader_id: 下载器ID :param data: 表格数据 :param field_count: 字段数量 @@ -1936,24 +2122,26 @@ class DownloaderHelper(_PluginBase): }] }] - def __build_dashboard_widget_table_body(self, + def __build_dashboard_widget_torrent_table_body(self, data: List[List[Any]], field_count: int, downloader_id: str) -> dict: """ - 构造仪表板组件表体内容 + 构造仪表板组件种子表体 """ return { 'component': 'tbody', - 'content': self.__build_dashboard_widget_table_body_content(data=data, field_count=field_count, downloader_id=downloader_id) + 'content': self.__build_dashboard_widget_torrent_table_body_content(data=data, field_count=field_count, downloader_id=downloader_id) } - def __get_target_downloader_ids(self) -> List[str]: + def __get_dashboard_widget_target_downloader_ids(self, config_key: str) -> List[str]: """ - 获取目标下载器ids + 获取仪表板组件目标下载器ids """ target_downloader_ids = [] - target_downloaders = self.__get_config_item('dashboard_widget_target_downloaders') + if not config_key: + return target_downloader_ids + target_downloaders = self.__get_config_item(config_key) if not target_downloaders: return target_downloader_ids for target_downloader in target_downloaders: @@ -1963,9 +2151,21 @@ class DownloaderHelper(_PluginBase): target_downloader_ids.append(target_downloader) return target_downloader_ids - def __get_dashboard_widget_display_fields(self) -> List[TorrentField]: + def __get_dashboard_active_torrent_widget_target_downloader_ids(self) -> List[str]: """ - 获取仪表板组件展示字段 + 获取仪表板活动种子组件目标下载器ids + """ + return self.__get_dashboard_widget_target_downloader_ids(config_key='dashboard_widget_target_downloaders') + + def __get_dashboard_speed_widget_target_downloader_ids(self) -> List[str]: + """ + 获取仪表板实时速率组件目标下载器ids + """ + return self.__get_dashboard_widget_target_downloader_ids(config_key='dashboard_speed_widget_target_downloaders') + + def __get_dashboard_active_torrent_widget_display_fields(self) -> List[TorrentField]: + """ + 获取仪表板活动种子组件展示字段 """ fields = self.__get_config_item('dashboard_widget_display_fields') return self.__ensure_torrent_fields(fields=fields) @@ -1992,28 +2192,28 @@ class DownloaderHelper(_PluginBase): else: return False - def __get_downloader_torrent_data(self, - downloader_id: str, - fields: List[TorrentField] = None): + def __get_downloader_active_torrent_data(self, + downloader_id: str, + fields: List[TorrentField] = None): """ - 获取下载器种子数据 + 获取下载器活动种子数据 """ if not downloader_id: return None # 字段 if not fields: - fields = self.__get_dashboard_widget_display_fields() + fields = self.__get_dashboard_active_torrent_widget_display_fields() if downloader_id == Downloader.QB.id: - return self.__get_qbittorrent_torrent_data(fields=fields) + return self.__get_qbittorrent_active_torrent_data(fields=fields) elif downloader_id == Downloader.TR.id: - return self.__get_transmission_torrent_data(fields=fields) + return self.__get_transmission_active_torrent_data(fields=fields) else: return None - def __get_qbittorrent_torrent_data(self, - fields: List[TorrentField] = None): + def __get_qbittorrent_active_torrent_data(self, + fields: List[TorrentField] = None): """ - 获取qb种子数据 + 获取qb活动种子数据 """ if self.__exit_event.is_set(): logger.warn('插件服务正在退出,操作取消') @@ -2023,7 +2223,7 @@ class DownloaderHelper(_PluginBase): return None # 字段 if not fields: - fields = self.__get_dashboard_widget_display_fields() + fields = self.__get_dashboard_active_torrent_widget_display_fields() # 活动种子 torrents, error = qbittorrent.get_torrents(status=['active']) if error: @@ -2153,10 +2353,10 @@ class DownloaderHelper(_PluginBase): arguments.append('uploadLimited') return list(set(arguments)) - def __get_transmission_torrent_data(self, - fields: List[TorrentField] = None): + def __get_transmission_active_torrent_data(self, + fields: List[TorrentField] = None): """ - 获取tr种子数据 + 获取tr活动种子数据 """ if self.__exit_event.is_set(): logger.warn('插件服务正在退出,操作取消') @@ -2166,7 +2366,7 @@ class DownloaderHelper(_PluginBase): return None # 字段 if not fields: - fields = self.__get_dashboard_widget_display_fields() + fields = self.__get_dashboard_active_torrent_widget_display_fields() torrents, _ = transmission.trc.get_recently_active_torrents(arguments=self.__build_transmission_field_arguments(fields=fields)) if not torrents: return None @@ -2282,18 +2482,18 @@ class DownloaderHelper(_PluginBase): logger.error(f'从tr种子中提取值异常: {str(e)}, torrent = {str(torrent.fields)}', exc_info=True) return None - def __get_dashboard_elememts(self, downloader_id: str) -> list: + def __get_dashboard_active_torrent_widget_elememts(self, downloader_id: str) -> list: """ - 获取仪表板元素 + 获取仪表板活动种子组件元素 """ if not downloader_id: return None if self.__exit_event.is_set(): logger.warn('插件服务正在退出,操作取消') return None - fields = self.__get_dashboard_widget_display_fields() + fields = self.__get_dashboard_active_torrent_widget_display_fields() field_count=len(fields) - data = self.__get_downloader_torrent_data(downloader_id=downloader_id, fields=fields) + data = self.__get_downloader_active_torrent_data(downloader_id=downloader_id, fields=fields) if self.__exit_event.is_set(): logger.warn('插件服务正在退出,操作取消') return None @@ -2308,11 +2508,232 @@ class DownloaderHelper(_PluginBase): } }, 'content': [ - self.__build_dashboard_widget_table_head(fields=fields), - self.__build_dashboard_widget_table_body(data=data, field_count=field_count, downloader_id=downloader_id) + self.__build_dashboard_widget_torrent_table_head(fields=fields), + self.__build_dashboard_widget_torrent_table_body(data=data, field_count=field_count, downloader_id=downloader_id) ] }] + def __get_downloader_transfer_info(self, + downloader_id: str) -> DownloaderTransferInfo: + """ + 获取下载器传输信息 + """ + if downloader_id == Downloader.QB.id: + return self.__get_qbittorrent_transfer_info() + elif downloader_id == Downloader.TR.id: + return self.__get_transmission_transfer_info() + else: + return DownloaderTransferInfo() + + def __get_qbittorrent_transfer_info(self) -> DownloaderTransferInfo: + """ + 获取qb下载器传输信息 + """ + result = DownloaderTransferInfo() + if self.__exit_event.is_set(): + logger.warn('插件服务正在退出,操作取消') + return result + qbittorrent = self.__get_qbittorrent() + if not qbittorrent: + return result + info = qbittorrent.transfer_info() + if info: + result.download_speed = f'{StringUtils.str_filesize(info.get("dl_info_speed"))}/s' + result.upload_speed = f'{StringUtils.str_filesize(info.get("up_info_speed"))}/s' + result.download_size = StringUtils.str_filesize(info.get("dl_info_data")) + result.upload_size = StringUtils.str_filesize(info.get("up_info_data")) + maindata = self.__get_qbittorrent_maindata() + if maindata: + server_state = maindata.get("server_state") + if server_state: + result.free_space = StringUtils.str_filesize(server_state.get("free_space_on_disk")) + return result + + def __get_qbittorrent_maindata(self): + """ + 获取qb的maindata + """ + cache_key = "qbittorrent_maindata" + maindata = self.__ttl_cache.get(cache_key) + if not maindata: + qbittorrent = self.__get_qbittorrent() + if qbittorrent: + maindata = qbittorrent.qbc.sync_maindata() + self.__ttl_cache[cache_key] = maindata + return maindata + + def __get_transmission_transfer_info(self) -> DownloaderTransferInfo: + """ + 获取qb下载器传输信息 + """ + result = DownloaderTransferInfo() + if self.__exit_event.is_set(): + logger.warn('插件服务正在退出,操作取消') + return result + transmission = self.__get_transmission() + if not transmission: + return result + info = transmission.transfer_info() + if info: + result.download_speed = f"{StringUtils.str_filesize(info.download_speed)}/s" + result.upload_speed = f"{StringUtils.str_filesize(info.upload_speed,)}/s" + result.download_size = StringUtils.str_filesize(info.current_stats.downloaded_bytes) + result.upload_size = StringUtils.str_filesize(info.current_stats.uploaded_bytes) + session = self.__get_transmission_session() + if session: + result.free_space = StringUtils.str_filesize(session.download_dir_free_space) + return result + + def __get_transmission_session(self): + """ + 获取tr的session + """ + cache_key = "transmission_session" + session = self.__ttl_cache.get(cache_key) + if not session: + transmission = self.__get_transmission() + if transmission: + session = transmission.get_session() + self.__ttl_cache[cache_key] = session + return session + + def __build_mdi_icon_svg_elememt(self, mdi_icon: str) -> dict: + """ + 构造 svg mdi 图标元素 + """ + if not mdi_icon: + return None + path = self.__mdi_icon_svg_path.get(mdi_icon) + if not path: + return None + return { + 'component': 'svg', + 'props': { + 'class': 'v-icon notranslate v-theme--light v-icon--size-default iconify iconify--mdi', + 'rounded': True, + 'width': '1em', + 'height': '1em', + 'viewBox': '0 0 24 24', + 'style': { + 'top': '-1px' + } + }, + 'content': [{ + 'component': 'path', + 'props': { + 'fill': 'currentColor', + 'd': path + } + }] + } + + def __build_dashboard_speed_widget_list_item_element(self, mdi_icon: str, label: str, value: str) -> dict: + """ + 构造仪表板实时速率组件列表item元素 + """ + if not mdi_icon or not label or not value: + return None + return { + 'component': 'div', + 'props': { + 'style': { + 'display': 'grid', + 'grid-template-areas': '"prepend content append"', + 'grid-template-columns': 'max-content 1fr auto', + 'padding-bottom': '16px' + } + }, + 'content': [{ + 'component': 'div', + 'props': { + 'style': { + 'grid-area': 'prepend', + 'height': '21px', + 'color': '#6a6670' + } + }, + 'content': [self.__build_mdi_icon_svg_elememt(mdi_icon=mdi_icon)] + }, { + 'component': 'div', + 'props': { + 'style': { + 'grid-area': 'content', + 'margin-left': '15px' + } + }, + 'content': [{ + 'component': 'h6', + 'props': { + 'class': 'text-sm font-weight-medium mb-1' + }, + 'text': label + }] + }, { + 'component': 'div', + 'props': { + 'style': { + 'grid-area': 'append' + } + }, + 'content': [{ + 'component': 'h6', + 'props': { + 'class': 'text-sm font-weight-medium mb-2' + }, + 'text': value + }] + }] + } + + def __get_dashboard_speed_widget_elememts(self, downloader_id: str) -> list: + """ + 获取仪表板实时速率组件元素 + """ + if not downloader_id: + return None + if self.__exit_event.is_set(): + logger.warn('插件服务正在退出,操作取消') + return None + data = self.__get_downloader_transfer_info(downloader_id=downloader_id) + if self.__exit_event.is_set(): + logger.warn('插件服务正在退出,操作取消') + return None + list_items = [ + self.__build_dashboard_speed_widget_list_item_element(mdi_icon='mdi-cloud-upload', label='总上传量', value=data.upload_size), + self.__build_dashboard_speed_widget_list_item_element(mdi_icon='mdi-download-box', label='总下载量', value=data.download_size), + self.__build_dashboard_speed_widget_list_item_element(mdi_icon='mdi-content-save', label='磁盘剩余空间', value=data.free_space), + ] + return [{ + 'component': 'div', + 'props': { + 'style': { + 'padding': '16px 0 20px 0' + } + }, + 'content': [{ + 'component': 'div', + 'content': [{ + 'component': 'p', + 'props': { + 'class': 'text-h5 me-2' + }, + 'text': f'↑{data.upload_speed}' + }, { + 'component': 'p', + 'props': { + 'class': 'text-h4 me-2' + }, + 'text': f'↓{data.download_speed}' + }] + }, { + 'component': 'div', + 'props': { + 'class': 'card-list mt-9' + }, + 'content': list_items + }] + }] + @eventmanager.register(EventType.DownloadAdded) def listen_download_added_event(self, event: Event = None): """ diff --git a/plugins/downloaderhelper/module.py b/plugins/downloaderhelper/module.py index 72dbc4a..cbabf2d 100644 --- a/plugins/downloaderhelper/module.py +++ b/plugins/downloaderhelper/module.py @@ -308,3 +308,20 @@ class TorrentField(Enum): # TorrentField 映射 TorrentFieldMap = dict((field.name, field) for field in TorrentField) + + +class DownloaderTransferInfo(): + """ + 下载器传输信息 + """ + + # 下载速度 + download_speed: Optional[str] = '0.00B/s' + # 上传速度 + upload_speed: Optional[str] = '0.00B/s' + # 下载量 + download_size: Optional[str] = '0.00B' + # 上传量 + upload_size: Optional[str] = '0.00B' + # 剩余空间 + free_space: Optional[str] = '0.00B'