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 清单校验排除下划线内部目录
This commit is contained in:
jxxghp
2026-08-16 16:30:16 +08:00
parent 24671f8f18
commit 7e851dbfa7
102 changed files with 6041 additions and 5888 deletions
+4 -92
View File
@@ -1,14 +1,11 @@
from pathlib import Path
from typing import Set, Tuple, Optional, Union, List, Dict
from torrentool.torrent import Torrent
from app import schemas
from app.runtime.cache import FileCache
from app.runtime.config import settings
from app.domain.metainfo import MetaInfo
from app.runtime.log import logger
from app.modules import _ModuleBase, _DownloaderBase
from app.modules._base import _DownloaderModuleBase
from app.modules.rtorrent.rtorrent import Rtorrent
from app.schemas import DownloaderTorrent
from app.schemas.types import (
@@ -18,13 +15,12 @@ from app.schemas.types import (
TorrentQueryStatus,
TorrentStatus,
)
from app.domain import torrent as torrent_rules
from app.foundation import size as size_tools
from app.foundation import temporal as time_tools
from app.foundation import text as text_tools
class RtorrentModule(_ModuleBase, _DownloaderBase[Rtorrent]):
class RtorrentModule(_DownloaderModuleBase[Rtorrent]):
def init_module(self) -> None:
"""
初始化模块
@@ -61,31 +57,9 @@ class RtorrentModule(_ModuleBase, _DownloaderBase[Rtorrent]):
def stop(self):
pass
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"无法连接rTorrent下载器:{name}"
return True, ""
def init_setting(self) -> Tuple[str, Union[str, bool]]:
pass
def scheduler_job(self) -> None:
"""
定时任务,每10分钟调用一次
"""
for name, server in self.get_instances().items():
if server.is_inactive():
logger.info(f"rTorrent下载器 {name} 连接断开,尝试重连 ...")
server.reconnect()
def download(
self,
content: Union[Path, str, bytes],
@@ -108,38 +82,11 @@ class RtorrentModule(_ModuleBase, _DownloaderBase[Rtorrent]):
:return: 下载器名称、种子Hash、种子文件布局、错误原因
"""
def __get_torrent_info() -> 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
if not content:
return None, None, None, "下载内容为空"
# 读取种子的名称
torrent_from_file, content = __get_torrent_info()
torrent_from_file, content = self._get_torrent_info(content)
# 检查是否为磁力链接
is_magnet = (
isinstance(content, str)
@@ -311,7 +258,7 @@ class RtorrentModule(_ModuleBase, _DownloaderBase[Rtorrent]):
else:
servers: Dict[str, Rtorrent] = self.get_instances()
ret_torrents = []
query_status = self.__normalize_query_status(status)
query_status = self._normalize_query_status(status)
query_tags = None if include_all_tags else settings.TORRENT_TAG
def __get_torrent_path(torrent_data: dict) -> Path:
@@ -424,41 +371,6 @@ class RtorrentModule(_ModuleBase, _DownloaderBase[Rtorrent]):
return None
return ret_torrents # noqa
@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
@staticmethod
def __normalize_torrent_state(
state: Optional[Union[int, str]],