diff --git a/app/api/endpoints/torrent.py b/app/api/endpoints/torrent.py index 7f6a51664..6bd8bcc2c 100644 --- a/app/api/endpoints/torrent.py +++ b/app/api/endpoints/torrent.py @@ -8,7 +8,7 @@ from app.api.dependencies.auth import ( ) from app.api.response import ResponseAPIRouter from app.application.configuration import get_api_runtime_config_snapshot -from app.application.torrent_cache import TorrentCacheRecognitionService +from app.application.torrent.cache import TorrentCacheRecognitionService from app.chain.media import MediaChain from app.chain.torrents import TorrentsChain from app.foundation.crypto import HashUtils diff --git a/app/application/torrent/__init__.py b/app/application/torrent/__init__.py new file mode 100644 index 000000000..d71aeca50 --- /dev/null +++ b/app/application/torrent/__init__.py @@ -0,0 +1 @@ +"""种子下载与缓存应用能力包;宿主直接依赖职责子模块。""" diff --git a/app/application/torrent_cache.py b/app/application/torrent/cache.py similarity index 100% rename from app/application/torrent_cache.py rename to app/application/torrent/cache.py index 614f2bc5c..357779fd7 100644 --- a/app/application/torrent_cache.py +++ b/app/application/torrent/cache.py @@ -3,10 +3,10 @@ from typing import Any, Optional from app.domain.context import MediaInfo, MusicInfo +from app.domain.media import is_music_media_source, normalize_music_type from app.domain.meta.metamusic import MetaMusic from app.domain.metainfo import MetaInfo from app.foundation.crypto import HashUtils -from app.domain.media import is_music_media_source, normalize_music_type from app.schemas.types import MUSIC_ENTITY_RECORDING, MediaSource, MediaType, MusicTargetEntityType diff --git a/app/application/torrent.py b/app/application/torrent/download.py similarity index 100% rename from app/application/torrent.py rename to app/application/torrent/download.py diff --git a/app/chain/_music.py b/app/chain/_music.py index 740ed1c9e..bfbf878e6 100644 --- a/app/chain/_music.py +++ b/app/chain/_music.py @@ -10,7 +10,7 @@ from app.application.subscription.contract import ( subscribe_media_key, ) from app.application.subscription.mutation import SubscriptionActor -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.chain._contracts import MusicSubscribeMixinHost from app.chain.download import DownloadChain from app.chain.media import MediaChain diff --git a/app/chain/download/batch.py b/app/chain/download/batch.py index f8ae8358d..292d6b886 100644 --- a/app/chain/download/batch.py +++ b/app/chain/download/batch.py @@ -4,7 +4,7 @@ import copy from typing import Callable, Dict, List, Optional, Set, Tuple, cast from app.application.download import selection as _selection -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.chain.download.contract import _DownloadOwnerBase from app.domain import episode as episode_rules from app.domain.context import ( diff --git a/app/chain/download/selection.py b/app/chain/download/selection.py index 061cf8e64..1db401615 100644 --- a/app/chain/download/selection.py +++ b/app/chain/download/selection.py @@ -7,7 +7,7 @@ from app.application.configuration import get_chain_runtime_config_snapshot from app.application.download.failures import ( DownloadFailureSnapshot, ) -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.chain.download.contract import _DownloadOwnerBase from app.domain.context import ( Context, diff --git a/app/chain/download/submission.py b/app/chain/download/submission.py index ab3a95a6d..b35297856 100644 --- a/app/chain/download/submission.py +++ b/app/chain/download/submission.py @@ -9,7 +9,7 @@ from urllib.parse import urlencode, urljoin, urlparse from app.application.configuration import get_chain_runtime_config_snapshot from app.application.directory import validate_download_save_path -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.chain.download.contract import _DownloadOwnerBase from app.chain.download.ports import ( _close_download_response, diff --git a/app/chain/download/subtitle.py b/app/chain/download/subtitle.py index 24ae3add1..426a54387 100644 --- a/app/chain/download/subtitle.py +++ b/app/chain/download/subtitle.py @@ -8,7 +8,7 @@ from typing import Any, List, Optional, Tuple, Union, cast from app.application.configuration import get_chain_runtime_config_snapshot from app.application.directory import DirectoryHelper, validate_download_save_path -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.chain.download.contract import _DownloadOwnerBase from app.chain.download.ports import ( DownloadArchivePort, diff --git a/app/chain/interaction.py b/app/chain/interaction.py index 9aa061e15..f62691630 100644 --- a/app/chain/interaction.py +++ b/app/chain/interaction.py @@ -7,7 +7,7 @@ from app.application.messaging.media import ( PendingMediaInteraction, media_interaction_manager, ) -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.chain.base import ChainBase from app.chain.download import DownloadChain from app.chain.media import MediaChain diff --git a/app/chain/search/music.py b/app/chain/search/music.py index f7e4793b9..86872a496 100644 --- a/app/chain/search/music.py +++ b/app/chain/search/music.py @@ -10,7 +10,7 @@ from unicodedata import normalize from app.application.configuration import ( get_configured_system_config, ) -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.chain.search.contract import _SearchOwnerBase as _SearchOwnerBase from app.domain.context import Context, MusicInfo, TorrentInfo from app.domain.meta.metamusic import MetaMusic diff --git a/app/chain/search/result.py b/app/chain/search/result.py index 79132ef96..f6f6e415e 100644 --- a/app/chain/search/result.py +++ b/app/chain/search/result.py @@ -4,7 +4,7 @@ import copy from typing import Callable, Dict, List, Optional, Tuple, cast from app.application.configuration import get_configured_system_config -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.chain.media import MediaChain from app.chain.search.contract import _SearchOwnerBase from app.domain.context import Context, MediaInfo, TorrentInfo diff --git a/app/chain/search/subtitle.py b/app/chain/search/subtitle.py index 7663a704f..b51e5111c 100644 --- a/app/chain/search/subtitle.py +++ b/app/chain/search/subtitle.py @@ -4,7 +4,7 @@ import asyncio import random from typing import Any, AsyncIterator, Dict, List, Optional, cast -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.chain.media import MediaChain from app.chain.search.contract import _SearchOwnerBase as _SearchOwnerBase from app.domain.context import MediaInfo, SubtitleInfo, TorrentInfo diff --git a/app/chain/subscribe/match.py b/app/chain/subscribe/match.py index 162b0433f..0fedc5888 100644 --- a/app/chain/subscribe/match.py +++ b/app/chain/subscribe/match.py @@ -6,7 +6,7 @@ from typing import Callable, Dict, List, Optional from app.application.configuration import get_configured_system_config from app.application.subscription.contract import build_subscribe_meta, subscribe_media_key -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.chain.media import MediaChain from app.chain.subscribe.contract import _SubscribeOwnerBase from app.chain.subscribe.identity import subscribe_recognize_kwargs diff --git a/app/chain/torrents.py b/app/chain/torrents.py index 380682b29..7cda327e3 100644 --- a/app/chain/torrents.py +++ b/app/chain/torrents.py @@ -6,7 +6,7 @@ from typing import Callable, Dict, List, Optional, Union from app.application.configuration import get_configured_system_config from app.application.rss import RssHelper from app.application.site.sites import SitesHelper # pylint: disable=import-error,no-name-in-module -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.chain.base import ChainBase from app.chain.media import MediaChain from app.domain import site as site_rules diff --git a/app/runtime/compat/manifest.py b/app/runtime/compat/manifest.py index 0302bc740..46070d847 100644 --- a/app/runtime/compat/manifest.py +++ b/app/runtime/compat/manifest.py @@ -619,7 +619,8 @@ MODULE_ALIASES: Dict[str, ModuleAlias] = { introduced="v3.0.0", owner="runtime", ), "app.helper.torrent": ModuleAlias( - target="app.application.torrent", replacement="app.application.torrent", + target="app.application.torrent.download", + replacement="app.application.torrent.download", introduced="v3.0.0", owner="application", ), "app.helper.transferhistory": ModuleAlias( diff --git a/app/startup/initializers/site.py b/app/startup/initializers/site.py index a225fe454..2cde74aef 100644 --- a/app/startup/initializers/site.py +++ b/app/startup/initializers/site.py @@ -25,7 +25,7 @@ from app.application.security.cookie import ( configure_cookie_ports, reset_cookie_ports, ) -from app.application.torrent import ( +from app.application.torrent.download import ( TorrentHttpPort, TorrentResponsePort, configure_torrent_port, diff --git a/app/workflow/actions/filter_torrents.py b/app/workflow/actions/filter_torrents.py index 245d81e8a..a96f0617b 100644 --- a/app/workflow/actions/filter_torrents.py +++ b/app/workflow/actions/filter_torrents.py @@ -2,7 +2,7 @@ from typing import List, Optional from pydantic import Field -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.runtime.log import logger from app.runtime.stop import runtime_stop_state from app.schemas.workflow import ActionContext, ActionParams diff --git a/docs/rules/05-architecture.md b/docs/rules/05-architecture.md index cec2d3a9e..6f9c10dc4 100644 --- a/docs/rules/05-architecture.md +++ b/docs/rules/05-architecture.md @@ -361,7 +361,7 @@ mentions media, site or torrent: | Media | `context.py` owns canonical `Context`, `MediaInfo` and `TorrentInfo` construction; `projection/` owns the pure, input-immutable TMDB/Douban/Bangumi/AniList field mappings and TMDB image-builder port; `media.py` owns source selection policy; identity representation/normalization stays in `schemas/media.py`; `title.py` owns title-candidate and search-keyword rules; `episode.py` owns episode-range display; `scraper.py` owns Kodi-style NFO reading and metadata document generation | | Recognition | `metainfo.py`, `meta/` and `tokens.py` parse names, paths, release groups, streaming platforms, anime, video and music metadata | | Site | `site.py` owns site-domain exceptions and interprets HTML into business states such as logged-in and checked-in; configured catalog/auth/index resources stay in `app/application/site/`, generic URL/DOM parsing stays in foundation and network access stays in adapters | -| Torrent | `torrent.py` owns magnet-link semantics; configured download/cache/file behavior stays in `app/application/torrent.py` | +| Torrent | `domain/torrent.py` owns magnet-link semantics; configured download/file behavior stays in `application/torrent/download.py`, while cache recognition stays in `application/torrent/cache.py` | `app/domain` may depend only on schemas and foundation. It must not read global settings, access DB/network/filesystem adapters, import Rust, discover services diff --git a/tests/fixtures/architecture/dependency-baseline.json b/tests/fixtures/architecture/dependency-baseline.json index 37c95f3bc..551c5880a 100644 --- a/tests/fixtures/architecture/dependency-baseline.json +++ b/tests/fixtures/architecture/dependency-baseline.json @@ -1089,8 +1089,8 @@ "runtime_only": true } }, - "edge_count": 8301, - "edge_sha256": "a1148b9576004423eb291429dc3e9a0e99282f14fe9d3d416539584a28aec8d0", + "edge_count": 8330, + "edge_sha256": "1a084d7f7953fed5f141f7bc6082442fffd69c0b71ad65a65b64e0454602c617", "edges": [ "app -> app.foundation", "app -> app.foundation.environment", @@ -3536,7 +3536,8 @@ "app.api.endpoints.torrent -> app.api.response", "app.api.endpoints.torrent -> app.application", "app.api.endpoints.torrent -> app.application.configuration", - "app.api.endpoints.torrent -> app.application.torrent_cache", + "app.api.endpoints.torrent -> app.application.torrent", + "app.api.endpoints.torrent -> app.application.torrent.cache", "app.api.endpoints.torrent -> app.chain", "app.api.endpoints.torrent -> app.chain.media", "app.api.endpoints.torrent -> app.chain.torrents", @@ -4222,36 +4223,36 @@ "app.application.subscription.write -> app.schemas.common", "app.application.subscription.write -> app.schemas.media", "app.application.subscription.write -> app.schemas.types", - "app.application.torrent -> app.application", - "app.application.torrent -> app.application.configuration", - "app.application.torrent -> app.application.site", - "app.application.torrent -> app.application.site.query", - "app.application.torrent -> app.domain", - "app.application.torrent -> app.domain.context", - "app.application.torrent -> app.domain.meta", - "app.application.torrent -> app.domain.meta.metabase", - "app.application.torrent -> app.domain.meta.metamusic", - "app.application.torrent -> app.domain.metainfo", - "app.application.torrent -> app.domain.torrent", - "app.application.torrent -> app.foundation", - "app.application.torrent -> app.foundation.crypto", - "app.application.torrent -> app.foundation.text", - "app.application.torrent -> app.runtime", - "app.application.torrent -> app.runtime.cache", - "app.application.torrent -> app.runtime.log", - "app.application.torrent -> app.schemas", - "app.application.torrent -> app.schemas.media", - "app.application.torrent -> app.schemas.types", - "app.application.torrent_cache -> app.domain", - "app.application.torrent_cache -> app.domain.context", - "app.application.torrent_cache -> app.domain.media", - "app.application.torrent_cache -> app.domain.meta", - "app.application.torrent_cache -> app.domain.meta.metamusic", - "app.application.torrent_cache -> app.domain.metainfo", - "app.application.torrent_cache -> app.foundation", - "app.application.torrent_cache -> app.foundation.crypto", - "app.application.torrent_cache -> app.schemas", - "app.application.torrent_cache -> app.schemas.types", + "app.application.torrent.cache -> app.domain", + "app.application.torrent.cache -> app.domain.context", + "app.application.torrent.cache -> app.domain.media", + "app.application.torrent.cache -> app.domain.meta", + "app.application.torrent.cache -> app.domain.meta.metamusic", + "app.application.torrent.cache -> app.domain.metainfo", + "app.application.torrent.cache -> app.foundation", + "app.application.torrent.cache -> app.foundation.crypto", + "app.application.torrent.cache -> app.schemas", + "app.application.torrent.cache -> app.schemas.types", + "app.application.torrent.download -> app.application", + "app.application.torrent.download -> app.application.configuration", + "app.application.torrent.download -> app.application.site", + "app.application.torrent.download -> app.application.site.query", + "app.application.torrent.download -> app.domain", + "app.application.torrent.download -> app.domain.context", + "app.application.torrent.download -> app.domain.meta", + "app.application.torrent.download -> app.domain.meta.metabase", + "app.application.torrent.download -> app.domain.meta.metamusic", + "app.application.torrent.download -> app.domain.metainfo", + "app.application.torrent.download -> app.domain.torrent", + "app.application.torrent.download -> app.foundation", + "app.application.torrent.download -> app.foundation.crypto", + "app.application.torrent.download -> app.foundation.text", + "app.application.torrent.download -> app.runtime", + "app.application.torrent.download -> app.runtime.cache", + "app.application.torrent.download -> app.runtime.log", + "app.application.torrent.download -> app.schemas", + "app.application.torrent.download -> app.schemas.media", + "app.application.torrent.download -> app.schemas.types", "app.application.transfer.workflow -> app.application", "app.application.transfer.workflow -> app.application.history", "app.application.transfer.workflow -> app.application.transfer", @@ -4310,6 +4311,7 @@ "app.chain._music -> app.application.subscription.contract", "app.chain._music -> app.application.subscription.mutation", "app.chain._music -> app.application.torrent", + "app.chain._music -> app.application.torrent.download", "app.chain._music -> app.chain", "app.chain._music -> app.chain._contracts", "app.chain._music -> app.chain.download", @@ -4399,6 +4401,7 @@ "app.chain.download.batch -> app.application.download", "app.chain.download.batch -> app.application.download.selection", "app.chain.download.batch -> app.application.torrent", + "app.chain.download.batch -> app.application.torrent.download", "app.chain.download.batch -> app.chain", "app.chain.download.batch -> app.chain.download", "app.chain.download.batch -> app.chain.download.contract", @@ -4490,6 +4493,7 @@ "app.chain.download.selection -> app.application.download", "app.chain.download.selection -> app.application.download.failures", "app.chain.download.selection -> app.application.torrent", + "app.chain.download.selection -> app.application.torrent.download", "app.chain.download.selection -> app.chain", "app.chain.download.selection -> app.chain.download", "app.chain.download.selection -> app.chain.download.contract", @@ -4510,6 +4514,7 @@ "app.chain.download.submission -> app.application.configuration", "app.chain.download.submission -> app.application.directory", "app.chain.download.submission -> app.application.torrent", + "app.chain.download.submission -> app.application.torrent.download", "app.chain.download.submission -> app.chain", "app.chain.download.submission -> app.chain.download", "app.chain.download.submission -> app.chain.download.contract", @@ -4531,6 +4536,7 @@ "app.chain.download.subtitle -> app.application.configuration", "app.chain.download.subtitle -> app.application.directory", "app.chain.download.subtitle -> app.application.torrent", + "app.chain.download.subtitle -> app.application.torrent.download", "app.chain.download.subtitle -> app.chain", "app.chain.download.subtitle -> app.chain.download", "app.chain.download.subtitle -> app.chain.download.contract", @@ -4566,6 +4572,7 @@ "app.chain.interaction -> app.application.messaging", "app.chain.interaction -> app.application.messaging.media", "app.chain.interaction -> app.application.torrent", + "app.chain.interaction -> app.application.torrent.download", "app.chain.interaction -> app.chain", "app.chain.interaction -> app.chain.base", "app.chain.interaction -> app.chain.download", @@ -4905,6 +4912,7 @@ "app.chain.search.music -> app.application", "app.chain.search.music -> app.application.configuration", "app.chain.search.music -> app.application.torrent", + "app.chain.search.music -> app.application.torrent.download", "app.chain.search.music -> app.chain", "app.chain.search.music -> app.chain.search", "app.chain.search.music -> app.chain.search.contract", @@ -4968,6 +4976,7 @@ "app.chain.search.result -> app.application", "app.chain.search.result -> app.application.configuration", "app.chain.search.result -> app.application.torrent", + "app.chain.search.result -> app.application.torrent.download", "app.chain.search.result -> app.chain", "app.chain.search.result -> app.chain.media", "app.chain.search.result -> app.chain.search", @@ -4995,6 +5004,7 @@ "app.chain.search.site -> app.schemas.types", "app.chain.search.subtitle -> app.application", "app.chain.search.subtitle -> app.application.torrent", + "app.chain.search.subtitle -> app.application.torrent.download", "app.chain.search.subtitle -> app.chain", "app.chain.search.subtitle -> app.chain.media", "app.chain.search.subtitle -> app.chain.search", @@ -5157,6 +5167,7 @@ "app.chain.subscribe.match -> app.application.subscription", "app.chain.subscribe.match -> app.application.subscription.contract", "app.chain.subscribe.match -> app.application.torrent", + "app.chain.subscribe.match -> app.application.torrent.download", "app.chain.subscribe.match -> app.chain", "app.chain.subscribe.match -> app.chain.media", "app.chain.subscribe.match -> app.chain.subscribe", @@ -5317,6 +5328,7 @@ "app.chain.torrents -> app.application.rss", "app.chain.torrents -> app.application.site", "app.chain.torrents -> app.application.torrent", + "app.chain.torrents -> app.application.torrent.download", "app.chain.torrents -> app.chain", "app.chain.torrents -> app.chain.base", "app.chain.torrents -> app.chain.media", @@ -6303,10 +6315,6 @@ "app.modules -> app.schemas.message", "app.modules -> app.schemas.system", "app.modules -> app.schemas.types", - "app.modules._base -> app.modules", - "app.modules._base -> app.modules._base.downloader", - "app.modules._base -> app.modules._base.mediaserver", - "app.modules._base -> app.modules._base.notification", "app.modules._base.downloader -> app.domain", "app.modules._base.downloader -> app.domain.torrent", "app.modules._base.downloader -> app.modules", @@ -6413,6 +6421,7 @@ "app.modules.dingtalk -> app.domain.context", "app.modules.dingtalk -> app.modules", "app.modules.dingtalk -> app.modules._base", + "app.modules.dingtalk -> app.modules._base.notification", "app.modules.dingtalk -> app.modules.dingtalk.dingtalk", "app.modules.dingtalk -> app.schemas", "app.modules.dingtalk -> app.schemas.message", @@ -6432,6 +6441,7 @@ "app.modules.discord -> app.domain.context", "app.modules.discord -> app.modules", "app.modules.discord -> app.modules._base", + "app.modules.discord -> app.modules._base.notification", "app.modules.discord -> app.modules.discord.discord", "app.modules.discord -> app.runtime", "app.modules.discord -> app.runtime.log", @@ -6496,6 +6506,7 @@ "app.modules.douban.scraper -> app.schemas.types", "app.modules.emby -> app.modules", "app.modules.emby -> app.modules._base", + "app.modules.emby -> app.modules._base.mediaserver", "app.modules.emby -> app.modules.emby.emby", "app.modules.emby -> app.runtime", "app.modules.emby -> app.runtime.log", @@ -6537,6 +6548,7 @@ "app.modules.feishu -> app.domain.context", "app.modules.feishu -> app.modules", "app.modules.feishu -> app.modules._base", + "app.modules.feishu -> app.modules._base.notification", "app.modules.feishu -> app.modules.feishu.feishu", "app.modules.feishu -> app.runtime", "app.modules.feishu -> app.runtime.log", @@ -7100,6 +7112,7 @@ "app.modules.indexer.spider.yema -> app.schemas.types", "app.modules.jellyfin -> app.modules", "app.modules.jellyfin -> app.modules._base", + "app.modules.jellyfin -> app.modules._base.mediaserver", "app.modules.jellyfin -> app.modules.jellyfin.jellyfin", "app.modules.jellyfin -> app.runtime", "app.modules.jellyfin -> app.runtime.log", @@ -7222,6 +7235,7 @@ "app.modules.plex -> app.domain.context", "app.modules.plex -> app.modules", "app.modules.plex -> app.modules._base", + "app.modules.plex -> app.modules._base.mediaserver", "app.modules.plex -> app.modules.plex.plex", "app.modules.plex -> app.runtime", "app.modules.plex -> app.runtime.events", @@ -7260,6 +7274,7 @@ "app.modules.qbittorrent -> app.foundation.text", "app.modules.qbittorrent -> app.modules", "app.modules.qbittorrent -> app.modules._base", + "app.modules.qbittorrent -> app.modules._base.downloader", "app.modules.qbittorrent -> app.modules.qbittorrent.qbittorrent", "app.modules.qbittorrent -> app.runtime", "app.modules.qbittorrent -> app.runtime.log", @@ -7291,6 +7306,7 @@ "app.modules.qqbot.module -> app.domain.context", "app.modules.qqbot.module -> app.modules", "app.modules.qqbot.module -> app.modules._base", + "app.modules.qqbot.module -> app.modules._base.notification", "app.modules.qqbot.module -> app.modules.qqbot", "app.modules.qqbot.module -> app.modules.qqbot.qqbot", "app.modules.qqbot.module -> app.runtime", @@ -7334,6 +7350,7 @@ "app.modules.rtorrent -> app.foundation.text", "app.modules.rtorrent -> app.modules", "app.modules.rtorrent -> app.modules._base", + "app.modules.rtorrent -> app.modules._base.downloader", "app.modules.rtorrent -> app.modules.rtorrent.rtorrent", "app.modules.rtorrent -> app.runtime", "app.modules.rtorrent -> app.runtime.log", @@ -7351,6 +7368,7 @@ "app.modules.slack -> app.domain.context", "app.modules.slack -> app.modules", "app.modules.slack -> app.modules._base", + "app.modules.slack -> app.modules._base.notification", "app.modules.slack -> app.modules.slack.slack", "app.modules.slack -> app.runtime", "app.modules.slack -> app.runtime.log", @@ -7397,6 +7415,7 @@ "app.modules.synologychat -> app.domain.context", "app.modules.synologychat -> app.modules", "app.modules.synologychat -> app.modules._base", + "app.modules.synologychat -> app.modules._base.notification", "app.modules.synologychat -> app.modules.synologychat.synologychat", "app.modules.synologychat -> app.runtime", "app.modules.synologychat -> app.runtime.log", @@ -7422,6 +7441,7 @@ "app.modules.telegram.module -> app.domain.context", "app.modules.telegram.module -> app.modules", "app.modules.telegram.module -> app.modules._base", + "app.modules.telegram.module -> app.modules._base.notification", "app.modules.telegram.module -> app.modules.telegram", "app.modules.telegram.module -> app.modules.telegram.telegram", "app.modules.telegram.module -> app.runtime", @@ -7703,6 +7723,7 @@ "app.modules.transmission -> app.foundation.temporal", "app.modules.transmission -> app.modules", "app.modules.transmission -> app.modules._base", + "app.modules.transmission -> app.modules._base.downloader", "app.modules.transmission -> app.modules.transmission.transmission", "app.modules.transmission -> app.runtime", "app.modules.transmission -> app.runtime.log", @@ -7723,6 +7744,7 @@ "app.modules.trimemedia.api -> app.runtime.settings", "app.modules.trimemedia.module -> app.modules", "app.modules.trimemedia.module -> app.modules._base", + "app.modules.trimemedia.module -> app.modules._base.mediaserver", "app.modules.trimemedia.module -> app.modules.trimemedia", "app.modules.trimemedia.module -> app.modules.trimemedia.trimemedia", "app.modules.trimemedia.module -> app.runtime", @@ -7758,6 +7780,7 @@ "app.modules.ugreen.api -> app.runtime.log", "app.modules.ugreen.module -> app.modules", "app.modules.ugreen.module -> app.modules._base", + "app.modules.ugreen.module -> app.modules._base.mediaserver", "app.modules.ugreen.module -> app.modules.ugreen", "app.modules.ugreen.module -> app.modules.ugreen.ugreen", "app.modules.ugreen.module -> app.runtime", @@ -7787,6 +7810,7 @@ "app.modules.vocechat -> app.domain.context", "app.modules.vocechat -> app.modules", "app.modules.vocechat -> app.modules._base", + "app.modules.vocechat -> app.modules._base.notification", "app.modules.vocechat -> app.modules.vocechat.vocechat", "app.modules.vocechat -> app.runtime", "app.modules.vocechat -> app.runtime.log", @@ -7825,6 +7849,7 @@ "app.modules.wechat -> app.foundation.dom", "app.modules.wechat -> app.modules", "app.modules.wechat -> app.modules._base", + "app.modules.wechat -> app.modules._base.notification", "app.modules.wechat -> app.modules.wechat.wechat", "app.modules.wechat -> app.modules.wechat.wechatbot", "app.modules.wechat -> app.runtime", @@ -7871,6 +7896,7 @@ "app.modules.wechatclawbot -> app.domain.context", "app.modules.wechatclawbot -> app.modules", "app.modules.wechatclawbot -> app.modules._base", + "app.modules.wechatclawbot -> app.modules._base.notification", "app.modules.wechatclawbot -> app.modules.wechatclawbot.wechatclawbot", "app.modules.wechatclawbot -> app.runtime", "app.modules.wechatclawbot -> app.runtime.cache", @@ -7895,6 +7921,7 @@ "app.modules.wechatclawbot.wechatclawbot -> app.runtime.settings", "app.modules.zspace -> app.modules", "app.modules.zspace -> app.modules._base", + "app.modules.zspace -> app.modules._base.mediaserver", "app.modules.zspace -> app.modules.zspace.zspace", "app.modules.zspace -> app.runtime", "app.modules.zspace -> app.runtime.log", @@ -9149,6 +9176,7 @@ "app.startup.initializers.site -> app.application.security", "app.startup.initializers.site -> app.application.security.cookie", "app.startup.initializers.site -> app.application.torrent", + "app.startup.initializers.site -> app.application.torrent.download", "app.startup.initializers.site -> app.chain", "app.startup.initializers.site -> app.chain.site", "app.startup.initializers.transfer -> app.chain", @@ -9326,6 +9354,7 @@ "app.workflow.actions.filter_medias -> app.workflow.actions", "app.workflow.actions.filter_torrents -> app.application", "app.workflow.actions.filter_torrents -> app.application.torrent", + "app.workflow.actions.filter_torrents -> app.application.torrent.download", "app.workflow.actions.filter_torrents -> app.runtime", "app.workflow.actions.filter_torrents -> app.runtime.log", "app.workflow.actions.filter_torrents -> app.runtime.stop", @@ -9394,7 +9423,7 @@ "app.workflow.actions.transfer_file -> app.workflow", "app.workflow.actions.transfer_file -> app.workflow.actions" ], - "module_count": 972, + "module_count": 973, "modules": [ "app", "app.adapters", @@ -9754,7 +9783,8 @@ "app.application.subscription.search", "app.application.subscription.write", "app.application.torrent", - "app.application.torrent_cache", + "app.application.torrent.cache", + "app.application.torrent.download", "app.application.transfer", "app.application.transfer.execution", "app.application.transfer.workflow", diff --git a/tests/fixtures/architecture/filename-policy.json b/tests/fixtures/architecture/filename-policy.json index 19204db2e..0b11474b3 100644 --- a/tests/fixtures/architecture/filename-policy.json +++ b/tests/fixtures/architecture/filename-policy.json @@ -104,7 +104,6 @@ "app/agent/tools/impl/write_file.py", "app/api/openai_utils.py", "app/api/router_specs.py", - "app/application/torrent_cache.py", "app/chain/_contracts.py", "app/chain/_interaction.py", "app/chain/_messaging.py", diff --git a/tests/fixtures/architecture/mypy-baseline.json b/tests/fixtures/architecture/mypy-baseline.json index 8a73561ad..c815cf2f5 100644 --- a/tests/fixtures/architecture/mypy-baseline.json +++ b/tests/fixtures/architecture/mypy-baseline.json @@ -1225,7 +1225,7 @@ "app/application/subscription/write.py": { "arg-type": 2 }, - "app/application/torrent.py": { + "app/application/torrent/download.py": { "arg-type": 2, "assignment": 3, "attr-defined": 1, @@ -1237,7 +1237,7 @@ "union-attr": 4, "var-annotated": 1 }, - "app/application/torrent_cache.py": { + "app/application/torrent/cache.py": { "type-arg": 1 }, "app/chain/_interaction.py": { diff --git a/tests/fixtures/architecture/ruff-baseline.json b/tests/fixtures/architecture/ruff-baseline.json index f5bb2b84b..c48923e0a 100644 --- a/tests/fixtures/architecture/ruff-baseline.json +++ b/tests/fixtures/architecture/ruff-baseline.json @@ -216,9 +216,6 @@ "app/application/storage.py": { "I001": 1 }, - "app/application/torrent_cache.py": { - "I001": 1 - }, "app/db/__init__.py": { "I001": 1 }, diff --git a/tests/fixtures/architecture/runtime-contract-baseline.json b/tests/fixtures/architecture/runtime-contract-baseline.json index c204b9f7a..9897de395 100644 --- a/tests/fixtures/architecture/runtime-contract-baseline.json +++ b/tests/fixtures/architecture/runtime-contract-baseline.json @@ -565,8 +565,8 @@ "introduced": "v3.0.0", "is_package": false, "owner": "application", - "replacement": "app.application.torrent", - "target": "app.application.torrent" + "replacement": "app.application.torrent.download", + "target": "app.application.torrent.download" }, "app.helper.transferhistory": { "introduced": "v3.0.0", diff --git a/tests/test_architecture_adapter_imports.py b/tests/test_architecture_adapter_imports.py index b605aa7cb..245cfea0c 100644 --- a/tests/test_architecture_adapter_imports.py +++ b/tests/test_architecture_adapter_imports.py @@ -35,7 +35,7 @@ FROZEN_DIRECT_ADAPTER_IMPORTS = { ("app.application.security.cookie", "app.adapters.external.ocr"): "S2-L6", ("app.application.security.cookie", "app.adapters.network.browser"): "S2-L6", ("app.application.security.cookie", "app.adapters.network.http"): "S2-L6", - ("app.application.torrent", "app.adapters.network.http"): "S2-L6", + ("app.application.torrent.download", "app.adapters.network.http"): "S2-L6", ("app.application.transfer.workflow", "app.adapters.system.host"): "S2-L6", } diff --git a/tests/test_architecture_dependencies.py b/tests/test_architecture_dependencies.py index bc37e1e33..d8af7768f 100644 --- a/tests/test_architecture_dependencies.py +++ b/tests/test_architecture_dependencies.py @@ -84,6 +84,8 @@ RETIRED_CANONICAL_FILES = ( "app/adapters/network/sites.pyi", "app/application/plugins.py", "app/application/subscribe.py", + "app/application/torrent.py", + "app/application/torrent_cache.py", "app/application/chain/durable_events.py", "app/application/transfer.py", "app/application/transfer_execution.py", @@ -363,6 +365,34 @@ def test_runtime_dependencies_use_same_named_single_word_package() -> None: assert violations == [] +def test_application_torrent_uses_same_named_single_word_package() -> None: + """种子应用能力必须归入同名包,包根不得重复导出内部实现。""" + package = APP_ROOT / "application" / "torrent" + assert {path.name for path in package.glob("*.py")} == { + "__init__.py", + "cache.py", + "download.py", + } + init_tree = ast.parse( + (package / "__init__.py").read_text(encoding="utf-8"), + filename=str(package / "__init__.py"), + ) + assert ast.get_docstring(init_tree) + assert len(init_tree.body) == 1 + + root_imports: list[str] = [] + for path in APP_ROOT.rglob("*.py"): + if path.is_relative_to(APP_ROOT / "plugins"): + continue + tree = ast.parse(path.read_text(encoding="utf-8-sig"), filename=str(path)) + for node in ast.walk(tree): + if isinstance(node, ast.ImportFrom) and node.module == "app.application.torrent": + root_imports.append( + f"{path.relative_to(PROJECT_ROOT).as_posix()}:{node.lineno}" + ) + assert root_imports == [] + + def test_host_module_package_roots_only_export_capability_entrypoints() -> None: """宿主模块包根只公开 capability entrypoint,内部 owner 必须直达子模块。""" base_path = APP_ROOT / "modules" / "_base" / "__init__.py" diff --git a/tests/test_metainfo.py b/tests/test_metainfo.py index b83a2dce8..edb34f736 100644 --- a/tests/test_metainfo.py +++ b/tests/test_metainfo.py @@ -16,7 +16,7 @@ from app.domain.meta.runtime import ( get_media_extensions, get_metainfo_accelerator, ) -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.schemas.types import MediaSource, MediaType from tests.cases.meta import meta_cases diff --git a/tests/test_site_access_ports.py b/tests/test_site_access_ports.py index 1b05ab272..ad69af1d9 100644 --- a/tests/test_site_access_ports.py +++ b/tests/test_site_access_ports.py @@ -5,12 +5,12 @@ from types import SimpleNamespace import pytest +import app.application.torrent.download as torrent_module from app.application import rss as rss_module -from app.application import torrent as torrent_module from app.application.rss import RssHelper, configure_rss_ports from app.application.security import cookie as cookie_module from app.application.security.cookie import CookieHelper, configure_cookie_ports -from app.application.torrent import ( +from app.application.torrent.download import ( TorrentHelper, configure_torrent_port, ) diff --git a/tests/test_site_chain_ports.py b/tests/test_site_chain_ports.py index 97d2a2338..bff050830 100644 --- a/tests/test_site_chain_ports.py +++ b/tests/test_site_chain_ports.py @@ -9,9 +9,9 @@ from typing import Iterator, Optional import pytest +import app.application.torrent.download as torrent_module from app.adapters.external import cookiecloud as cookiecloud_module from app.application import rss as rss_module -from app.application import torrent as torrent_module from app.application.security import cookie as cookie_module from app.application.site.contract import SiteSnapshot from app.chain import site as site_module diff --git a/tests/test_subscribe_chain.py b/tests/test_subscribe_chain.py index 0f00368cb..f5aaf1f9f 100644 --- a/tests/test_subscribe_chain.py +++ b/tests/test_subscribe_chain.py @@ -400,7 +400,10 @@ def _load_subscribe_chain_class(): ) helper_mediaserver_module.MediaServerHelper = type("MediaServerHelper", (), {}) - helper_torrent_module = ensure_module("app.application.torrent", types.ModuleType("app.application.torrent")) + helper_torrent_module = ensure_module( + "app.application.torrent.download", + types.ModuleType("app.application.torrent.download"), + ) helper_torrent_module.TorrentHelper = type("TorrentHelper", (), {}) db_model_module = ensure_module("app.db.models.subscribe", types.ModuleType("app.db.models.subscribe")) @@ -490,8 +493,8 @@ def _load_subscribe_chain_class(): import app.startup.initializers.chain as chain_initializer from app.application.messaging.subscribe import SubscribeInteractionHandler from app.application.subscription.contract import SubscriptionSnapshot, build_subscribe_meta - from app.chain.subscribe import SubscribeChain as ProductionSubscribeChain from app.chain.subscribe.context import _SubscribeCreateContext + from app.chain.subscribe.facade import SubscribeChain as ProductionSubscribeChain class SubscribeChain(ProductionSubscribeChain): """保留存量单元测试按调用时替换 repository 的隔离接缝。""" diff --git a/tests/test_torrent_filter.py b/tests/test_torrent_filter.py index d1fb9e7dc..cd20c14a1 100644 --- a/tests/test_torrent_filter.py +++ b/tests/test_torrent_filter.py @@ -3,7 +3,7 @@ from types import SimpleNamespace from unittest.mock import patch from app.domain.context import MediaInfo, TorrentInfo -from app.application.torrent import TorrentHelper +from app.application.torrent.download import TorrentHelper from app.modules.filter import FilterModule from app.application.rules import BUILTIN_RULE_SET from app.adapters.system import rust as rust_accel