mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 03:56:43 +08:00
124 lines
4.4 KiB
Python
124 lines
4.4 KiB
Python
"""下载器模块业务样板基类。
|
|
|
|
沉淀三个内置下载器模块(qbittorrent/transmission/rtorrent)逐字复制的样板:
|
|
连接测试、定时重连、种子信息读取与查询状态归一。差异化逻辑
|
|
(任务添加、原始状态映射、任务列表构建)仍留在各模块。
|
|
"""
|
|
from pathlib import Path
|
|
from collections.abc import Callable
|
|
from typing import Any, List, Optional, Tuple, TypeVar, Union
|
|
|
|
from torrentool.torrent import Torrent
|
|
|
|
from app.domain import torrent as torrent_rules
|
|
from app.modules import _DownloaderBase, _ModuleBase, TService
|
|
from app.runtime.cache import FileCache
|
|
from app.runtime.log import logger
|
|
from app.schemas.types import TorrentQueryStatus, TorrentStatus
|
|
|
|
|
|
TFile = TypeVar("TFile")
|
|
|
|
|
|
class _DownloaderModuleBase(_ModuleBase, _DownloaderBase[TService]):
|
|
"""
|
|
下载器模块业务样板基类。
|
|
"""
|
|
|
|
def test(self) -> Optional[Tuple[bool, str]]:
|
|
"""
|
|
测试模块连接性
|
|
"""
|
|
if not self.get_instances():
|
|
return None
|
|
for name, server in self.get_instances().items():
|
|
if server.is_inactive():
|
|
server.reconnect()
|
|
if not server.transfer_info():
|
|
return False, f"无法连接{self.get_name()}下载器:{name}"
|
|
return True, ""
|
|
|
|
def scheduler_job(self) -> None:
|
|
"""
|
|
定时任务,每10分钟调用一次
|
|
"""
|
|
for name, server in self.get_instances().items():
|
|
if server.is_inactive():
|
|
logger.info(f"{self.get_name()}下载器 {name} 连接断开,尝试重连 ...")
|
|
server.reconnect()
|
|
|
|
@staticmethod
|
|
def _normalize_torrent_files(
|
|
files: Any, item_factory: Callable[[Any], TFile]
|
|
) -> Optional[List[TFile]]:
|
|
"""把 provider 文件集合统一投影为不依赖外部 SDK 的宿主 DTO。"""
|
|
if files is None:
|
|
return None
|
|
source = getattr(files, "data", files)
|
|
return [item_factory(item) for item in source]
|
|
|
|
def _get_torrent_info(self, content: Union[Path, str, bytes]) \
|
|
-> Tuple[Optional[Torrent], Optional[bytes]]:
|
|
"""
|
|
读取种子内容,返回解析后的种子信息与原始内容,磁力链接不解析
|
|
"""
|
|
torrent_info, torrent_content = None, None
|
|
try:
|
|
if isinstance(content, Path):
|
|
if content.exists():
|
|
torrent_content = content.read_bytes()
|
|
else:
|
|
# 读取缓存的种子文件
|
|
torrent_content = FileCache().get(
|
|
content.as_posix(), region="torrents"
|
|
)
|
|
else:
|
|
torrent_content = content
|
|
|
|
if torrent_content:
|
|
# 检查是否为磁力链接
|
|
if torrent_rules.is_magnet_link(torrent_content):
|
|
return None, torrent_content
|
|
else:
|
|
torrent_info = Torrent.from_string(torrent_content)
|
|
|
|
return torrent_info, torrent_content
|
|
except Exception as e:
|
|
logger.error(f"获取种子名称失败:{e}")
|
|
return None, None
|
|
|
|
@staticmethod
|
|
def _normalize_query_status(
|
|
status: Optional[Union[TorrentStatus, TorrentQueryStatus, str]]
|
|
) -> TorrentQueryStatus:
|
|
"""
|
|
归一任务查询状态。
|
|
"""
|
|
status_value = getattr(status, "value", status)
|
|
status_text = str(status_value or "").strip().lower()
|
|
if not status_text or status_text in {"all", "全部"}:
|
|
return TorrentQueryStatus.ALL
|
|
if status_text in {
|
|
TorrentStatus.TRANSFER.value,
|
|
TorrentQueryStatus.TRANSFER.value,
|
|
"transfer",
|
|
}:
|
|
return TorrentQueryStatus.TRANSFER
|
|
if status_text in {
|
|
TorrentStatus.DOWNLOADING.value,
|
|
TorrentQueryStatus.DOWNLOADING.value,
|
|
"downloading",
|
|
}:
|
|
return TorrentQueryStatus.DOWNLOADING
|
|
if status_text in {
|
|
TorrentQueryStatus.COMPLETED.value,
|
|
"complete",
|
|
"seeding",
|
|
"完成",
|
|
"已完成",
|
|
}:
|
|
return TorrentQueryStatus.COMPLETED
|
|
if status_text in {TorrentQueryStatus.PAUSED.value, "pause", "暂停", "已暂停"}:
|
|
return TorrentQueryStatus.PAUSED
|
|
return TorrentQueryStatus.ALL
|