From 33ca1f824f34296e17402f8254191fc508532471 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sat, 22 Aug 2026 20:46:48 +0800 Subject: [PATCH] refactor: extract download success settlement --- app/application/rss.py | 7 +- app/chain/download.py | 217 +++++++++--------- .../backend-architecture-next-stage.md | 2 + .../architecture/complexity-baseline.json | 2 +- 4 files changed, 115 insertions(+), 113 deletions(-) diff --git a/app/application/rss.py b/app/application/rss.py index 62606e8b7..3935d81da 100644 --- a/app/application/rss.py +++ b/app/application/rss.py @@ -279,9 +279,8 @@ class RssHelper: return False try: - config = get_chain_runtime_config_snapshot() ret = RequestUtils(ua=ua, - proxies=config.proxy if proxy else None, + proxies=get_chain_runtime_config_snapshot().proxy if proxy else None, timeout=timeout or 30, headers=headers).get_res(url) if not ret: logger.error(f"获取RSS失败:请求返回空值,URL: {url}") @@ -307,8 +306,8 @@ class RssHelper: if raw_data: ret_xml = RequestUtils.get_decoded_xml_content( ret, - performance_mode=config.encoding_detection_performance_mode, - confidence_threshold=config.encoding_detection_min_confidence + performance_mode=get_chain_runtime_config_snapshot().encoding_detection_performance_mode, + confidence_threshold=get_chain_runtime_config_snapshot().encoding_detection_min_confidence ) rust_items = self.__parse_with_rust(ret_xml) if rust_items is not None: diff --git a/app/chain/download.py b/app/chain/download.py index c6f51f542..2e40d91b2 100644 --- a/app/chain/download.py +++ b/app/chain/download.py @@ -1097,6 +1097,95 @@ class DownloadChain(ChainBase): logger.warn(str(err)) return save_path, str(err) + def _settle_download_success( + self, + *, + context: Context, + media: MediaInfo, + meta: MetaInfo, + torrent: TorrentInfo, + folder_name: str, + file_list: list, + download_dir: Path, + layout: Optional[str], + downloader: Optional[str], + download_hash: str, + download_episodes: Optional[str], + episodes: Optional[Set[int]], + channel: Optional[NotificationChannel], + source: Optional[str], + userid: Union[str, int, None], + username: Optional[str], + torrent_content: Union[str, bytes], + custom_words: Optional[str], + ) -> None: + """提交下载历史、文件明细和 durable 下载事件。""" + if layout == "NoSubfolder" or not folder_name: + download_path = download_dir / file_list[0] if file_list else download_dir + elif folder_name: + download_path = download_dir / folder_name + else: + download_path = download_dir / Path(file_list[0]).stem if file_list else download_dir + save_path = download_dir if layout == "NoSubfolder" or not folder_name else download_path + media_source, media_id = resolve_media_identity(media=media) + history_payload = { + "path": download_path.as_posix(), "type": media.type.value, + "title": media.title, "year": media.year, + "media_source": media_source, "media_id": media_id, + "music_type": getattr(media, "music_type", None), "seasons": meta.season, + "episodes": download_episodes or meta.episode, + "image": media.get_backdrop_image(), "poster": media.get_poster_image(), + "downloader": downloader, "download_hash": download_hash, + "torrent_name": torrent.title, "torrent_description": torrent.description, + "torrent_site": torrent.site_name, "userid": userid, "username": username, + "channel": channel.value if channel else None, + "date": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), + "media_category": media.category, "episode_group": media.episode_group, + "note": self._build_download_note(source, media, meta), + "custom_words": custom_words, + } + files_to_add = [] + for file in file_list: + if episodes: + file_meta = MetaInfo(Path(file).stem) + if not file_meta.begin_episode or file_meta.begin_episode not in episodes: + continue + if not Path(file).suffix or Path(file).suffix.lower() not in self.runtime_config.media_extensions: + continue + files_to_add.append({ + "download_hash": download_hash, "downloader": downloader, + "fullpath": (save_path / file).as_posix(), "savepath": save_path.as_posix(), + "filepath": file, "torrentname": meta.org_string, + }) + event_payload = { + "hash": download_hash, "context": context, "username": username, + "downloader": downloader, "episodes": episodes or meta.episode_list, "source": source, + } + + def after_commit() -> None: + """在历史与 intent 提交后保持原有通知和任务编排。""" + self._after_download_history_commit( + context=context, media=media, meta=meta, torrent=torrent, + channel=channel, source=source, userid=userid, username=username, + download_episodes=download_episodes, download_dir=download_dir, + torrent_content=torrent_content, + ) + + durable_event_writer = getattr(self, "durable_event_writer", None) + if durable_event_writer: + durable_event_writer.download_added( + history_payload=history_payload, file_payloads=files_to_add, + event_payload=event_payload, after_commit=after_commit, + publish=lambda payload: self.eventmanager.send_event(EventType.DownloadAdded, payload), + ) + return + downloadhis = DownloadHistoryOper() + downloadhis.add(**history_payload) + if files_to_add: + downloadhis.add_files(files_to_add) + after_commit() + self.eventmanager.send_event(EventType.DownloadAdded, event_payload) + def download_single(self, context: Context, torrent_file: Path = None, torrent_content: Optional[Union[str, bytes]] = None, @@ -1218,114 +1307,26 @@ class DownloadChain(ChainBase): _downloader, _hash, _layout, error_msg = None, None, None, "未找到下载器" if _hash: - # `不创建子文件夹` 或 `不存在子文件夹` - if _layout == "NoSubfolder" or not _folder_name: - # 下载路径记录至文件 - download_path = download_dir / _file_list[0] if _file_list else download_dir - # 原始布局 - elif _folder_name: - download_path = download_dir / _folder_name - # 创建子文件夹 - else: - download_path = download_dir / Path(_file_list[0]).stem if _file_list else download_dir - # 文件保存路径 - _save_path = download_dir if _layout == "NoSubfolder" or not _folder_name else download_path - - media_source, media_id = resolve_media_identity(media=_media) - history_payload = { - "path": download_path.as_posix(), - "type": _media.type.value, - "title": _media.title, - "year": _media.year, - "media_source": media_source, - "media_id": media_id, - "music_type": getattr(_media, "music_type", None), - "seasons": _meta.season, - "episodes": download_episodes or _meta.episode, - "image": _media.get_backdrop_image(), - "poster": _media.get_poster_image(), - "downloader": _downloader, - "download_hash": _hash, - "torrent_name": _torrent.title, - "torrent_description": _torrent.description, - "torrent_site": _torrent.site_name, - "userid": userid, - "username": username, - "channel": channel.value if channel else None, - "date": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), - "media_category": _media.category, - "episode_group": _media.episode_group, - "note": self._build_download_note(source, _media, _meta), - "custom_words": custom_words, - } - - # 登记下载文件 - files_to_add = [] - for file in _file_list: - if episodes: - # 识别文件集 - file_meta = MetaInfo(Path(file).stem) - if not file_meta.begin_episode \ - or file_meta.begin_episode not in episodes: - continue - # 只处理音视频、字幕格式 - media_exts = self.runtime_config.media_extensions - if not Path(file).suffix \ - or Path(file).suffix.lower() not in media_exts: - continue - files_to_add.append({ - "download_hash": _hash, - "downloader": _downloader, - "fullpath": (_save_path / file).as_posix(), - "savepath": _save_path.as_posix(), - "filepath": file, - "torrentname": _meta.org_string, - }) - event_payload = { - "hash": _hash, - "context": context, - "username": username, - "downloader": _downloader, - "episodes": episodes or _meta.episode_list, - "source": source, - } - - def after_commit() -> None: - """在历史与 intent 提交后保持原有通知和任务编排。""" - self._after_download_history_commit( - context=context, - media=_media, - meta=_meta, - torrent=_torrent, - channel=channel, - source=source, - userid=userid, - username=username, - download_episodes=download_episodes, - download_dir=download_dir, - torrent_content=torrent_content, - ) - - durable_event_writer = getattr(self, "durable_event_writer", None) - if durable_event_writer: - durable_event_writer.download_added( - history_payload=history_payload, - file_payloads=files_to_add, - event_payload=event_payload, - after_commit=after_commit, - publish=lambda payload: self.eventmanager.send_event( - EventType.DownloadAdded, - payload, - ), - ) - else: - # 显式注入旧测试上下文时保持兼容;正式启动上下文总会提供 durable writer。 - downloadhis = DownloadHistoryOper() - downloadhis.add(**history_payload) - if files_to_add: - downloadhis.add_files(files_to_add) - after_commit() - self.eventmanager.send_event(EventType.DownloadAdded, event_payload) + self._settle_download_success( + context=context, + media=_media, + meta=_meta, + torrent=_torrent, + folder_name=_folder_name, + file_list=_file_list, + download_dir=download_dir, + layout=_layout, + downloader=_downloader, + download_hash=_hash, + download_episodes=download_episodes, + episodes=episodes, + channel=channel, + source=source, + userid=userid, + username=username, + torrent_content=torrent_content, + custom_words=custom_words, + ) else: # 下载失败 logger.error(f"{_media.title_year} 添加下载任务失败:" diff --git a/docs/refactor/backend-architecture-next-stage.md b/docs/refactor/backend-architecture-next-stage.md index 9766bf784..656402f16 100644 --- a/docs/refactor/backend-architecture-next-stage.md +++ b/docs/refactor/backend-architecture-next-stage.md @@ -983,6 +983,8 @@ Outbox adapter、DB 装饰器、Base 与 UoW,strict 清单扩大到 37 个源 清理服务与 Chain 专项测试通过。 Passkey 的 APP_DOMAIN、NGINX_PORT 和用户验证要求也已接入 API 配置快照,配置债务降至 131 个文件; MFA/Passkey 专项测试与架构门禁通过,密钥类配置仍保留在安全端口范围内。 +`DownloadChain.download_single` 的下载成功结算已提取为独立阶段,入口从 255 行降至 167 行; +历史、文件明细、durable intent、post-commit 通知和旧测试 fallback 语义保持,下载专项测试通过。 #### ARCH-272:异步阻塞检测 diff --git a/tests/fixtures/architecture/complexity-baseline.json b/tests/fixtures/architecture/complexity-baseline.json index 2ddb39da3..ba3aad4b8 100644 --- a/tests/fixtures/architecture/complexity-baseline.json +++ b/tests/fixtures/architecture/complexity-baseline.json @@ -18,7 +18,7 @@ }, "chain_public": { "app/chain/download.py:DownloadChain.batch_download": 572, - "app/chain/download.py:DownloadChain.download_single": 255, + "app/chain/download.py:DownloadChain.download_single": 167, "app/chain/mediaserver.py:MediaServerChain.sync": 292, "app/chain/subscribe.py:SubscribeChain.add": 183, "app/chain/subscribe.py:SubscribeChain.async_add": 186,