mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-03-20 03:57:30 +08:00
TODO 后台整理队列
This commit is contained in:
@@ -343,6 +343,7 @@ class DownloadChain(ChainBase):
|
|||||||
seasons=_meta.season,
|
seasons=_meta.season,
|
||||||
episodes=download_episodes or _meta.episode,
|
episodes=download_episodes or _meta.episode,
|
||||||
image=_media.get_backdrop_image(),
|
image=_media.get_backdrop_image(),
|
||||||
|
downloader=_downloader,
|
||||||
download_hash=_hash,
|
download_hash=_hash,
|
||||||
torrent_name=_torrent.title,
|
torrent_name=_torrent.title,
|
||||||
torrent_description=_torrent.description,
|
torrent_description=_torrent.description,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import queue
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional, Tuple, Union, Dict
|
from queue import Queue
|
||||||
|
from typing import List, Optional, Tuple, Union, Dict, Callable
|
||||||
|
|
||||||
from app.chain import ChainBase
|
from app.chain import ChainBase
|
||||||
from app.chain.media import MediaChain
|
from app.chain.media import MediaChain
|
||||||
@@ -20,20 +22,34 @@ from app.helper.directory import DirectoryHelper
|
|||||||
from app.helper.format import FormatParser
|
from app.helper.format import FormatParser
|
||||||
from app.helper.progress import ProgressHelper
|
from app.helper.progress import ProgressHelper
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.schemas import TransferInfo, TransferTorrent, Notification, EpisodeFormat, FileItem, TransferDirectoryConf
|
from app.schemas import TransferInfo, TransferTorrent, Notification, EpisodeFormat, FileItem, TransferDirectoryConf, \
|
||||||
|
TransferTask, TransferQueue
|
||||||
from app.schemas.types import TorrentStatus, EventType, MediaType, ProgressKey, NotificationType, MessageChannel, \
|
from app.schemas.types import TorrentStatus, EventType, MediaType, ProgressKey, NotificationType, MessageChannel, \
|
||||||
SystemConfigKey
|
SystemConfigKey
|
||||||
|
from app.utils.singleton import Singleton
|
||||||
from app.utils.string import StringUtils
|
from app.utils.string import StringUtils
|
||||||
from app.utils.system import SystemUtils
|
from app.utils.system import SystemUtils
|
||||||
|
|
||||||
lock = threading.Lock()
|
lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
class TransferChain(ChainBase):
|
class TransferChain(ChainBase, metaclass=Singleton):
|
||||||
"""
|
"""
|
||||||
文件整理处理链
|
文件整理处理链
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 可处理的文件后缀
|
||||||
|
all_exts = settings.RMT_MEDIAEXT
|
||||||
|
|
||||||
|
# 待整理任务队列
|
||||||
|
_queue = Queue()
|
||||||
|
|
||||||
|
# 文件整理线程
|
||||||
|
_transfer_thread = None
|
||||||
|
|
||||||
|
# 文件整理检查间隔(秒)
|
||||||
|
_transfer_interval = 5
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.downloadhis = DownloadHistoryOper()
|
self.downloadhis = DownloadHistoryOper()
|
||||||
@@ -44,7 +60,69 @@ class TransferChain(ChainBase):
|
|||||||
self.storagechain = StorageChain()
|
self.storagechain = StorageChain()
|
||||||
self.systemconfig = SystemConfigOper()
|
self.systemconfig = SystemConfigOper()
|
||||||
self.directoryhelper = DirectoryHelper()
|
self.directoryhelper = DirectoryHelper()
|
||||||
self.all_exts = settings.RMT_MEDIAEXT
|
|
||||||
|
# 启动整理任务
|
||||||
|
self.__init()
|
||||||
|
|
||||||
|
def __init(self):
|
||||||
|
"""
|
||||||
|
初始化
|
||||||
|
"""
|
||||||
|
# 启动文件整理线程
|
||||||
|
self._transfer_thread = threading.Thread(target=self.__start_transfer, daemon=True)
|
||||||
|
self._transfer_thread.start()
|
||||||
|
|
||||||
|
def put_to_queue(self, task: TransferTask, callback: Optional[Callable] = None):
|
||||||
|
"""
|
||||||
|
添加到待整理队列
|
||||||
|
:param task: 任务信息
|
||||||
|
:param callback: 回调函数
|
||||||
|
"""
|
||||||
|
if not task:
|
||||||
|
return
|
||||||
|
self._queue.put(TransferQueue(
|
||||||
|
task=task,
|
||||||
|
callback=callback
|
||||||
|
))
|
||||||
|
|
||||||
|
def __start_transfer(self):
|
||||||
|
"""
|
||||||
|
处理队列
|
||||||
|
"""
|
||||||
|
while not global_vars.is_system_stopped:
|
||||||
|
try:
|
||||||
|
item: TransferQueue = self._queue.get(timeout=self._transfer_interval)
|
||||||
|
if item:
|
||||||
|
self.__handle_transfer(task=item.task, callback=item.callback)
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"整理队列处理出现错误:{e}")
|
||||||
|
|
||||||
|
def __handle_transfer(self, task: TransferTask, callback: Optional[Callable] = None):
|
||||||
|
"""
|
||||||
|
处理整理任务
|
||||||
|
"""
|
||||||
|
if not task:
|
||||||
|
return
|
||||||
|
# 执行整理
|
||||||
|
transferinfo: TransferInfo = self.transfer(fileitem=task.fileitem,
|
||||||
|
meta=task.meta,
|
||||||
|
mediainfo=task.mediainfo,
|
||||||
|
target_directory=task.target_directory,
|
||||||
|
target_storage=task.target_storage,
|
||||||
|
target_path=task.target_path,
|
||||||
|
transfer_type=task.transfer_type,
|
||||||
|
episodes_info=task.episodes_info,
|
||||||
|
scrape=task.scrape,
|
||||||
|
library_type_folder=task.library_type_folder,
|
||||||
|
library_category_folder=task.library_category_folder)
|
||||||
|
if not transferinfo:
|
||||||
|
logger.error("文件整理模块运行失败")
|
||||||
|
return
|
||||||
|
# 回调,位置传参:任务、整理结果
|
||||||
|
if callback:
|
||||||
|
callback(task, transferinfo)
|
||||||
|
|
||||||
def recommend_name(self, meta: MetaBase, mediainfo: MediaInfo) -> Optional[str]:
|
def recommend_name(self, meta: MetaBase, mediainfo: MediaInfo) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
@@ -152,7 +230,8 @@ class TransferChain(ChainBase):
|
|||||||
library_type_folder: bool = None, library_category_folder: bool = None,
|
library_type_folder: bool = None, library_category_folder: bool = None,
|
||||||
season: int = None, epformat: EpisodeFormat = None, min_filesize: int = 0,
|
season: int = None, epformat: EpisodeFormat = None, min_filesize: int = 0,
|
||||||
downloader: str = None, download_hash: str = None,
|
downloader: str = None, download_hash: str = None,
|
||||||
force: bool = False, src_match: bool = False) -> Tuple[bool, str]:
|
force: bool = False, src_match: bool = False,
|
||||||
|
background: bool = False) -> Tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
执行一个复杂目录的整理操作
|
执行一个复杂目录的整理操作
|
||||||
:param fileitem: 文件项
|
:param fileitem: 文件项
|
||||||
@@ -172,9 +251,16 @@ class TransferChain(ChainBase):
|
|||||||
:param download_hash: 下载记录hash
|
:param download_hash: 下载记录hash
|
||||||
:param force: 是否强制整理
|
:param force: 是否强制整理
|
||||||
:param src_match: 是否源目录匹配
|
:param src_match: 是否源目录匹配
|
||||||
|
:param background: 是否后台整理
|
||||||
返回:成功标识,错误信息
|
返回:成功标识,错误信息
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __callback(_task: TransferTask, _transferinfo: TransferInfo):
|
||||||
|
"""
|
||||||
|
TODO 整理完成回调
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
# 自定义格式
|
# 自定义格式
|
||||||
formaterHandler = FormatParser(eformat=epformat.format,
|
formaterHandler = FormatParser(eformat=epformat.format,
|
||||||
details=epformat.detail,
|
details=epformat.detail,
|
||||||
@@ -411,7 +497,26 @@ class TransferChain(ChainBase):
|
|||||||
storage=file_item.storage,
|
storage=file_item.storage,
|
||||||
target_storage=target_storage)
|
target_storage=target_storage)
|
||||||
|
|
||||||
# 执行整理
|
# 后台整理
|
||||||
|
if background:
|
||||||
|
self.__handle_transfer(task=TransferTask(
|
||||||
|
fileitem=file_item,
|
||||||
|
meta=file_meta,
|
||||||
|
mediainfo=file_mediainfo,
|
||||||
|
target_directory=target_directory or dir_info,
|
||||||
|
target_storage=target_storage,
|
||||||
|
target_path=target_path,
|
||||||
|
transfer_type=transfer_type,
|
||||||
|
episodes_info=episodes_info,
|
||||||
|
scrape=scrape,
|
||||||
|
library_type_folder=library_type_folder,
|
||||||
|
library_category_folder=library_category_folder,
|
||||||
|
downloader=downloader,
|
||||||
|
download_hash=download_hash
|
||||||
|
), callback=__callback)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 实时整理
|
||||||
transferinfo: TransferInfo = self.transfer(fileitem=file_item,
|
transferinfo: TransferInfo = self.transfer(fileitem=file_item,
|
||||||
meta=file_meta,
|
meta=file_meta,
|
||||||
mediainfo=file_mediainfo,
|
mediainfo=file_mediainfo,
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ class DownloadHistory(Base):
|
|||||||
episodes = Column(String)
|
episodes = Column(String)
|
||||||
# 海报
|
# 海报
|
||||||
image = Column(String)
|
image = Column(String)
|
||||||
|
# 下载器
|
||||||
|
downloader = Column(String)
|
||||||
# 下载任务Hash
|
# 下载任务Hash
|
||||||
download_hash = Column(String, index=True)
|
download_hash = Column(String, index=True)
|
||||||
# 种子名称
|
# 种子名称
|
||||||
@@ -168,10 +170,10 @@ class DownloadFiles(Base):
|
|||||||
下载文件记录
|
下载文件记录
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
||||||
# 下载任务Hash
|
|
||||||
download_hash = Column(String, index=True)
|
|
||||||
# 下载器
|
# 下载器
|
||||||
downloader = Column(String)
|
downloader = Column(String)
|
||||||
|
# 下载任务Hash
|
||||||
|
download_hash = Column(String, index=True)
|
||||||
# 完整路径
|
# 完整路径
|
||||||
fullpath = Column(String, index=True)
|
fullpath = Column(String, index=True)
|
||||||
# 保存路径
|
# 保存路径
|
||||||
|
|||||||
195
app/monitor.py
195
app/monitor.py
@@ -1,11 +1,9 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import platform
|
import platform
|
||||||
import queue
|
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from queue import Queue
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -28,7 +26,7 @@ from app.db.transferhistory_oper import TransferHistoryOper
|
|||||||
from app.helper.directory import DirectoryHelper
|
from app.helper.directory import DirectoryHelper
|
||||||
from app.helper.message import MessageHelper
|
from app.helper.message import MessageHelper
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.schemas import FileItem, TransferInfo, Notification
|
from app.schemas import FileItem, TransferInfo, Notification, TransferTask
|
||||||
from app.schemas.types import SystemConfigKey, MediaType, NotificationType, EventType
|
from app.schemas.types import SystemConfigKey, MediaType, NotificationType, EventType
|
||||||
from app.utils.singleton import Singleton
|
from app.utils.singleton import Singleton
|
||||||
from app.utils.string import StringUtils
|
from app.utils.string import StringUtils
|
||||||
@@ -52,12 +50,10 @@ class FileMonitorHandler(FileSystemEventHandler):
|
|||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
|
||||||
def on_created(self, event: FileSystemEvent):
|
def on_created(self, event: FileSystemEvent):
|
||||||
self.callback.event_handler(event=event, text="创建",
|
self.callback.event_handler(event=event, text="创建", event_path=Path(event.src_path))
|
||||||
mon_path=self._watch_path, event_path=Path(event.src_path))
|
|
||||||
|
|
||||||
def on_moved(self, event: FileSystemMovedEvent):
|
def on_moved(self, event: FileSystemMovedEvent):
|
||||||
self.callback.event_handler(event=event, text="移动",
|
self.callback.event_handler(event=event, text="移动", event_path=Path(event.dest_path))
|
||||||
mon_path=self._watch_path, event_path=Path(event.dest_path))
|
|
||||||
|
|
||||||
|
|
||||||
class Monitor(metaclass=Singleton):
|
class Monitor(metaclass=Singleton):
|
||||||
@@ -80,15 +76,6 @@ class Monitor(metaclass=Singleton):
|
|||||||
# 存储过照间隔(分钟)
|
# 存储过照间隔(分钟)
|
||||||
_snapshot_interval = 5
|
_snapshot_interval = 5
|
||||||
|
|
||||||
# 待整理任务队列
|
|
||||||
_queue = Queue()
|
|
||||||
|
|
||||||
# 文件整理线程
|
|
||||||
_transfer_thread = None
|
|
||||||
|
|
||||||
# 文件整理间隔(秒)
|
|
||||||
_transfer_interval = 60
|
|
||||||
|
|
||||||
# 消息汇总
|
# 消息汇总
|
||||||
_msg_medias = {}
|
_msg_medias = {}
|
||||||
|
|
||||||
@@ -120,10 +107,6 @@ class Monitor(metaclass=Singleton):
|
|||||||
# 停止现有任务
|
# 停止现有任务
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
# 启动文件整理线程
|
|
||||||
self._transfer_thread = threading.Thread(target=self.__start_transfer, daemon=True)
|
|
||||||
self._transfer_thread.start()
|
|
||||||
|
|
||||||
# 读取目录配置
|
# 读取目录配置
|
||||||
monitor_dirs = self.directoryhelper.get_download_dirs()
|
monitor_dirs = self.directoryhelper.get_download_dirs()
|
||||||
if not monitor_dirs:
|
if not monitor_dirs:
|
||||||
@@ -212,16 +195,6 @@ class Monitor(metaclass=Singleton):
|
|||||||
logger.warn(f"导入模块错误:{error},将使用 PollingObserver 监控目录")
|
logger.warn(f"导入模块错误:{error},将使用 PollingObserver 监控目录")
|
||||||
return PollingObserver()
|
return PollingObserver()
|
||||||
|
|
||||||
def put_to_queue(self, storage: str, filepath: Path, mon_path: Path):
|
|
||||||
"""
|
|
||||||
添加到待整理队列
|
|
||||||
"""
|
|
||||||
self._queue.put({
|
|
||||||
"storage": storage,
|
|
||||||
"filepath": filepath,
|
|
||||||
"mon_path": mon_path
|
|
||||||
})
|
|
||||||
|
|
||||||
def polling_observer(self, storage: str, mon_path: Path):
|
def polling_observer(self, storage: str, mon_path: Path):
|
||||||
"""
|
"""
|
||||||
轮询监控
|
轮询监控
|
||||||
@@ -237,37 +210,95 @@ class Monitor(metaclass=Singleton):
|
|||||||
new_files = new_snapshot.keys() - old_snapshot.keys()
|
new_files = new_snapshot.keys() - old_snapshot.keys()
|
||||||
for new_file in new_files:
|
for new_file in new_files:
|
||||||
# 添加到待整理队列
|
# 添加到待整理队列
|
||||||
self.put_to_queue(storage=storage, filepath=Path(new_file), mon_path=mon_path)
|
self.__handle_file(storage=storage, event_path=Path(new_file))
|
||||||
# 更新快照
|
# 更新快照
|
||||||
self._storage_snapshot[storage] = new_snapshot
|
self._storage_snapshot[storage] = new_snapshot
|
||||||
|
|
||||||
def event_handler(self, event, mon_path: Path, text: str, event_path: Path):
|
def event_handler(self, event, text: str, event_path: Path):
|
||||||
"""
|
"""
|
||||||
处理文件变化
|
处理文件变化
|
||||||
:param event: 事件
|
:param event: 事件
|
||||||
:param mon_path: 监控目录
|
|
||||||
:param text: 事件描述
|
:param text: 事件描述
|
||||||
:param event_path: 事件文件路径
|
:param event_path: 事件文件路径
|
||||||
"""
|
"""
|
||||||
if not event.is_directory:
|
if not event.is_directory:
|
||||||
# 文件发生变化
|
# 文件发生变化
|
||||||
logger.debug(f"文件 {event_path} 发生了 {text}")
|
logger.debug(f"文件 {event_path} 发生了 {text}")
|
||||||
# 添加到待整理队列
|
# 整理文件
|
||||||
self.put_to_queue(storage="local", filepath=event_path, mon_path=mon_path)
|
self.__handle_file(storage="local", event_path=event_path)
|
||||||
|
|
||||||
def __start_transfer(self):
|
def __transfer_queue(self, task: TransferTask):
|
||||||
"""
|
"""
|
||||||
整理队列中的文件
|
添加到整理队列
|
||||||
"""
|
"""
|
||||||
while not self._event.is_set():
|
|
||||||
try:
|
def __callback(_task: TransferTask, _transferinfo: TransferInfo, /):
|
||||||
item = self._queue.get(timeout=self._transfer_interval)
|
"""
|
||||||
if item:
|
整理完成后处理
|
||||||
self.__handle_file(storage=item.get("storage"), event_path=item.get("filepath"))
|
"""
|
||||||
except queue.Empty:
|
if not _transferinfo:
|
||||||
continue
|
logger.error("文件转移模块运行失败")
|
||||||
except Exception as e:
|
return
|
||||||
logger.error(f"整理队列处理出现错误:{e}")
|
|
||||||
|
if not _transferinfo.success:
|
||||||
|
# 转移失败
|
||||||
|
logger.warn(f"{_task.file_path.name} 入库失败:{_transferinfo.message}")
|
||||||
|
# 新增转移失败历史记录
|
||||||
|
self.transferhis.add_fail(
|
||||||
|
fileitem=_task.fileitem,
|
||||||
|
mode=_transferinfo.transfer_type if _transferinfo else '',
|
||||||
|
download_hash=_task.download_hash,
|
||||||
|
meta=_task.meta,
|
||||||
|
mediainfo=_task.mediainfo,
|
||||||
|
transferinfo=_transferinfo
|
||||||
|
)
|
||||||
|
# 发送失败消息
|
||||||
|
self.chain.post_message(Notification(
|
||||||
|
mtype=NotificationType.Manual,
|
||||||
|
title=f"{_task.mediainfo.title_year} {_task.meta.season_episode} 入库失败!",
|
||||||
|
text=f"原因:{_transferinfo.message or '未知'}",
|
||||||
|
image=_task.mediainfo.get_message_image(),
|
||||||
|
link=settings.MP_DOMAIN('#/history')
|
||||||
|
))
|
||||||
|
return
|
||||||
|
|
||||||
|
# 转移成功
|
||||||
|
logger.info(f"{_task.file_path.name} 入库成功:{_transferinfo.target_diritem.path}")
|
||||||
|
# 新增转移成功历史记录
|
||||||
|
self.transferhis.add_success(
|
||||||
|
fileitem=_task.fileitem,
|
||||||
|
mode=_transferinfo.transfer_type if _transferinfo else '',
|
||||||
|
download_hash=_task.download_hash,
|
||||||
|
meta=_task.meta,
|
||||||
|
mediainfo=_task.mediainfo,
|
||||||
|
transferinfo=_transferinfo
|
||||||
|
)
|
||||||
|
|
||||||
|
# 汇总刮削
|
||||||
|
if _transferinfo.need_scrape:
|
||||||
|
self.mediaChain.scrape_metadata(fileitem=_transferinfo.target_diritem,
|
||||||
|
meta=_task.meta,
|
||||||
|
mediainfo=_task.mediainfo)
|
||||||
|
|
||||||
|
# 广播事件
|
||||||
|
EventManager().send_event(EventType.TransferComplete, {
|
||||||
|
'fileitem': _task.fileitem,
|
||||||
|
'meta': _task.meta,
|
||||||
|
'mediainfo': _task.mediainfo,
|
||||||
|
'transferinfo': _transferinfo
|
||||||
|
})
|
||||||
|
|
||||||
|
# 发送消息汇总
|
||||||
|
if _transferinfo.need_notify:
|
||||||
|
self.__collect_msg_medias(mediainfo=_task.mediainfo, file_meta=_task.meta,
|
||||||
|
transferinfo=_transferinfo)
|
||||||
|
|
||||||
|
# 移动模式删除空目录
|
||||||
|
if _transferinfo.transfer_type in ["move"]:
|
||||||
|
self.storagechain.delete_media_file(_task.fileitem, delete_self=False)
|
||||||
|
|
||||||
|
# 加入整理队列
|
||||||
|
self.transferchain.put_to_queue(task=task, callback=__callback)
|
||||||
|
|
||||||
def __handle_file(self, storage: str, event_path: Path):
|
def __handle_file(self, storage: str, event_path: Path):
|
||||||
"""
|
"""
|
||||||
@@ -342,8 +373,9 @@ class Monitor(metaclass=Singleton):
|
|||||||
download_history = self.downloadhis.get_by_hash(download_file.download_hash)
|
download_history = self.downloadhis.get_by_hash(download_file.download_hash)
|
||||||
|
|
||||||
# 获取下载Hash
|
# 获取下载Hash
|
||||||
download_hash = None
|
downloader, download_hash = None, None
|
||||||
if download_history:
|
if download_history:
|
||||||
|
downloader = download_history.downloader
|
||||||
download_hash = download_history.download_hash
|
download_hash = download_history.download_hash
|
||||||
|
|
||||||
# 识别媒体信息
|
# 识别媒体信息
|
||||||
@@ -413,73 +445,20 @@ class Monitor(metaclass=Singleton):
|
|||||||
else:
|
else:
|
||||||
episodes_info = None
|
episodes_info = None
|
||||||
|
|
||||||
# 转移
|
# 进入队列
|
||||||
transferinfo: TransferInfo = self.chain.transfer(fileitem=file_item,
|
self.__transfer_queue(
|
||||||
meta=file_meta,
|
TransferTask(
|
||||||
mediainfo=mediainfo,
|
|
||||||
target_directory=dir_info,
|
|
||||||
episodes_info=episodes_info)
|
|
||||||
|
|
||||||
if not transferinfo:
|
|
||||||
logger.error("文件转移模块运行失败")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not transferinfo.success:
|
|
||||||
# 转移失败
|
|
||||||
logger.warn(f"{event_path.name} 入库失败:{transferinfo.message}")
|
|
||||||
# 新增转移失败历史记录
|
|
||||||
self.transferhis.add_fail(
|
|
||||||
fileitem=file_item,
|
fileitem=file_item,
|
||||||
mode=transferinfo.transfer_type if transferinfo else '',
|
file_path=event_path,
|
||||||
download_hash=download_hash,
|
|
||||||
meta=file_meta,
|
meta=file_meta,
|
||||||
mediainfo=mediainfo,
|
mediainfo=mediainfo,
|
||||||
transferinfo=transferinfo
|
target_directory=dir_info,
|
||||||
|
episodes_info=episodes_info,
|
||||||
|
downloader=downloader,
|
||||||
|
download_hash=download_hash
|
||||||
)
|
)
|
||||||
# 发送失败消息
|
|
||||||
self.chain.post_message(Notification(
|
|
||||||
mtype=NotificationType.Manual,
|
|
||||||
title=f"{mediainfo.title_year} {file_meta.season_episode} 入库失败!",
|
|
||||||
text=f"原因:{transferinfo.message or '未知'}",
|
|
||||||
image=mediainfo.get_message_image(),
|
|
||||||
link=settings.MP_DOMAIN('#/history')
|
|
||||||
))
|
|
||||||
return
|
|
||||||
|
|
||||||
# 转移成功
|
|
||||||
logger.info(f"{event_path.name} 入库成功:{transferinfo.target_diritem.path}")
|
|
||||||
# 新增转移成功历史记录
|
|
||||||
self.transferhis.add_success(
|
|
||||||
fileitem=file_item,
|
|
||||||
mode=transferinfo.transfer_type if transferinfo else '',
|
|
||||||
download_hash=download_hash,
|
|
||||||
meta=file_meta,
|
|
||||||
mediainfo=mediainfo,
|
|
||||||
transferinfo=transferinfo
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# 汇总刮削
|
|
||||||
if transferinfo.need_scrape:
|
|
||||||
self.mediaChain.scrape_metadata(fileitem=transferinfo.target_diritem,
|
|
||||||
meta=file_meta,
|
|
||||||
mediainfo=mediainfo)
|
|
||||||
|
|
||||||
# 广播事件
|
|
||||||
EventManager().send_event(EventType.TransferComplete, {
|
|
||||||
'fileitem': file_item,
|
|
||||||
'meta': file_meta,
|
|
||||||
'mediainfo': mediainfo,
|
|
||||||
'transferinfo': transferinfo
|
|
||||||
})
|
|
||||||
|
|
||||||
# 发送消息汇总
|
|
||||||
if transferinfo.need_notify:
|
|
||||||
self.__collect_msg_medias(mediainfo=mediainfo, file_meta=file_meta, transferinfo=transferinfo)
|
|
||||||
|
|
||||||
# 移动模式删除空目录
|
|
||||||
if transferinfo.transfer_type in ["move"]:
|
|
||||||
self.storagechain.delete_media_file(file_item, delete_self=False)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("目录监控发生错误:%s - %s" % (str(e), traceback.format_exc()))
|
logger.error("目录监控发生错误:%s - %s" % (str(e), traceback.format_exc()))
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from typing import Optional, Dict, Any, List, Set
|
|||||||
|
|
||||||
from pydantic import BaseModel, Field, root_validator
|
from pydantic import BaseModel, Field, root_validator
|
||||||
|
|
||||||
from app.core.context import Context
|
|
||||||
from app.schemas import MessageChannel
|
from app.schemas import MessageChannel
|
||||||
|
|
||||||
|
|
||||||
@@ -169,7 +168,7 @@ class ResourceSelectionEventData(BaseModel):
|
|||||||
|
|
||||||
# 输出参数
|
# 输出参数
|
||||||
updated: bool = Field(default=False, description="是否已更新")
|
updated: bool = Field(default=False, description="是否已更新")
|
||||||
updated_contexts: Optional[List[Context]] = Field(default=None, description="已更新的资源上下文列表")
|
updated_contexts: Optional[List[Any]] = Field(default=None, description="已更新的资源上下文列表")
|
||||||
source: Optional[str] = Field(default="未知拦截源", description="拦截源")
|
source: Optional[str] = Field(default="未知拦截源", description="拦截源")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, List
|
from typing import Optional, List, Any, Callable
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from app.schemas import TmdbEpisode, MetaInfo, MediaInfo
|
from app.schemas import TmdbEpisode
|
||||||
from app.schemas.system import TransferDirectoryConf
|
|
||||||
from app.schemas.file import FileItem
|
from app.schemas.file import FileItem
|
||||||
|
from app.schemas.system import TransferDirectoryConf
|
||||||
|
|
||||||
|
|
||||||
class TransferTorrent(BaseModel):
|
class TransferTorrent(BaseModel):
|
||||||
@@ -47,8 +47,9 @@ class TransferTask(BaseModel):
|
|||||||
文件整理任务
|
文件整理任务
|
||||||
"""
|
"""
|
||||||
fileitem: Optional[FileItem] = None
|
fileitem: Optional[FileItem] = None
|
||||||
meta: Optional[MetaInfo] = None
|
file_path: Optional[Path] = None
|
||||||
mediainfo: Optional[MediaInfo] = None
|
meta: Optional[Any] = None
|
||||||
|
mediainfo: Optional[Any] = None
|
||||||
target_directory: Optional[TransferDirectoryConf] = None
|
target_directory: Optional[TransferDirectoryConf] = None
|
||||||
target_storage: Optional[str] = None
|
target_storage: Optional[str] = None
|
||||||
target_path: Optional[Path] = None
|
target_path: Optional[Path] = None
|
||||||
@@ -57,6 +58,8 @@ class TransferTask(BaseModel):
|
|||||||
library_type_folder: Optional[bool] = None
|
library_type_folder: Optional[bool] = None
|
||||||
library_category_folder: Optional[bool] = None
|
library_category_folder: Optional[bool] = None
|
||||||
episodes_info: Optional[List[TmdbEpisode]] = None
|
episodes_info: Optional[List[TmdbEpisode]] = None
|
||||||
|
downloader: Optional[str] = None
|
||||||
|
download_hash: Optional[str] = None
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
"""
|
"""
|
||||||
@@ -111,13 +114,15 @@ class TransferInfo(BaseModel):
|
|||||||
return dicts
|
return dicts
|
||||||
|
|
||||||
|
|
||||||
class AsyncTransferCallback(BaseModel):
|
class TransferQueue(BaseModel):
|
||||||
"""
|
"""
|
||||||
异步整理回调信息
|
异步整理队列信息
|
||||||
"""
|
"""
|
||||||
# 任务信息
|
# 任务信息
|
||||||
task: Optional[TransferTask] = None
|
task: Optional[TransferTask] = None
|
||||||
# 结果信息
|
# 回调函数
|
||||||
|
callback: Optional[Callable] = None
|
||||||
|
# 整理结果
|
||||||
result: Optional[TransferInfo] = None
|
result: Optional[TransferInfo] = None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from app.utils.system import SystemUtils
|
|||||||
try:
|
try:
|
||||||
from app.helper.sites import SitesHelper
|
from app.helper.sites import SitesHelper
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
|
SitesHelper = None
|
||||||
error_message = f"错误: {str(e)}\n站点认证及索引相关资源导入失败,请尝试重建容器或手动拉取资源"
|
error_message = f"错误: {str(e)}\n站点认证及索引相关资源导入失败,请尝试重建容器或手动拉取资源"
|
||||||
print(error_message, file=sys.stderr)
|
print(error_message, file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
29
database/versions/bf28a012734c_2_0_8.py
Normal file
29
database/versions/bf28a012734c_2_0_8.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"""2.0.8
|
||||||
|
|
||||||
|
Revision ID: bf28a012734c
|
||||||
|
Revises: eaf9cbc49027
|
||||||
|
Create Date: 2024-12-23 18:29:31.202143
|
||||||
|
|
||||||
|
"""
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'bf28a012734c'
|
||||||
|
down_revision = 'eaf9cbc49027'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
# 下载历史记录 增加下载器字段
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
op.add_column('downloadhistory', sa.Column('downloader', sa.String(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user