From e168e31a8faa12c225fb7ca039591555bdb1b3e6 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 17 May 2026 08:26:13 +0800 Subject: [PATCH] fix: offload subtitle download after add task --- app/chain/download.py | 32 +++++++++++- tests/test_download_chain.py | 97 ++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 tests/test_download_chain.py diff --git a/app/chain/download.py b/app/chain/download.py index 0fe13395..fbc316fb 100644 --- a/app/chain/download.py +++ b/app/chain/download.py @@ -17,6 +17,7 @@ from app.core.metainfo import MetaInfo from app.db.downloadhistory_oper import DownloadHistoryOper from app.db.mediaserver_oper import MediaServerOper from app.helper.directory import DirectoryHelper +from app.helper.thread import ThreadHelper from app.helper.torrent import TorrentHelper from app.log import logger from app.schemas import ExistMediaInfo, FileURI, NotExistMediaInfo, DownloadingTorrent, Notification, ResourceSelectionEventData, \ @@ -32,6 +33,31 @@ class DownloadChain(ChainBase): 下载处理链 """ + def _submit_download_added_task( + self, + context: Context, + download_dir: Path, + torrent_content: Union[str, bytes], + ) -> None: + """ + 后台执行下载成功后的附加处理,避免站点字幕下载阻塞添加下载响应。 + """ + + def _run_download_added() -> None: + try: + self.download_added( + context=context, + download_dir=download_dir, + torrent_content=torrent_content, + ) + except Exception as err: + logger.error(f"执行下载成功后处理失败:{str(err)}") + + try: + ThreadHelper().submit(_run_download_added) + except Exception as err: + logger.error(f"提交下载成功后处理后台任务失败:{str(err)}") + def download_torrent(self, torrent: TorrentInfo, channel: MessageChannel = None, source: Optional[str] = None, @@ -371,7 +397,11 @@ class DownloadChain(ChainBase): username=username, ) # 下载成功后处理 - self.download_added(context=context, download_dir=download_dir, torrent_content=torrent_content) + self._submit_download_added_task( + context=context, + download_dir=download_dir, + torrent_content=torrent_content, + ) # 广播事件 self.eventmanager.send_event(EventType.DownloadAdded, { "hash": _hash, diff --git a/tests/test_download_chain.py b/tests/test_download_chain.py new file mode 100644 index 00000000..f1aca479 --- /dev/null +++ b/tests/test_download_chain.py @@ -0,0 +1,97 @@ +from pathlib import Path +from unittest.mock import MagicMock + +import app.chain.download as download_module +from app.chain.download import DownloadChain +from app.core.context import Context, MediaInfo, TorrentInfo +from app.core.metainfo import MetaInfo +from app.schemas.types import MediaType + + +class _FakeDownloadHistoryOper: + """ + 避免单元测试写入真实下载历史,只验证下载链路的控制流。 + """ + + def add(self, **_kwargs): + pass + + def add_files(self, _files): + pass + + +class _FakeTorrentHelper: + """ + 避免解析真实种子内容,让测试聚焦下载成功后的后台处理。 + """ + + def get_fileinfo_from_torrent_content(self, _torrent_content): + return "", [] + + +class _FakeThreadHelper: + """ + 捕获提交到线程池的任务,测试中手动触发以避免真正启动后台线程。 + """ + + submitted = [] + + def submit(self, func, *args, **kwargs): + self.submitted.append((func, args, kwargs)) + + +def test_download_single_submits_download_added_to_background(monkeypatch): + """ + 添加下载成功后,站点字幕等后处理应提交到后台,不能阻塞下载接口返回。 + """ + _FakeThreadHelper.submitted = [] + monkeypatch.setattr(download_module, "ThreadHelper", _FakeThreadHelper) + monkeypatch.setattr(download_module, "DownloadHistoryOper", _FakeDownloadHistoryOper) + monkeypatch.setattr(download_module, "TorrentHelper", _FakeTorrentHelper) + + chain = DownloadChain.__new__(DownloadChain) + chain.download = MagicMock(return_value=("qb", "hash123", "Original", "添加下载成功")) + chain.download_added = MagicMock() + chain.eventmanager = MagicMock() + chain.eventmanager.send_event.return_value = None + chain.post_message = MagicMock() + + context = Context( + meta_info=MetaInfo("Demo Movie 2024"), + media_info=MediaInfo( + type=MediaType.MOVIE, + title="Demo Movie", + year="2024", + tmdb_id=1, + genre_ids=[18], + ), + torrent_info=TorrentInfo( + title="Demo Movie 2024", + enclosure="https://example.com/demo.torrent", + site_cookie="uid=1", + site_name="TestSite", + ), + ) + + result = chain.download_single( + context=context, + torrent_content=b"torrent-content", + save_path="/downloads", + username="tester", + ) + + assert result == "hash123" + chain.download_added.assert_not_called() + assert len(_FakeThreadHelper.submitted) == 1 + + task, args, kwargs = _FakeThreadHelper.submitted[0] + assert args == () + assert kwargs == {} + + task() + + chain.download_added.assert_called_once_with( + context=context, + download_dir=Path("/downloads"), + torrent_content=b"torrent-content", + )