From dc9eab695c675f736f81a6d07adb0a087574cffe Mon Sep 17 00:00:00 2001 From: jxxghp Date: Tue, 3 Nov 2026 13:11:17 +0800 Subject: [PATCH] fix(download): respect configured directory priority (#7282) --- app/application/directory.py | 12 ++++ app/chain/download/subtitle.py | 8 ++- tests/test_download_save_path_alias.py | 2 +- tests/test_media_classification_directory.py | 74 ++++++++++++++++++++ 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/app/application/directory.py b/app/application/directory.py index 1b91c84fd..c2804f9c3 100644 --- a/app/application/directory.py +++ b/app/application/directory.py @@ -393,6 +393,18 @@ class DirectoryHelper: """ return sorted([d for d in self.get_dirs() if d.download_path], key=lambda x: x.priority or 0) + def get_download_dir(self, media: Optional[DirectoryMedia]) -> Optional[_SchemaTransferDirectoryConf]: + """ + 按配置优先级返回首个媒体条件匹配且具有下载路径的目录。 + + 类型和固定分类只用于过滤候选,不覆盖用户配置的优先级;同优先级保留配置顺序。 + + :param media: 待下载的影视或音乐媒体信息 + :return: 首个可用下载目录,无匹配时返回 None + """ + return next((directory for directory in self.get_download_dirs() + if self.matches_media(directory, media)), None) + def get_local_download_dirs(self) -> List[_SchemaTransferDirectoryConf]: """ 获取所有本地的可下载目录 diff --git a/app/chain/download/subtitle.py b/app/chain/download/subtitle.py index edd8eba08..77f9b9b52 100644 --- a/app/chain/download/subtitle.py +++ b/app/chain/download/subtitle.py @@ -335,7 +335,11 @@ class DownloadSubtitleOwner(_DownloadOwnerBase): save_path: Optional[str] = None, ) -> Tuple[Optional[str], Optional[Path], str]: """ - 根据媒体信息解析下载目录。 + 显式路径沿用原有校验与分类规则,默认路径按配置优先级选择可用下载目录。 + + :param media_info: 待下载媒体信息 + :param save_path: 显式保存路径;空白值视为未指定 + :return: 存储类型、保存目录及错误信息 """ storage = 'local' save_path = specified_download_save_path(save_path) @@ -366,7 +370,7 @@ class DownloadSubtitleOwner(_DownloadOwnerBase): ) return storage, target_dir, "" - dir_info = DirectoryHelper().get_dir(media_info, include_unsorted=True) + dir_info = DirectoryHelper().get_download_dir(media_info) storage = (dir_info.storage or storage) if dir_info else storage if not dir_info: logger.error(f"未找到下载目录:{media_info.type.value if media_info.type else ""} {media_info.title_year}") diff --git a/tests/test_download_save_path_alias.py b/tests/test_download_save_path_alias.py index a0d8e1b8e..8848cf42a 100644 --- a/tests/test_download_save_path_alias.py +++ b/tests/test_download_save_path_alias.py @@ -189,6 +189,6 @@ def test_resolve_media_download_dir_uses_default_dir_for_blank_save_path(): """ default_dir = TransferDirectoryConf(name="剧集库", priority=1, storage="local", download_path="/downloads/tv") media = MediaInfo(type=MediaType.TV, title="测试剧集") - with patch.object(DirectoryHelper, "get_dir", return_value=default_dir): + with patch.object(DirectoryHelper, "get_download_dir", return_value=default_dir): result = DownloadSubtitleOwner._resolve_media_download_dir(media, save_path="") assert result == ("local", Path("/downloads/tv"), "") diff --git a/tests/test_media_classification_directory.py b/tests/test_media_classification_directory.py index ccb773977..b871613be 100644 --- a/tests/test_media_classification_directory.py +++ b/tests/test_media_classification_directory.py @@ -388,6 +388,80 @@ def test_directory_priority_applies_only_within_same_match_rank( assert selected is high_priority +@pytest.mark.parametrize("save_path", [None, "", " "]) +@pytest.mark.parametrize("fixed_category", [False, True]) +def test_default_download_respects_priority_before_match_specificity( + directory_resolver: ClassificationCategoryResolver, + monkeypatch: pytest.MonkeyPatch, + save_path: str | None, + fixed_category: bool, +) -> None: + """ + 未指定保存路径时,类型或固定分类更精确的目录不得盖过配置优先级。 + """ + general = TransferDirectoryConf( + name="全部", priority=0, download_path="/downloads/all", + ) + specific = TransferDirectoryConf( + name="电视剧", priority=1, media_type="tv", download_path="/downloads/tv", + media_category_id="tv.anime.jp" if fixed_category else None, + ) + monkeypatch.setattr( + DirectoryHelper, "get_dirs", staticmethod(lambda: [specific, general]), + ) + + result = DownloadSubtitleOwner._resolve_media_download_dir(_media(), save_path) + + assert result == ("local", Path("/downloads/all"), "") + + +@pytest.mark.parametrize( + "overrides", + [ + {"download_path": None, "library_path": "/library"}, + {"media_type": "movie"}, + {"media_category_id": "tv.deleted"}, + {"media_category": "其他"}, + ], +) +def test_default_download_skips_unavailable_or_mismatched_directories( + directory_resolver: ClassificationCategoryResolver, + monkeypatch: pytest.MonkeyPatch, + overrides: dict[str, object], +) -> None: + """ + 高优先级目录缺少下载路径、类型分类不符或分类失效时,继续选择可用目录。 + """ + invalid = TransferDirectoryConf.model_validate({ + "priority": 0, "download_path": "/downloads/invalid", **overrides, + }) + fallback = TransferDirectoryConf(priority=1, download_path="/downloads/fallback") + monkeypatch.setattr( + DirectoryHelper, "get_dirs", staticmethod(lambda: [invalid, fallback]), + ) + assert DirectoryHelper(directory_resolver).get_download_dir(_media()) is fallback + + monkeypatch.setattr(DirectoryHelper, "get_dirs", staticmethod(lambda: [invalid])) + assert DownloadSubtitleOwner._resolve_media_download_dir(_media()) == ( + None, None, "未找到下载目录", + ) + + +def test_default_download_preserves_configuration_order_for_equal_priority( + directory_resolver: ClassificationCategoryResolver, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """ + 未设置和显式零优先级相同时,按配置顺序选择而非分类精确度。 + """ + first = TransferDirectoryConf(download_path="/downloads/first") + second = TransferDirectoryConf(priority=0, media_type="tv", download_path="/downloads/second") + monkeypatch.setattr( + DirectoryHelper, "get_dirs", staticmethod(lambda: [first, second]), + ) + assert DirectoryHelper(directory_resolver).get_download_dir(_media()) is first + + def test_directory_media_type_aliases_match_consistently( directory_resolver: ClassificationCategoryResolver, ) -> None: