mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-01 21:07:01 +08:00
fix(download): 复用下载器已有任务时不再重复通知与记录文件明细 (#7078)
This commit is contained in:
@@ -21,6 +21,7 @@ from app.domain.context import (
|
||||
)
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.media import resolve_media_identity
|
||||
from app.schemas.types import (
|
||||
EventType,
|
||||
@@ -76,9 +77,12 @@ class DownloadHistoryOwner(_DownloadOwnerBase):
|
||||
username: Optional[str],
|
||||
torrent_content: Union[str, bytes],
|
||||
custom_words: Optional[str],
|
||||
already_exists: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
提交下载历史、文件明细和 durable 下载事件。
|
||||
|
||||
:param already_exists: 下载器中已存在同一任务时为 True,仅补记历史,不再重复通知与文件明细
|
||||
"""
|
||||
if layout == "NoSubfolder" or not folder_name:
|
||||
download_path = download_dir / file_list[0] if file_list else download_dir
|
||||
@@ -140,12 +144,14 @@ class DownloadHistoryOwner(_DownloadOwnerBase):
|
||||
torrentname=meta.org_string,
|
||||
)
|
||||
)
|
||||
if already_exists:
|
||||
files_to_add = self._filter_unrecorded_download_files(files_to_add, download_hash)
|
||||
frozen_files = tuple(files_to_add)
|
||||
event_payload = {
|
||||
"hash": download_hash, "context": context, "username": username,
|
||||
"downloader": downloader, "episodes": episodes or meta.episode_list, "source": source,
|
||||
}
|
||||
notification = self._build_download_notification(
|
||||
notification = None if already_exists else self._build_download_notification(
|
||||
media=media,
|
||||
meta=meta,
|
||||
torrent=torrent,
|
||||
@@ -187,3 +193,30 @@ class DownloadHistoryOwner(_DownloadOwnerBase):
|
||||
downloader=downloader,
|
||||
)
|
||||
self.eventmanager.send_event(EventType.DownloadAdded, event_payload)
|
||||
|
||||
def _filter_unrecorded_download_files(
|
||||
self,
|
||||
files: list[DownloadFileWrite],
|
||||
download_hash: str,
|
||||
) -> list[DownloadFileWrite]:
|
||||
"""
|
||||
剔除同一任务 Hash 下已经记录的有效文件明细,避免重复提交写入成对副本。
|
||||
|
||||
:param files: 本次提交解析出的文件明细
|
||||
:param download_hash: 下载器中的任务哈希标识
|
||||
:return: 尚未记录过的文件明细
|
||||
"""
|
||||
if not files:
|
||||
return files
|
||||
recorded = {
|
||||
item.fullpath
|
||||
for item in self.download_history_repository.get_files_by_hash(download_hash, state=1)
|
||||
if item.fullpath
|
||||
}
|
||||
if not recorded:
|
||||
return files
|
||||
retained = [item for item in files if item.fullpath not in recorded]
|
||||
skipped = len(files) - len(retained)
|
||||
if skipped:
|
||||
logger.info(f"下载任务 {download_hash} 的 {skipped} 条文件明细已记录,不再重复写入")
|
||||
return retained
|
||||
|
||||
@@ -40,6 +40,7 @@ from app.domain.meta.metabase import MetaBase
|
||||
from app.runtime.cache import FileCache
|
||||
from app.runtime.events import eventmanager
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.download import DOWNLOAD_TASK_ALREADY_EXISTS
|
||||
from app.schemas.event import ResourceDownloadEventData
|
||||
from app.schemas.file import FileURI
|
||||
from app.schemas.media import resolve_media_identity
|
||||
@@ -750,6 +751,7 @@ class DownloadSubmissionOwner(_DownloadResourceOwner):
|
||||
actual_downloader=actual_downloader,
|
||||
download_hash=download_hash,
|
||||
layout=layout,
|
||||
already_exists=error_msg == DOWNLOAD_TASK_ALREADY_EXISTS,
|
||||
)
|
||||
else:
|
||||
self._record_rejected_download(
|
||||
@@ -779,10 +781,15 @@ class DownloadSubmissionOwner(_DownloadResourceOwner):
|
||||
actual_downloader: Optional[str],
|
||||
download_hash: str,
|
||||
layout: Optional[str],
|
||||
already_exists: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
按普通下载合同结算下载器明确返回的成功结果。
|
||||
|
||||
:param already_exists: 下载器中已存在同一任务时为 True,结算时复用现有任务而不重复通知
|
||||
"""
|
||||
if already_exists:
|
||||
logger.info(f"{prepared.torrent.title} 下载器中已存在同一任务,沿用现有下载进度")
|
||||
# 下载器中已存在相同 Hash 时下载器会沿用原保存目录,须以实际目录记录
|
||||
download_dir = self._resolve_actual_download_dir(
|
||||
downloader=actual_downloader,
|
||||
@@ -808,6 +815,7 @@ class DownloadSubmissionOwner(_DownloadResourceOwner):
|
||||
username=username,
|
||||
torrent_content=prepared.torrent_content,
|
||||
custom_words=custom_words,
|
||||
already_exists=already_exists,
|
||||
)
|
||||
|
||||
def _resolve_actual_download_dir(
|
||||
|
||||
@@ -17,6 +17,7 @@ from app.modules.qbittorrent.qbittorrent import Qbittorrent
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.settings import get_runtime_setting
|
||||
from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo
|
||||
from app.schemas.download import DOWNLOAD_TASK_ALREADY_EXISTS
|
||||
from app.schemas.transfer import DownloaderFile, DownloaderTorrent
|
||||
from app.schemas.types import (
|
||||
DownloaderType,
|
||||
@@ -193,7 +194,7 @@ class QbittorrentModule(_DownloaderModuleBase[Qbittorrent]):
|
||||
server, torrent_hash, episodes or set(), custom_words, downloader, torrent_layout,
|
||||
already_exists=True,
|
||||
)
|
||||
return downloader or self.get_default_config_name(), torrent_hash, torrent_layout, "下载任务已存在"
|
||||
return downloader or self.get_default_config_name(), torrent_hash, torrent_layout, DOWNLOAD_TASK_ALREADY_EXISTS
|
||||
finally:
|
||||
torrents.clear()
|
||||
del torrents
|
||||
@@ -236,7 +237,7 @@ class QbittorrentModule(_DownloaderModuleBase[Qbittorrent]):
|
||||
if not selected:
|
||||
return None, None, None, message
|
||||
if already_exists:
|
||||
message = "下载任务已存在"
|
||||
message = DOWNLOAD_TASK_ALREADY_EXISTS
|
||||
return downloader or self.get_default_config_name(), torrent_hash, torrent_layout, message
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -14,6 +14,7 @@ from app.modules.rtorrent.rtorrent import Rtorrent, TorrentData
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.settings import get_runtime_setting
|
||||
from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo
|
||||
from app.schemas.download import DOWNLOAD_TASK_ALREADY_EXISTS
|
||||
from app.schemas.transfer import DownloaderFile, DownloaderTorrent
|
||||
from app.schemas.types import (
|
||||
DownloaderType,
|
||||
@@ -183,7 +184,7 @@ class RtorrentModule(_DownloaderModuleBase[Rtorrent]):
|
||||
downloader or self.get_default_config_name(),
|
||||
torrent_hash,
|
||||
torrent_layout,
|
||||
"下载任务已存在",
|
||||
DOWNLOAD_TASK_ALREADY_EXISTS,
|
||||
)
|
||||
finally:
|
||||
torrents.clear()
|
||||
|
||||
@@ -16,6 +16,7 @@ from app.modules.transmission.transmission import Transmission
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.settings import get_runtime_setting
|
||||
from app.schemas.dashboard import DownloaderInfo as _SchemaDownloaderInfo
|
||||
from app.schemas.download import DOWNLOAD_TASK_ALREADY_EXISTS
|
||||
from app.schemas.transfer import DownloaderFile, DownloaderTorrent
|
||||
from app.schemas.types import (
|
||||
DownloaderType,
|
||||
@@ -231,7 +232,7 @@ class TransmissionModule(_DownloaderModuleBase[Transmission]):
|
||||
)
|
||||
if not selected:
|
||||
return None, None, None, message
|
||||
return downloader or self.get_default_config_name(), torrent_hash, torrent_layout, "下载任务已存在"
|
||||
return downloader or self.get_default_config_name(), torrent_hash, torrent_layout, DOWNLOAD_TASK_ALREADY_EXISTS
|
||||
finally:
|
||||
torrents.clear()
|
||||
del torrents
|
||||
|
||||
@@ -10,6 +10,9 @@ from pydantic import model_validator as _model_validator
|
||||
from app.schemas.types import MediaSource as _MediaSource
|
||||
from app.schemas.types import MusicEntityType as _MusicEntityType
|
||||
|
||||
# 下载器报告同一 hash 的任务已存在时返回的统一说明,订阅链路据此区分复用与新增提交。
|
||||
DOWNLOAD_TASK_ALREADY_EXISTS = "下载任务已存在"
|
||||
|
||||
|
||||
class DownloadTask(BaseModel):
|
||||
"""
|
||||
|
||||
@@ -137,6 +137,7 @@ SCHEMA_EXPORTS = {
|
||||
'CustomFilterRuleUpdateRequest': ('app.schemas.rule', 'CustomFilterRuleUpdateRequest'),
|
||||
'CustomIdentifiersUpdateRequest': ('app.schemas.system', 'CustomIdentifiersUpdateRequest'),
|
||||
'CustomRule': ('app.schemas.rule', 'CustomRule'),
|
||||
'DOWNLOAD_TASK_ALREADY_EXISTS': ('app.schemas.download', 'DOWNLOAD_TASK_ALREADY_EXISTS'),
|
||||
'DashboardMemoryInfo': ('app.schemas.dashboard', 'DashboardMemoryInfo'),
|
||||
'DashboardSystemInfo': ('app.schemas.dashboard', 'DashboardSystemInfo'),
|
||||
'DataT': ('app.schemas.response', 'DataT'),
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
> [`docs/rules/04-design-patterns.md`](rules/04-design-patterns.md) 为准,本文与其保持一致;
|
||||
> 如出现差异,以规则文档为准。
|
||||
>
|
||||
> *最后更新:2026-10-18*
|
||||
> *最后更新:2026-10-19*
|
||||
|
||||
---
|
||||
|
||||
@@ -811,13 +811,13 @@ flowchart LR
|
||||
|
||||
### 10.2 当前架构快照
|
||||
|
||||
以下是 2026-10-18 核对的 `tests/fixtures/architecture/` 数据;这是源码合同快照,
|
||||
以下是 2026-10-19 核对的 `tests/fixtures/architecture/` 数据;这是源码合同快照,
|
||||
不代表某个已发布镜像或正在运行的实例已经验证。
|
||||
|
||||
| 指标 | 当前值 |
|
||||
|---|---:|
|
||||
| Python 模块 | 1124 |
|
||||
| 内部导入边 | 9,615 |
|
||||
| 内部导入边 | 9,621 |
|
||||
| 非平凡 SCC | 1(29 个模块的 TMDB vendor 精确 containment) |
|
||||
| Application / Chain 具体 Adapter 直连 | 0 / 0 |
|
||||
| Direct egress | 51(精确 policy 准入,Application / Chain 均为 0) |
|
||||
|
||||
@@ -975,8 +975,8 @@
|
||||
"runtime_only": true
|
||||
}
|
||||
},
|
||||
"edge_count": 9615,
|
||||
"edge_sha256": "3b153e37066b873384b640fc3e20b3a526d6ec44e7c16a6ba407e1ec2ab5fc6a",
|
||||
"edge_count": 9621,
|
||||
"edge_sha256": "30d190a87ebc01743763a67be2f52f9b88e59bc553279fb65b66b758131c344e",
|
||||
"edges": [
|
||||
"app -> app.foundation",
|
||||
"app -> app.foundation.environment",
|
||||
@@ -4674,6 +4674,8 @@
|
||||
"app.chain.download.history -> app.domain.meta",
|
||||
"app.chain.download.history -> app.domain.meta.metabase",
|
||||
"app.chain.download.history -> app.domain.metainfo",
|
||||
"app.chain.download.history -> app.runtime",
|
||||
"app.chain.download.history -> app.runtime.log",
|
||||
"app.chain.download.history -> app.schemas",
|
||||
"app.chain.download.history -> app.schemas.media",
|
||||
"app.chain.download.history -> app.schemas.types",
|
||||
@@ -4745,6 +4747,7 @@
|
||||
"app.chain.download.submission -> app.runtime.events",
|
||||
"app.chain.download.submission -> app.runtime.log",
|
||||
"app.chain.download.submission -> app.schemas",
|
||||
"app.chain.download.submission -> app.schemas.download",
|
||||
"app.chain.download.submission -> app.schemas.event",
|
||||
"app.chain.download.submission -> app.schemas.file",
|
||||
"app.chain.download.submission -> app.schemas.media",
|
||||
@@ -8065,6 +8068,7 @@
|
||||
"app.modules.qbittorrent -> app.runtime.settings",
|
||||
"app.modules.qbittorrent -> app.schemas",
|
||||
"app.modules.qbittorrent -> app.schemas.dashboard",
|
||||
"app.modules.qbittorrent -> app.schemas.download",
|
||||
"app.modules.qbittorrent -> app.schemas.transfer",
|
||||
"app.modules.qbittorrent -> app.schemas.types",
|
||||
"app.modules.qbittorrent.qbittorrent -> app.domain",
|
||||
@@ -8139,6 +8143,7 @@
|
||||
"app.modules.rtorrent -> app.runtime.settings",
|
||||
"app.modules.rtorrent -> app.schemas",
|
||||
"app.modules.rtorrent -> app.schemas.dashboard",
|
||||
"app.modules.rtorrent -> app.schemas.download",
|
||||
"app.modules.rtorrent -> app.schemas.transfer",
|
||||
"app.modules.rtorrent -> app.schemas.types",
|
||||
"app.modules.rtorrent.rtorrent -> app.runtime",
|
||||
@@ -8537,6 +8542,7 @@
|
||||
"app.modules.transmission -> app.runtime.settings",
|
||||
"app.modules.transmission -> app.schemas",
|
||||
"app.modules.transmission -> app.schemas.dashboard",
|
||||
"app.modules.transmission -> app.schemas.download",
|
||||
"app.modules.transmission -> app.schemas.transfer",
|
||||
"app.modules.transmission -> app.schemas.types",
|
||||
"app.modules.transmission.transmission -> app.foundation",
|
||||
|
||||
@@ -1965,9 +1965,13 @@ def _prepared_download_for_settle(download_dir: Path) -> download_submission._Pr
|
||||
)
|
||||
|
||||
|
||||
def _settle_accepted_download(chain, download_dir: Path) -> None:
|
||||
def _settle_accepted_download(chain, download_dir: Path, already_exists: bool = False) -> None:
|
||||
"""
|
||||
以固定下载上下文调用一次成功结算。
|
||||
|
||||
:param chain: 待结算的下载链实例
|
||||
:param download_dir: 提交前解析出的预期下载目录
|
||||
:param already_exists: 是否按下载器已存在同一任务结算
|
||||
"""
|
||||
chain._settle_accepted_download(
|
||||
prepared=_prepared_download_for_settle(download_dir),
|
||||
@@ -1981,6 +1985,7 @@ def _settle_accepted_download(chain, download_dir: Path) -> None:
|
||||
actual_downloader="qb",
|
||||
download_hash="hash123",
|
||||
layout="NoSubfolder",
|
||||
already_exists=already_exists,
|
||||
)
|
||||
|
||||
|
||||
@@ -2043,6 +2048,19 @@ def test_settle_accepted_download_skips_lookup_for_remote_dir():
|
||||
chain.list_torrents.assert_not_called()
|
||||
|
||||
|
||||
def test_settle_accepted_download_marks_reuse_when_task_exists():
|
||||
"""
|
||||
下载器已存在同一任务时,复用事实须传递到结算,供历史跳过重复通知。
|
||||
"""
|
||||
chain = DownloadChain.__new__(DownloadChain)
|
||||
chain.list_torrents = MagicMock(return_value=[])
|
||||
chain._settle_download_success = MagicMock()
|
||||
|
||||
_settle_accepted_download(chain, Path("/downloads/TV"), already_exists=True)
|
||||
|
||||
assert chain._settle_download_success.call_args.kwargs["already_exists"] is True
|
||||
|
||||
|
||||
def test_settle_accepted_download_records_actual_path_for_existing_torrent():
|
||||
"""
|
||||
复现刷流先下载场景:下载历史与文件明细须记录下载器实际路径。
|
||||
|
||||
@@ -5,16 +5,19 @@
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import MagicMock, Mock
|
||||
|
||||
import pytest
|
||||
from torrentool.bencode import Bencode
|
||||
|
||||
from app.chain.download.facade import DownloadChain
|
||||
from app.domain.context import Context
|
||||
from app.domain.context import Context, MediaInfo, TorrentInfo
|
||||
from app.domain.metainfo import MetaInfo
|
||||
from app.modules.qbittorrent import QbittorrentModule
|
||||
from app.modules.transmission import TransmissionModule
|
||||
from app.modules.transmission.transmission import Transmission
|
||||
from app.schemas.download import DOWNLOAD_TASK_ALREADY_EXISTS
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
_CUSTOM_WORDS = (
|
||||
"A.Will.Eternal.S04 => 一念永恒{[tmdbid=107371;type=tv]}S01 "
|
||||
@@ -335,3 +338,127 @@ def test_qbittorrent_force_start_confirms_selection_result(downloader, started):
|
||||
assert result[1] == (torrent_hash if started else None)
|
||||
server.torrents_set_force_start.assert_called_once_with(torrent_hash)
|
||||
server.start_torrents.assert_not_called()
|
||||
|
||||
|
||||
def _report_existing_task(server, module, torrent_hash) -> None:
|
||||
"""
|
||||
让真实模块把本次添加解释为下载器中已存在的同一任务。
|
||||
|
||||
:param server: 被替换的外部下载器客户端
|
||||
:param module: 当前参数化的下载器模块
|
||||
:param torrent_hash: 下载器中已存在任务的哈希
|
||||
"""
|
||||
server.add_torrent.return_value = (
|
||||
(False, []) if isinstance(module, QbittorrentModule) else None
|
||||
)
|
||||
server.get_torrents.return_value = (
|
||||
[{"hash": torrent_hash, "name": "Same.Show.S01", "total_size": 42, "tags": ""}]
|
||||
if isinstance(module, QbittorrentModule) else
|
||||
[SimpleNamespace(hashString=torrent_hash, name="Same.Show.S01", total_size=42, labels=[])],
|
||||
None,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("existing", [True, False])
|
||||
def test_downloader_task_identity_decides_reuse_flag(downloader, existing):
|
||||
"""
|
||||
复用已有任务与新增提交在结算入口必须可区分,供链路跳过重复通知与文件明细。
|
||||
"""
|
||||
module, server, content, torrent_hash = downloader
|
||||
if existing:
|
||||
_report_existing_task(server, module, torrent_hash)
|
||||
chain = DownloadChain.__new__(DownloadChain)
|
||||
chain.run_module = lambda method, **kwargs: getattr(module, method)(**kwargs)
|
||||
chain._settle_accepted_download = Mock()
|
||||
chain._record_rejected_download = Mock()
|
||||
|
||||
result, message = _submit(chain, content, None, None)
|
||||
|
||||
assert result == torrent_hash
|
||||
assert chain._settle_accepted_download.call_args.kwargs["already_exists"] is existing
|
||||
assert (message == DOWNLOAD_TASK_ALREADY_EXISTS) is existing
|
||||
chain._record_rejected_download.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("durable", [False, True])
|
||||
@pytest.mark.parametrize("existing,expected_files", [
|
||||
("matched", [_FILES[1]]),
|
||||
("all", []),
|
||||
("empty", _FILES),
|
||||
("unrelated", _FILES),
|
||||
], ids=["matched", "all", "empty", "unrelated"])
|
||||
def test_reuse_settlement_keeps_history_and_skips_duplicate_effects(durable, existing, expected_files):
|
||||
"""
|
||||
复用已有任务时仍记录历史并触发整理与事件,但不生成通知意图,也不重复写入已记录的文件明细。
|
||||
"""
|
||||
recorded_file, new_file = _FILES
|
||||
records = {
|
||||
"matched": [f"/downloads/shared-show/{recorded_file}", ""],
|
||||
"all": [f"/downloads/shared-show/{name}" for name in _FILES],
|
||||
"empty": [],
|
||||
"unrelated": ["/downloads/shared-show/Other.Show.S01E01.1080p.mkv"],
|
||||
}[existing]
|
||||
meta = MetaInfo(recorded_file)
|
||||
media = MediaInfo(type=MediaType.TV, title="一念永恒", tmdb_id=107371)
|
||||
torrent = TorrentInfo(title="A.Will.Eternal.S04.2160p")
|
||||
chain = DownloadChain.__new__(DownloadChain)
|
||||
chain.download_history_repository = MagicMock()
|
||||
chain.download_history_repository.get_files_by_hash.return_value = [
|
||||
SimpleNamespace(fullpath=path) for path in records
|
||||
]
|
||||
chain.durable_event_writer = MagicMock() if durable else None
|
||||
chain._build_download_notification = MagicMock(return_value=object())
|
||||
chain._submit_download_added_task = MagicMock()
|
||||
chain.post_message = MagicMock()
|
||||
chain.eventmanager = MagicMock()
|
||||
|
||||
chain._settle_download_success(
|
||||
context=Context(meta_info=meta, media_info=media, torrent_info=torrent),
|
||||
media=media, meta=meta, torrent=torrent,
|
||||
folder_name="shared-show", file_list=[recorded_file, new_file],
|
||||
download_dir=Path("/downloads"), layout="Original", downloader="qb",
|
||||
download_hash="reuse-hash", download_episodes=None, episodes=None,
|
||||
channel=None, source="subscribe", userid=None, username=None,
|
||||
torrent_content=b"torrent", custom_words=None, already_exists=True,
|
||||
)
|
||||
|
||||
if durable:
|
||||
payload = chain.durable_event_writer.download_added.call_args.kwargs
|
||||
assert payload["notification_payload"] is None
|
||||
history, files = payload["history"], payload["files"]
|
||||
else:
|
||||
history, files = chain.download_history_repository.add.call_args.args
|
||||
chain.post_message.assert_not_called()
|
||||
chain._submit_download_added_task.assert_called_once()
|
||||
chain.eventmanager.send_event.assert_called_once()
|
||||
assert history.download_hash == "reuse-hash"
|
||||
assert [item.filepath for item in files] == list(expected_files)
|
||||
chain._build_download_notification.assert_not_called()
|
||||
chain.download_history_repository.get_files_by_hash.assert_called_once_with("reuse-hash", state=1)
|
||||
|
||||
|
||||
def test_reuse_settlement_without_media_files_skips_file_lookup():
|
||||
"""
|
||||
复用任务且本次种子没有可记录的媒体文件时,不再查询历史明细,也不写入文件记录。
|
||||
"""
|
||||
meta = MetaInfo("readme.txt")
|
||||
media = MediaInfo(type=MediaType.TV, title="一念永恒", tmdb_id=107371)
|
||||
torrent = TorrentInfo(title="A.Will.Eternal.S04.2160p")
|
||||
chain = DownloadChain.__new__(DownloadChain)
|
||||
chain.download_history_repository = MagicMock()
|
||||
chain.durable_event_writer = MagicMock()
|
||||
chain._build_download_notification = MagicMock(return_value=object())
|
||||
|
||||
chain._settle_download_success(
|
||||
context=Context(meta_info=meta, media_info=media, torrent_info=torrent),
|
||||
media=media, meta=meta, torrent=torrent,
|
||||
folder_name="shared-show", file_list=["readme.txt"],
|
||||
download_dir=Path("/downloads"), layout="Original", downloader="qb",
|
||||
download_hash="reuse-hash", download_episodes=None, episodes=None,
|
||||
channel=None, source="subscribe", userid=None, username=None,
|
||||
torrent_content=b"torrent", custom_words=None, already_exists=True,
|
||||
)
|
||||
|
||||
payload = chain.durable_event_writer.download_added.call_args.kwargs
|
||||
assert payload["files"] == ()
|
||||
chain.download_history_repository.get_files_by_hash.assert_not_called()
|
||||
|
||||
Reference in New Issue
Block a user