mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-08 17:08:35 +08:00
fix(download): accept legacy remote subscription paths (#6147)
This commit is contained in:
+17
-4
@@ -303,18 +303,21 @@ def validate_download_save_path(save_path: str) -> str:
|
|||||||
"""
|
"""
|
||||||
校验用户传入的下载保存目录,/download/paths 暴露的下载目录配置是允许写入的公共合同。
|
校验用户传入的下载保存目录,/download/paths 暴露的下载目录配置是允许写入的公共合同。
|
||||||
|
|
||||||
:param save_path: 下载保存目录,支持本地 /path 或远端 <storage>:/path
|
:param save_path: 下载保存目录,支持本地 /path、远端 <storage>:/path 和旧版订阅中的无前缀远程路径
|
||||||
:return: 可直接传给下载接口的规范化保存目录
|
:return: 可直接传给下载接口的规范化保存目录
|
||||||
"""
|
"""
|
||||||
value = str(save_path or "").strip()
|
value = str(save_path or "").strip()
|
||||||
|
has_storage_prefix = any(value.startswith(f"{item.value}:") for item in StorageSchema)
|
||||||
storage, raw_path = _split_file_uri(value)
|
storage, raw_path = _split_file_uri(value)
|
||||||
target_style, target_path = _normalize_download_path(raw_path, storage)
|
target_style, target_path = _normalize_download_path(raw_path, storage)
|
||||||
|
|
||||||
|
download_roots = []
|
||||||
for dir_info in DirectoryHelper().get_download_dirs():
|
for dir_info in DirectoryHelper().get_download_dirs():
|
||||||
root = _normalize_download_root(dir_info)
|
root = _normalize_download_root(dir_info)
|
||||||
if not root:
|
if root:
|
||||||
continue
|
download_roots.append(root)
|
||||||
root_storage, root_style, root_path = root
|
|
||||||
|
for root_storage, root_style, root_path in download_roots:
|
||||||
if storage != root_storage:
|
if storage != root_storage:
|
||||||
continue
|
continue
|
||||||
if target_style != root_style:
|
if target_style != root_style:
|
||||||
@@ -322,4 +325,14 @@ def validate_download_save_path(save_path: str) -> str:
|
|||||||
if target_path == root_path or target_path.is_relative_to(root_path):
|
if target_path == root_path or target_path.is_relative_to(root_path):
|
||||||
return _download_path_uri(storage, target_path)
|
return _download_path_uri(storage, target_path)
|
||||||
|
|
||||||
|
# 旧版订阅界面只持久化 download_path,需要从已配置根目录恢复远程存储类型。
|
||||||
|
if (not has_storage_prefix
|
||||||
|
and storage == StorageSchema.Local.value
|
||||||
|
and target_style == "posix"):
|
||||||
|
for root_storage, root_style, root_path in download_roots:
|
||||||
|
if root_storage == StorageSchema.Local.value or target_style != root_style:
|
||||||
|
continue
|
||||||
|
if target_path == root_path or target_path.is_relative_to(root_path):
|
||||||
|
return _download_path_uri(root_storage, target_path)
|
||||||
|
|
||||||
raise ValueError("保存路径不在允许的下载目录范围内")
|
raise ValueError("保存路径不在允许的下载目录范围内")
|
||||||
|
|||||||
@@ -135,6 +135,34 @@ def test_validate_download_save_path_accepts_configured_roots_and_children(save_
|
|||||||
assert validate_download_save_path(save_path) == expected
|
assert validate_download_save_path(save_path) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_download_save_path_accepts_legacy_remote_path_without_storage_prefix():
|
||||||
|
"""旧版订阅保存的远程原始路径应恢复为带存储前缀的 FileURI。"""
|
||||||
|
assert validate_download_save_path("/media/anime/sub") == "rclone:/media/anime/sub"
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_download_save_path_prefers_configured_local_root(monkeypatch):
|
||||||
|
"""无前缀路径同时命中本地和远程根目录时应保持本地语义。"""
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"app.helper.directory.DirectoryHelper.get_download_dirs",
|
||||||
|
lambda _self: [
|
||||||
|
TransferDirectoryConf(
|
||||||
|
name="远程下载",
|
||||||
|
priority=1,
|
||||||
|
storage="rclone",
|
||||||
|
download_path="/shared",
|
||||||
|
),
|
||||||
|
TransferDirectoryConf(
|
||||||
|
name="本地下载",
|
||||||
|
priority=2,
|
||||||
|
storage="local",
|
||||||
|
download_path="/shared",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert validate_download_save_path("/shared/movie") == "/shared/movie"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("save_path", "expected"),
|
("save_path", "expected"),
|
||||||
[
|
[
|
||||||
@@ -258,6 +286,23 @@ def test_resolve_media_download_dir_applies_remote_root_classification(monkeypat
|
|||||||
assert error_msg == ""
|
assert error_msg == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_media_download_dir_accepts_legacy_remote_root_without_storage_prefix(monkeypatch):
|
||||||
|
"""订阅中的旧版远程根路径应按对应存储和分类配置解析。"""
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"app.helper.directory.DirectoryHelper.get_download_dirs",
|
||||||
|
lambda _self: _classified_download_dirs(),
|
||||||
|
)
|
||||||
|
|
||||||
|
storage, target_dir, error_msg = DownloadChain._resolve_media_download_dir(
|
||||||
|
media_info=_build_tv_media(),
|
||||||
|
save_path="/media",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert storage == "rclone"
|
||||||
|
assert target_dir == Path("/media/电视剧/动漫")
|
||||||
|
assert error_msg == ""
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_media_download_dir_uses_matching_media_specific_root(monkeypatch):
|
def test_resolve_media_download_dir_uses_matching_media_specific_root(monkeypatch):
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"app.helper.directory.DirectoryHelper.get_download_dirs",
|
"app.helper.directory.DirectoryHelper.get_download_dirs",
|
||||||
@@ -386,6 +431,27 @@ def test_download_single_applies_configured_root_classification(monkeypatch):
|
|||||||
assert chain.download.call_args.kwargs["download_dir"] == Path("/downloads/电视剧/动漫")
|
assert chain.download.call_args.kwargs["download_dir"] == Path("/downloads/电视剧/动漫")
|
||||||
|
|
||||||
|
|
||||||
|
def test_download_single_accepts_legacy_remote_root_without_storage_prefix(monkeypatch):
|
||||||
|
"""旧订阅的无前缀远程根应以正确 FileURI 提交给下载模块。"""
|
||||||
|
monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"app.helper.directory.DirectoryHelper.get_download_dirs",
|
||||||
|
lambda _self: _classified_download_dirs(),
|
||||||
|
)
|
||||||
|
chain = _build_download_chain()
|
||||||
|
chain.download.return_value = ("qb", None, "Original", "test stop")
|
||||||
|
context = _build_context()
|
||||||
|
context.media_info = _build_tv_media()
|
||||||
|
|
||||||
|
chain.download_single(
|
||||||
|
context=context,
|
||||||
|
torrent_content=b"torrent-content",
|
||||||
|
save_path="/media",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert chain.download.call_args.kwargs["download_dir"] == Path("rclone:/media/电视剧/动漫")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("save_path", ["", " "])
|
@pytest.mark.parametrize("save_path", ["", " "])
|
||||||
def test_download_single_rejects_explicit_empty_save_path_before_default_fallback(monkeypatch, save_path):
|
def test_download_single_rejects_explicit_empty_save_path_before_default_fallback(monkeypatch, save_path):
|
||||||
monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None)
|
monkeypatch.setattr(download_module.eventmanager, "send_event", lambda *args, **kwargs: None)
|
||||||
|
|||||||
Reference in New Issue
Block a user