mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-03 22:04:57 +08:00
fix(download): 下载历史回读下载器实际保存目录 (#6708)
This commit is contained in:
@@ -532,6 +532,12 @@ class DownloadSubmissionOwner(_DownloadResourceOwner):
|
||||
layout: Optional[str],
|
||||
) -> None:
|
||||
"""按普通下载合同结算下载器明确返回的成功结果。"""
|
||||
# 下载器中已存在相同 Hash 时下载器会沿用原保存目录,须以实际目录记录
|
||||
download_dir = self._resolve_actual_download_dir(
|
||||
downloader=actual_downloader,
|
||||
download_hash=download_hash,
|
||||
fallback=prepared.download_dir,
|
||||
)
|
||||
self._settle_download_success(
|
||||
context=context,
|
||||
media=prepared.media,
|
||||
@@ -539,7 +545,7 @@ class DownloadSubmissionOwner(_DownloadResourceOwner):
|
||||
torrent=prepared.torrent,
|
||||
folder_name=prepared.folder_name,
|
||||
file_list=prepared.file_list,
|
||||
download_dir=prepared.download_dir,
|
||||
download_dir=download_dir,
|
||||
layout=layout,
|
||||
downloader=actual_downloader,
|
||||
download_hash=download_hash,
|
||||
@@ -553,6 +559,50 @@ class DownloadSubmissionOwner(_DownloadResourceOwner):
|
||||
custom_words=custom_words,
|
||||
)
|
||||
|
||||
def _resolve_actual_download_dir(
|
||||
self,
|
||||
*,
|
||||
downloader: Optional[str],
|
||||
download_hash: str,
|
||||
fallback: Path,
|
||||
) -> Path:
|
||||
"""
|
||||
获取下载器中该任务的真实保存目录,作为下载历史与文件明细的落库路径。
|
||||
|
||||
下载器中已存在相同 Hash 的任务时(例如刷流插件先下载),下载器会沿用
|
||||
其原有保存目录;此时按预期下载目录记录会得到磁盘上不存在的路径,后续
|
||||
整理与手动整理都无法关联该文件。仅本地绝对路径需要回读,远程存储的
|
||||
目录由对应存储协议自身管理,保持原值不变。
|
||||
"""
|
||||
if not download_hash or not fallback.is_absolute():
|
||||
return fallback
|
||||
try:
|
||||
torrents = self.list_torrents(hashs=[download_hash], downloader=downloader)
|
||||
except Exception as err:
|
||||
logger.debug(f"查询下载任务实际保存目录失败:{str(err)}")
|
||||
return fallback
|
||||
if not torrents:
|
||||
return fallback
|
||||
torrent = next(
|
||||
(
|
||||
item for item in torrents
|
||||
if str(getattr(item, "hash", "")) == str(download_hash)
|
||||
),
|
||||
torrents[0],
|
||||
)
|
||||
save_path = getattr(torrent, "save_path", None)
|
||||
if not save_path:
|
||||
return fallback
|
||||
actual_dir = Path(str(save_path))
|
||||
if not actual_dir.is_absolute():
|
||||
return fallback
|
||||
if actual_dir != fallback:
|
||||
logger.warn(
|
||||
f"下载器中已存在该任务,实际保存目录 {actual_dir.as_posix()} 与预期 "
|
||||
f"{fallback.as_posix()} 不一致,按实际目录记录下载历史"
|
||||
)
|
||||
return actual_dir
|
||||
|
||||
def _record_rejected_download(
|
||||
self,
|
||||
*,
|
||||
|
||||
@@ -1443,3 +1443,112 @@ def test_downloading_includes_media_type_and_source_site(monkeypatch):
|
||||
assert torrent.site_name == "示例站点"
|
||||
assert torrent.userid == "user-1"
|
||||
assert torrent.username == "tester"
|
||||
|
||||
|
||||
def _prepared_download_for_settle(download_dir: Path) -> download_submission._PreparedDownload:
|
||||
"""构造仅用于下载结算路径断言的准备事实。"""
|
||||
file_name = "The.Ordinary.Jackpot.S01E01.1080p.mkv"
|
||||
return download_submission._PreparedDownload(
|
||||
torrent=TorrentInfo(title=file_name),
|
||||
media=MediaInfo(type=MediaType.TV, title="中头奖还是要上班"),
|
||||
meta=MetaInfo(file_name),
|
||||
torrent_content=b"torrent",
|
||||
folder_name="",
|
||||
file_list=[file_name],
|
||||
download_dir=download_dir,
|
||||
download_uri=download_dir.as_posix(),
|
||||
download_episodes=None,
|
||||
site_downloader="qb",
|
||||
)
|
||||
|
||||
|
||||
def _settle_accepted_download(chain, download_dir: Path) -> None:
|
||||
"""以固定下载上下文调用一次成功结算。"""
|
||||
chain._settle_accepted_download(
|
||||
prepared=_prepared_download_for_settle(download_dir),
|
||||
context=Context(),
|
||||
episodes=None,
|
||||
channel=None,
|
||||
source="subscribe",
|
||||
userid=None,
|
||||
username="admin",
|
||||
custom_words=None,
|
||||
actual_downloader="qb",
|
||||
download_hash="hash123",
|
||||
layout="NoSubfolder",
|
||||
)
|
||||
|
||||
|
||||
def test_settle_accepted_download_uses_downloader_actual_save_dir():
|
||||
"""下载器中已存在同 Hash 任务时,下载历史须落到下载器实际保存目录。"""
|
||||
chain = DownloadChain.__new__(DownloadChain)
|
||||
chain.list_torrents = MagicMock(return_value=[DownloaderTorrent(
|
||||
hash="hash123",
|
||||
downloader="qb",
|
||||
save_path="/volume4/flow",
|
||||
content_path="/volume4/flow/The.Ordinary.Jackpot.S01E01.1080p.mkv",
|
||||
)])
|
||||
chain._settle_download_success = MagicMock()
|
||||
|
||||
_settle_accepted_download(chain, Path("/volume5/PT/downloads/TV/日韩剧"))
|
||||
|
||||
assert chain._settle_download_success.call_args.kwargs["download_dir"] == Path("/volume4/flow")
|
||||
chain.list_torrents.assert_called_once_with(hashs=["hash123"], downloader="qb")
|
||||
|
||||
|
||||
def test_settle_accepted_download_keeps_expected_dir_without_downloader_task():
|
||||
"""下载器查询不到任务时保持预期目录,不改变既有结算行为。"""
|
||||
chain = DownloadChain.__new__(DownloadChain)
|
||||
chain.list_torrents = MagicMock(return_value=[])
|
||||
chain._settle_download_success = MagicMock()
|
||||
|
||||
_settle_accepted_download(chain, Path("/downloads/TV"))
|
||||
|
||||
assert chain._settle_download_success.call_args.kwargs["download_dir"] == Path("/downloads/TV")
|
||||
|
||||
|
||||
def test_settle_accepted_download_falls_back_when_downloader_lookup_fails():
|
||||
"""下载器查询失败时回退预期目录,不能因回读失败阻断下载结算。"""
|
||||
chain = DownloadChain.__new__(DownloadChain)
|
||||
chain.list_torrents = MagicMock(side_effect=RuntimeError("downloader unavailable"))
|
||||
chain._settle_download_success = MagicMock()
|
||||
|
||||
_settle_accepted_download(chain, Path("/downloads/TV"))
|
||||
|
||||
assert chain._settle_download_success.call_args.kwargs["download_dir"] == Path("/downloads/TV")
|
||||
|
||||
|
||||
def test_settle_accepted_download_skips_lookup_for_remote_dir():
|
||||
"""远程存储保存目录不由本地回读,保持预期值且不查询下载器。"""
|
||||
chain = DownloadChain.__new__(DownloadChain)
|
||||
chain.list_torrents = MagicMock(return_value=[])
|
||||
chain._settle_download_success = MagicMock()
|
||||
|
||||
_settle_accepted_download(chain, Path("smb:/downloads/TV"))
|
||||
|
||||
assert chain._settle_download_success.call_args.kwargs["download_dir"] == Path("smb:/downloads/TV")
|
||||
chain.list_torrents.assert_not_called()
|
||||
|
||||
|
||||
def test_settle_accepted_download_records_actual_path_for_existing_torrent():
|
||||
"""复现刷流先下载场景:下载历史与文件明细须记录下载器实际路径。"""
|
||||
chain = DownloadChain.__new__(DownloadChain)
|
||||
chain.runtime_config = SimpleNamespace(media_extensions=(".mkv",))
|
||||
chain.durable_event_writer = None
|
||||
chain.download_history_repository = MagicMock()
|
||||
chain._build_download_notification = MagicMock(return_value=None)
|
||||
chain._submit_download_added_task = MagicMock()
|
||||
chain.eventmanager = MagicMock()
|
||||
chain.list_torrents = MagicMock(return_value=[DownloaderTorrent(
|
||||
hash="hash123",
|
||||
downloader="qb",
|
||||
save_path="/volume4/flow",
|
||||
content_path="/volume4/flow/The.Ordinary.Jackpot.S01E01.1080p.mkv",
|
||||
)])
|
||||
|
||||
_settle_accepted_download(chain, Path("/volume5/PT/downloads/TV/日韩剧"))
|
||||
|
||||
history, files = chain.download_history_repository.add.call_args.args
|
||||
assert history.path == "/volume4/flow/The.Ordinary.Jackpot.S01E01.1080p.mkv"
|
||||
assert files[0].fullpath == "/volume4/flow/The.Ordinary.Jackpot.S01E01.1080p.mkv"
|
||||
assert files[0].savepath == "/volume4/flow"
|
||||
|
||||
Reference in New Issue
Block a user