Files
MoviePilot/app/modules/_base/downloader.py
T
jxxghp 7e851dbfa7 refactor(chain): 处理链功能域 mixin 化,清理未使用导入并根治兼容层循环导入
- ChainBase 拆分为 RecognitionMixin/MessageProcessingMixin/NotificationMixin
- TransferChain 拆分为 7 个功能 mixin(_mixins.py),SubscribeChain 音乐订阅域拆出 _music.py
- 斜杠命令交互四件套收敛为 InteractionChainMixin 委托,会话管理器移至 application 层,chain 层不再 re-export
- 模块基础类收敛到 app/modules/_base(notification/mediaserver 语义重命名)
- 清理 app/chain/__init__.py 24 个未使用导入,修正 49 处测试 patch 目标到实际命名空间
- 兼容层 legacy 符号不再并入 __all__,根治 schemas 初始化反向拉起 application.transfer 的循环导入
- 修复 bangumi 集数为字符串时 set_bangumi_info 抛 TypeError
- 新增重复代码等架构门禁测试;capability 清单校验排除下划线内部目录
2026-08-16 16:30:16 +08:00

110 lines
3.9 KiB
Python

"""下载器模块业务样板基类。
沉淀三个内置下载器模块(qbittorrent/transmission/rtorrent)逐字复制的样板:
连接测试、定时重连、种子信息读取与查询状态归一。差异化逻辑
(任务添加、原始状态映射、任务列表构建)仍留在各模块。
"""
from pathlib import Path
from typing import Optional, Tuple, 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
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()
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