From 7611cc8ea854c728c10fee518619800f21d48154 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sat, 22 Aug 2026 21:38:11 +0800 Subject: [PATCH] refactor: isolate transfer identity stage --- app/chain/transfer.py | 147 +++++++++++++++--- .../backend-architecture-next-stage.md | 3 + .../architecture/complexity-baseline.json | 3 +- 3 files changed, 128 insertions(+), 25 deletions(-) diff --git a/app/chain/transfer.py b/app/chain/transfer.py index 4d040928b..eb0ac1a7f 100755 --- a/app/chain/transfer.py +++ b/app/chain/transfer.py @@ -1606,6 +1606,59 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo return shared_roots + @staticmethod + def _normalize_transfer_identity( + mediainfo: Optional[Union[MediaInfo, MusicInfo]], + mtype: Optional[MediaType], + media_source: Optional[MediaSource], + media_id: Optional[str], + meta: Optional[MetaBase], + ) -> Tuple[ + Optional[Union[MediaInfo, MusicInfo]], + Optional[MediaSource], + Optional[str], + Optional[str], + ]: + """ + 规范整理请求的媒体身份,并在显式身份缺失时短路。 + + :return: ``(媒体信息、媒体来源、媒体 ID、错误信息)``;错误信息为空表示可继续执行 + """ + explicit_identity = media_source is not None or media_id is not None + normalized_source, normalized_media_id = resolve_media_identity( + media_source=media_source, + media_id=media_id, + ) + if explicit_identity and ( + not normalized_source or not normalized_media_id + ): + return ( + mediainfo, + normalized_source, + normalized_media_id, + "整理任务需要同时提供有效的 media_source 和 media_id", + ) + if not explicit_identity and mediainfo: + normalized_source, normalized_media_id = resolve_media_identity( + media=mediainfo + ) + if explicit_identity and not mediainfo: + mediainfo = MediaChain().recognize_media( + mtype=mtype, + media_source=normalized_source, + media_id=normalized_media_id, + music_type=getattr(meta, "music_type", None), + ) + if not mediainfo: + return ( + mediainfo, + normalized_source, + normalized_media_id, + "未识别到媒体信息," + f"media_source:{normalized_source},media_id:{normalized_media_id}", + ) + return mediainfo, normalized_source, normalized_media_id, None + def do_transfer( self, fileitem: FileItem, @@ -1634,6 +1687,70 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo cleanup_dest_fileitem: Optional[FileItem] = None, continue_callback: Callable = None, reorganize: Optional[bool] = False, + ) -> Tuple[bool, Union[str, dict]]: + """ + 兼容公开整理入口,委托给内部批次执行阶段。 + + 公开签名是 API、工作流、监控器和插件共同使用的稳定契约;具体整理阶段保留在 + 内部方法中,后续可以独立拆分规划、执行和结算,而不迫使调用方迁移参数。 + """ + return self._execute_transfer( + fileitem=fileitem, + meta=meta, + mediainfo=mediainfo, + mtype=mtype, + media_source=media_source, + media_id=media_id, + target_directory=target_directory, + target_storage=target_storage, + target_path=target_path, + transfer_type=transfer_type, + scrape=scrape, + library_type_folder=library_type_folder, + library_category_folder=library_category_folder, + season=season, + epformat=epformat, + min_filesize=min_filesize, + downloader=downloader, + download_hash=download_hash, + force=force, + background=background, + manual=manual, + preview=preview, + sync_extra_files=sync_extra_files, + cleanup_dest_fileitem=cleanup_dest_fileitem, + continue_callback=continue_callback, + reorganize=reorganize, + ) + + def _execute_transfer( + self, + fileitem: FileItem, + meta: MetaBase = None, + mediainfo: Optional[Union[MediaInfo, MusicInfo]] = None, + mtype: Optional[MediaType] = None, + media_source: Optional[MediaSource] = None, + media_id: Optional[str] = None, + target_directory: TransferDirectoryConf = None, + target_storage: Optional[str] = None, + target_path: Path = None, + transfer_type: Optional[str] = None, + scrape: Optional[bool] = None, + library_type_folder: Optional[bool] = None, + library_category_folder: Optional[bool] = None, + season: Optional[int] = None, + epformat: EpisodeFormat = None, + min_filesize: Optional[int] = 0, + downloader: Optional[str] = None, + download_hash: Optional[str] = None, + force: Optional[bool] = False, + background: Optional[bool] = True, + manual: Optional[bool] = False, + preview: Optional[bool] = False, + sync_extra_files: Optional[bool] = False, + cleanup_dest_fileitem: Optional[FileItem] = None, + continue_callback: Callable = None, + reorganize: Optional[bool] = False, ) -> Tuple[bool, Union[str, dict]]: """ 执行一个复杂目录的整理操作 @@ -1665,33 +1782,17 @@ class TransferChain(FileFilterMixin, ScrapeBatchMixin, EpisodeFormatMixin, Histo :param continue_callback: 继续处理回调 返回:成功标识,错误信息 """ - explicit_identity = media_source is not None or media_id is not None - normalized_source, normalized_media_id = resolve_media_identity( - media_source=media_source, - media_id=media_id, - ) - if explicit_identity and ( - not normalized_source or not normalized_media_id - ): - return False, "整理任务需要同时提供有效的 media_source 和 media_id" - if not explicit_identity and mediainfo: - normalized_source, normalized_media_id = resolve_media_identity( - media=mediainfo - ) - media_source = normalized_source - media_id = normalized_media_id - if explicit_identity and not mediainfo: - mediainfo = MediaChain().recognize_media( + mediainfo, media_source, media_id, identity_error = ( + self._normalize_transfer_identity( + mediainfo=mediainfo, mtype=mtype, media_source=media_source, media_id=media_id, - music_type=getattr(meta, "music_type", None), + meta=meta, ) - if not mediainfo: - return False, ( - "未识别到媒体信息," - f"media_source:{media_source},media_id:{media_id}" - ) + ) + if identity_error: + return False, identity_error # 是否全部成功 all_success = True diff --git a/docs/refactor/backend-architecture-next-stage.md b/docs/refactor/backend-architecture-next-stage.md index 3e58bcf20..affcf51bb 100644 --- a/docs/refactor/backend-architecture-next-stage.md +++ b/docs/refactor/backend-architecture-next-stage.md @@ -992,6 +992,9 @@ MFA/Passkey 专项测试与架构门禁通过,密钥类配置仍保留在安 `SubscribeAdded` payload、outbox stage/commit/post-commit 顺序仍由既有 `application/subscription/write.py` 负责。 两个公开入口均降至 150 行预算内,复杂度基线移除对应债务项;订阅识别、音乐订阅、写入事务和搜索来源专项 共 280 项测试通过,架构、复杂度与异步阻塞门禁通过。 +随后将 `TransferChain.do_transfer` 的公开入口收口为稳定兼容 Facade,先提取媒体身份规范化阶段,保留显式 +`media_source/media_id` 校验、识别失败文案和所有原有调用参数;整理专项 80 项测试通过,复杂度基线移除该入口, +后续继续拆分其批次规划与执行阶段。 #### ARCH-272:异步阻塞检测 diff --git a/tests/fixtures/architecture/complexity-baseline.json b/tests/fixtures/architecture/complexity-baseline.json index 9592de0aa..31be3aa8f 100644 --- a/tests/fixtures/architecture/complexity-baseline.json +++ b/tests/fixtures/architecture/complexity-baseline.json @@ -21,7 +21,6 @@ "app/chain/download.py:DownloadChain.download_single": 167, "app/chain/mediaserver.py:MediaServerChain.sync": 292, "app/chain/subscribe.py:SubscribeChain.match": 415, - "app/chain/subscribe.py:SubscribeChain.search": 246, - "app/chain/transfer.py:TransferChain.do_transfer": 885 + "app/chain/subscribe.py:SubscribeChain.search": 246 } }