From dd9258dc4218faacb7c9ff25083bca42885f3d11 Mon Sep 17 00:00:00 2001 From: thsrite Date: Thu, 24 Aug 2023 13:07:31 +0800 Subject: [PATCH] =?UTF-8?q?fix=20=E7=9B=AE=E5=BD=95=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=E8=BD=AC=E7=A7=BB=E6=B6=88=E6=81=AF=E7=BB=9F=E4=B8=80=E5=8F=91?= =?UTF-8?q?=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/meta/metabase.py | 2 +- app/plugins/dirmonitor/__init__.py | 426 +++++++++++++++++++---------- 2 files changed, 279 insertions(+), 149 deletions(-) diff --git a/app/core/meta/metabase.py b/app/core/meta/metabase.py index a1a55224..b9b2d5f9 100644 --- a/app/core/meta/metabase.py +++ b/app/core/meta/metabase.py @@ -243,7 +243,7 @@ class MetaBase(object): else: return [self.begin_season] - @ property + @property def episode(self) -> str: """ 返回开始集、结束集字符串 diff --git a/app/plugins/dirmonitor/__init__.py b/app/plugins/dirmonitor/__init__.py index 42ed1fa4..34f9ee2f 100644 --- a/app/plugins/dirmonitor/__init__.py +++ b/app/plugins/dirmonitor/__init__.py @@ -3,9 +3,13 @@ import shutil import threading import time import traceback +from datetime import datetime from pathlib import Path from typing import List, Tuple, Dict, Any +from threading import Event +from apscheduler.schedulers.background import BackgroundScheduler +from apscheduler.triggers.cron import CronTrigger from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer from watchdog.observers.polling import PollingObserver @@ -21,7 +25,7 @@ from app.modules.qbittorrent import Qbittorrent from app.modules.transmission import Transmission from app.plugins import _PluginBase from app.schemas import Notification, NotificationType, TransferInfo -from app.schemas.types import EventType +from app.schemas.types import EventType, MediaType from app.utils.system import SystemUtils lock = threading.Lock() @@ -72,6 +76,7 @@ class DirMonitor(_PluginBase): _synced_files = [] # 私有属性 + _scheduler = None transferhis = None downloadhis = None transferchian = None @@ -88,6 +93,9 @@ class DirMonitor(_PluginBase): _dirconf: Dict[str, Path] = {} qb = None tr = None + _medias = {} + # 退出事件 + _event = Event() def init_plugin(self, config: dict = None): self.transferhis = TransferHistoryOper(self.db) @@ -112,6 +120,7 @@ class DirMonitor(_PluginBase): if self._enabled: self.qb = Qbittorrent() self.tr = Transmission() + self._scheduler = BackgroundScheduler(timezone=settings.TZ) # 启动任务 monitor_dirs = self._monitor_dirs.split("\n") @@ -165,6 +174,12 @@ class DirMonitor(_PluginBase): logger.error(f"{mon_path} 启动目录监控失败:{err_msg}") self.systemmessage.put(f"{mon_path} 启动目录监控失败:{err_msg}") + # 追加入库消息统一发送服务 + self._scheduler.add_job(self.send_msg, trigger='interval', seconds=5) + # 启动服务 + self._scheduler.print_jobs() + self._scheduler.start() + def event_handler(self, event, mon_path: str, text: str, event_path: str): """ 处理文件变化 @@ -292,12 +307,68 @@ class DirMonitor(_PluginBase): # 刮削元数据 self.chain.scrape_metadata(path=transferinfo.target_path, mediainfo=mediainfo) + + """ + { + "title_year season": { + "files": [ + { + "path":, + "mediainfo":, + "file_meta":, + "transferinfo": + } + ], + "time": "2023-08-24 23:23:23.332" + } + } + """ + # 发送消息汇总 + media_list = self._medias.get(mediainfo.title_year + " " + meta.season) or {} + if media_list: + media_files = media_list.get("files") or [] + if media_files: + file_exists = False + for file in media_files: + if str(event_path) == file.get("path"): + file_exists = True + break + if not file_exists: + media_files.append({ + "path": event_path, + "mediainfo": mediainfo, + "file_meta": file_meta, + "transferinfo": transferinfo + }) + else: + media_files = [ + { + "path": event_path, + "mediainfo": mediainfo, + "file_meta": file_meta, + "transferinfo": transferinfo + } + ] + media_list = { + "files": media_files, + "time": datetime.now() + } + else: + media_list = { + "files": [ + { + "path": event_path, + "mediainfo": mediainfo, + "file_meta": file_meta, + "transferinfo": transferinfo + } + ], + "time": datetime.now() + } + self._medias[mediainfo.title_year + " " + meta.season] = media_list + # 刷新媒体库 self.chain.refresh_mediaserver(mediainfo=mediainfo, file_path=transferinfo.target_path) - # 发送通知 - if self._notify: - self.transferchian.send_transfer_message(meta=file_meta, mediainfo=mediainfo, - transferinfo=transferinfo) # 广播事件 self.eventmanager.send_event(EventType.TransferComplete, { 'meta': file_meta, @@ -319,6 +390,58 @@ class DirMonitor(_PluginBase): except Exception as e: logger.error("目录监控发生错误:%s - %s" % (str(e), traceback.format_exc())) + def send_msg(self): + """ + 定时检查是否有媒体处理完,发送统一消息 + """ + if not self._medias or not self._medias.keys(): + return + + # 遍历检查是否已刮削完,发送消息 + for medis_title_year_season in list(self._medias.keys()): + media_list = self._medias.get(medis_title_year_season) + logger.info(f"开始处理媒体 {medis_title_year_season} 消息") + + if not media_list: + continue + + # 获取最后更新时间 + last_update_time = media_list.get("time") + media_files = media_list.get("files") + if not last_update_time or not media_files: + continue + + transferinfo = media_files[0].get("transferinfo") + file_meta = media_files[0].get("file_meta") + mediainfo = media_files[0].get("mediainfo") + # 判断最后更新时间距现在是已超过3秒,超过则发送消息 + if (datetime.now() - last_update_time).total_seconds() > 3: + # 发送通知 + if self._notify: + + # 汇总处理文件总大小 + total_size = 0 + file_count = 0 + for file in media_files: + transferinfo = file.get("transferinfo") + total_size += transferinfo.total_size + file_count += 1 + transferinfo.total_size = total_size + # 汇总处理文件数量 + transferinfo.file_count = file_count + + # 处理文件多,说明是剧集,显示季入库消息 + if mediainfo.type == MediaType.TV and file_count > 1: + file_meta.begin_episode = file_meta.begin_episode + file_meta.end_episode = media_files[-1].get("file_meta").end_episode + + self.transferchian.send_transfer_message(meta=file_meta, + mediainfo=mediainfo, + transferinfo=transferinfo) + # 发送完消息,移出key + del self._medias[medis_title_year_season] + continue + def get_download_hash(self, src: Path, tmdb_id: int): """ 获取download_hash @@ -392,149 +515,149 @@ class DirMonitor(_PluginBase): def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: return [ - { - 'component': 'VForm', - 'content': [ - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 6 - }, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'enabled', - 'label': '启用插件', - } - } - ] - }, - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 6 - }, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'notify', - 'label': '发送通知', - } - } - ] - } - ] - }, - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 6 - }, - 'content': [ - { - 'component': 'VSelect', - 'props': { - 'model': 'mode', - 'label': '监控模式', - 'items': [ - {'title': '兼容模式', 'value': 'compatibility'}, - {'title': '性能模式', 'value': 'fast'} - ] - } - } - ] - }, - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 6 - }, - 'content': [ - { - 'component': 'VSelect', - 'props': { - 'model': 'transfer_type', - 'label': '转移方式', - 'items': [ - {'title': '移动', 'value': 'move'}, - {'title': '复制', 'value': 'copy'}, - {'title': '硬链接', 'value': 'link'}, - {'title': '软链接', 'value': 'softlink'} - ] - } - } - ] - } - ] - }, - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': { - 'cols': 12 - }, - 'content': [ - { - 'component': 'VTextarea', - 'props': { - 'model': 'monitor_dirs', - 'label': '监控目录', - 'rows': 5, - 'placeholder': '每一行一个目录,支持两种配置方式:\n' - '监控目录\n' - '监控目录:转移目的目录' - } - } - ] - } - ] - }, - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': { - 'cols': 12 - }, - 'content': [ - { - 'component': 'VTextarea', - 'props': { - 'model': 'exclude_keywords', - 'label': '排除关键词', - 'rows': 2, - 'placeholder': '每一行一个关键词' - } - } - ] - } - ] - } - ] - } - ], { - "enabled": False, - "notify": False, - "mode": "fast", - "transfer_type": settings.TRANSFER_TYPE, - "monitor_dirs": "", - "exclude_keywords": "" - } + { + 'component': 'VForm', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enabled', + 'label': '启用插件', + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'notify', + 'label': '发送通知', + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'model': 'mode', + 'label': '监控模式', + 'items': [ + {'title': '兼容模式', 'value': 'compatibility'}, + {'title': '性能模式', 'value': 'fast'} + ] + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'model': 'transfer_type', + 'label': '转移方式', + 'items': [ + {'title': '移动', 'value': 'move'}, + {'title': '复制', 'value': 'copy'}, + {'title': '硬链接', 'value': 'link'}, + {'title': '软链接', 'value': 'softlink'} + ] + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VTextarea', + 'props': { + 'model': 'monitor_dirs', + 'label': '监控目录', + 'rows': 5, + 'placeholder': '每一行一个目录,支持两种配置方式:\n' + '监控目录\n' + '监控目录:转移目的目录' + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VTextarea', + 'props': { + 'model': 'exclude_keywords', + 'label': '排除关键词', + 'rows': 2, + 'placeholder': '每一行一个关键词' + } + } + ] + } + ] + } + ] + } + ], { + "enabled": False, + "notify": False, + "mode": "fast", + "transfer_type": settings.TRANSFER_TYPE, + "monitor_dirs": "", + "exclude_keywords": "" + } def get_page(self) -> List[dict]: pass @@ -551,3 +674,10 @@ class DirMonitor(_PluginBase): except Exception as e: print(str(e)) self._observer = [] + if self._scheduler: + self._scheduler.remove_all_jobs() + if self._scheduler.running: + self._event.set() + self._scheduler.shutdown() + self._event.clear() + self._scheduler = None