mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
+54
-11
@@ -292,6 +292,37 @@ class _DownloaderBase(ServiceBase[TService, DownloaderConf]):
|
|||||||
"""
|
"""
|
||||||
self._default_config_name = None
|
self._default_config_name = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __replace_path_prefix(path: Union[Path, str], source: str, target: str) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
按完整路径段替换路径前缀,避免 /media 误匹配 /media2 这类相邻目录。
|
||||||
|
"""
|
||||||
|
if not source or not source.strip() or not target or not target.strip():
|
||||||
|
return None
|
||||||
|
|
||||||
|
path_text = Path(path).as_posix()
|
||||||
|
source_path = Path(source.strip()).as_posix()
|
||||||
|
target_path = Path(target.strip()).as_posix()
|
||||||
|
if path_text == source_path:
|
||||||
|
return target_path
|
||||||
|
|
||||||
|
source_prefix = f"{source_path.rstrip('/')}/"
|
||||||
|
if path_text.startswith(source_prefix):
|
||||||
|
suffix = path_text[len(source_prefix):]
|
||||||
|
return (Path(target_path) / suffix).as_posix()
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __strip_storage_prefix(path: str) -> str:
|
||||||
|
"""
|
||||||
|
去掉存储协议前缀 if any,下载器无法识别本地存储协议。
|
||||||
|
"""
|
||||||
|
for s in StorageSchema:
|
||||||
|
prefix = f"{s.value}:"
|
||||||
|
if path.startswith(prefix):
|
||||||
|
return path[len(prefix):]
|
||||||
|
return path
|
||||||
|
|
||||||
def normalize_path(self, path: Path, downloader: Optional[str]) -> str:
|
def normalize_path(self, path: Path, downloader: Optional[str]) -> str:
|
||||||
"""
|
"""
|
||||||
根据下载器配置和路径映射,规范化下载路径
|
根据下载器配置和路径映射,规范化下载路径
|
||||||
@@ -300,21 +331,33 @@ class _DownloaderBase(ServiceBase[TService, DownloaderConf]):
|
|||||||
:param downloader: 下载器名称
|
:param downloader: 下载器名称
|
||||||
:return: 规范化后发送给下载器的路径
|
:return: 规范化后发送给下载器的路径
|
||||||
"""
|
"""
|
||||||
dir = path.as_posix()
|
normalized_path = path.as_posix()
|
||||||
conf = self.get_config(downloader)
|
conf = self.get_config(downloader)
|
||||||
if conf and conf.path_mapping:
|
if conf and conf.path_mapping:
|
||||||
for (storage_path, download_path) in conf.path_mapping:
|
for (storage_path, download_path) in conf.path_mapping:
|
||||||
storage_path = Path(storage_path.strip()).as_posix()
|
mapped_path = self.__replace_path_prefix(normalized_path, storage_path, download_path)
|
||||||
download_path = Path(download_path.strip()).as_posix()
|
if mapped_path:
|
||||||
if dir.startswith(storage_path):
|
normalized_path = mapped_path
|
||||||
dir = dir.replace(storage_path, download_path, 1)
|
|
||||||
break
|
break
|
||||||
# 去掉存储协议前缀 if any, 下载器无法识别
|
return self.__strip_storage_prefix(normalized_path)
|
||||||
for s in StorageSchema:
|
|
||||||
prefix = f"{s.value}:"
|
def normalize_return_path(self, path: Path, downloader: Optional[str]) -> str:
|
||||||
if dir.startswith(prefix):
|
"""
|
||||||
return dir[len(prefix):]
|
将下载器返回的路径反向映射为 MoviePilot 可访问的存储路径。
|
||||||
return dir
|
|
||||||
|
:param path: 下载器返回的路径
|
||||||
|
:param downloader: 下载器名称
|
||||||
|
:return: MoviePilot 可访问的路径
|
||||||
|
"""
|
||||||
|
normalized_path = path.as_posix()
|
||||||
|
conf = self.get_config(downloader)
|
||||||
|
if conf and conf.path_mapping:
|
||||||
|
for (storage_path, download_path) in conf.path_mapping:
|
||||||
|
mapped_path = self.__replace_path_prefix(normalized_path, download_path, storage_path)
|
||||||
|
if mapped_path:
|
||||||
|
normalized_path = mapped_path
|
||||||
|
break
|
||||||
|
return self.__strip_storage_prefix(normalized_path)
|
||||||
|
|
||||||
|
|
||||||
class _MediaServerBase(ServiceBase[TService, MediaServerConf]):
|
class _MediaServerBase(ServiceBase[TService, MediaServerConf]):
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]):
|
|||||||
ret_torrents.append(TransferTorrent(
|
ret_torrents.append(TransferTorrent(
|
||||||
downloader=name,
|
downloader=name,
|
||||||
title=torrent.get('name'),
|
title=torrent.get('name'),
|
||||||
path=torrent_path,
|
path=Path(self.normalize_return_path(torrent_path, name)),
|
||||||
hash=torrent.get('hash'),
|
hash=torrent.get('hash'),
|
||||||
size=torrent.get('total_size'),
|
size=torrent.get('total_size'),
|
||||||
tags=torrent.get('tags'),
|
tags=torrent.get('tags'),
|
||||||
@@ -296,7 +296,7 @@ class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]):
|
|||||||
ret_torrents.append(TransferTorrent(
|
ret_torrents.append(TransferTorrent(
|
||||||
downloader=name,
|
downloader=name,
|
||||||
title=torrent.get('name'),
|
title=torrent.get('name'),
|
||||||
path=torrent_path,
|
path=Path(self.normalize_return_path(torrent_path, name)),
|
||||||
hash=torrent.get('hash'),
|
hash=torrent.get('hash'),
|
||||||
tags=torrent.get('tags')
|
tags=torrent.get('tags')
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ class RtorrentModule(_ModuleBase, _DownloaderBase[Rtorrent]):
|
|||||||
TransferTorrent(
|
TransferTorrent(
|
||||||
downloader=name,
|
downloader=name,
|
||||||
title=torrent.get("name"),
|
title=torrent.get("name"),
|
||||||
path=torrent_path,
|
path=Path(self.normalize_return_path(torrent_path, name)),
|
||||||
hash=torrent.get("hash"),
|
hash=torrent.get("hash"),
|
||||||
size=torrent.get("total_size"),
|
size=torrent.get("total_size"),
|
||||||
tags=torrent.get("tags"),
|
tags=torrent.get("tags"),
|
||||||
@@ -355,7 +355,7 @@ class RtorrentModule(_ModuleBase, _DownloaderBase[Rtorrent]):
|
|||||||
TransferTorrent(
|
TransferTorrent(
|
||||||
downloader=name,
|
downloader=name,
|
||||||
title=torrent.get("name"),
|
title=torrent.get("name"),
|
||||||
path=torrent_path,
|
path=Path(self.normalize_return_path(torrent_path, name)),
|
||||||
hash=torrent.get("hash"),
|
hash=torrent.get("hash"),
|
||||||
tags=torrent.get("tags"),
|
tags=torrent.get("tags"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -249,10 +249,11 @@ class TransmissionModule(_ModuleBase, _DownloaderBase[Transmission]):
|
|||||||
torrents, _ = server.get_torrents(ids=hashs, tags=settings.TORRENT_TAG) or []
|
torrents, _ = server.get_torrents(ids=hashs, tags=settings.TORRENT_TAG) or []
|
||||||
try:
|
try:
|
||||||
for torrent in torrents:
|
for torrent in torrents:
|
||||||
|
torrent_path = Path(torrent.download_dir) / torrent.name
|
||||||
ret_torrents.append(TransferTorrent(
|
ret_torrents.append(TransferTorrent(
|
||||||
downloader=name,
|
downloader=name,
|
||||||
title=torrent.name,
|
title=torrent.name,
|
||||||
path=Path(torrent.download_dir) / torrent.name,
|
path=Path(self.normalize_return_path(torrent_path, name)),
|
||||||
hash=torrent.hashString,
|
hash=torrent.hashString,
|
||||||
size=torrent.total_size,
|
size=torrent.total_size,
|
||||||
tags=",".join(torrent.labels or []),
|
tags=",".join(torrent.labels or []),
|
||||||
@@ -276,10 +277,11 @@ class TransmissionModule(_ModuleBase, _DownloaderBase[Transmission]):
|
|||||||
if not path:
|
if not path:
|
||||||
logger.debug(f"未获取到 {torrent.name} 下载保存路径")
|
logger.debug(f"未获取到 {torrent.name} 下载保存路径")
|
||||||
continue
|
continue
|
||||||
|
torrent_path = Path(torrent.download_dir) / torrent.name
|
||||||
ret_torrents.append(TransferTorrent(
|
ret_torrents.append(TransferTorrent(
|
||||||
downloader=name,
|
downloader=name,
|
||||||
title=torrent.name,
|
title=torrent.name,
|
||||||
path=Path(torrent.download_dir) / torrent.name,
|
path=Path(self.normalize_return_path(torrent_path, name)),
|
||||||
hash=torrent.hashString,
|
hash=torrent.hashString,
|
||||||
tags=",".join(torrent.labels or []),
|
tags=",".join(torrent.labels or []),
|
||||||
progress=torrent.progress,
|
progress=torrent.progress,
|
||||||
|
|||||||
@@ -0,0 +1,365 @@
|
|||||||
|
import sys
|
||||||
|
import types
|
||||||
|
import unittest
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
|
||||||
|
def _load_downloader_base():
|
||||||
|
repo_root = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
app_module = types.ModuleType("app")
|
||||||
|
app_module.__path__ = []
|
||||||
|
helper_module = types.ModuleType("app.helper")
|
||||||
|
helper_module.__path__ = []
|
||||||
|
service_module = types.ModuleType("app.helper.service")
|
||||||
|
schemas_module = types.ModuleType("app.schemas")
|
||||||
|
schema_types_module = types.ModuleType("app.schemas.types")
|
||||||
|
utils_module = types.ModuleType("app.utils")
|
||||||
|
utils_module.__path__ = []
|
||||||
|
mixins_module = types.ModuleType("app.utils.mixins")
|
||||||
|
|
||||||
|
class StorageSchema(Enum):
|
||||||
|
Local = "local"
|
||||||
|
Rclone = "rclone"
|
||||||
|
|
||||||
|
class _ConfigReloadMixin:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class _ServiceConfigHelper:
|
||||||
|
@staticmethod
|
||||||
|
def get_downloader_configs():
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_notification_configs():
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_mediaserver_configs():
|
||||||
|
return []
|
||||||
|
|
||||||
|
schema_types_module.StorageSchema = StorageSchema
|
||||||
|
schema_types_module.ModuleType = Enum("ModuleType", {"Downloader": "downloader"})
|
||||||
|
schema_types_module.DownloaderType = Enum("DownloaderType", {"Qbittorrent": "Qbittorrent"})
|
||||||
|
schema_types_module.MediaServerType = Enum("MediaServerType", {"Emby": "Emby"})
|
||||||
|
schema_types_module.MessageChannel = Enum("MessageChannel", {"Telegram": "telegram"})
|
||||||
|
schema_types_module.OtherModulesType = Enum("OtherModulesType", {"Subtitle": "subtitle"})
|
||||||
|
schema_types_module.SystemConfigKey = Enum(
|
||||||
|
"SystemConfigKey",
|
||||||
|
{
|
||||||
|
"Downloaders": "Downloaders",
|
||||||
|
"Notifications": "Notifications",
|
||||||
|
"MediaServers": "MediaServers",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
service_module.ServiceConfigHelper = _ServiceConfigHelper
|
||||||
|
mixins_module.ConfigReloadMixin = _ConfigReloadMixin
|
||||||
|
schemas_module.Notification = object
|
||||||
|
schemas_module.NotificationConf = object
|
||||||
|
schemas_module.MediaServerConf = object
|
||||||
|
schemas_module.DownloaderConf = object
|
||||||
|
|
||||||
|
app_module.helper = helper_module
|
||||||
|
app_module.schemas = schemas_module
|
||||||
|
app_module.utils = utils_module
|
||||||
|
helper_module.service = service_module
|
||||||
|
schemas_module.types = schema_types_module
|
||||||
|
utils_module.mixins = mixins_module
|
||||||
|
|
||||||
|
stub_modules = {
|
||||||
|
"app": app_module,
|
||||||
|
"app.helper": helper_module,
|
||||||
|
"app.helper.service": service_module,
|
||||||
|
"app.schemas": schemas_module,
|
||||||
|
"app.schemas.types": schema_types_module,
|
||||||
|
"app.utils": utils_module,
|
||||||
|
"app.utils.mixins": mixins_module,
|
||||||
|
}
|
||||||
|
|
||||||
|
module_path = repo_root / "app" / "modules" / "__init__.py"
|
||||||
|
module_spec = __import__("importlib.util").util.spec_from_file_location(
|
||||||
|
"_test_downloader_base_module",
|
||||||
|
module_path,
|
||||||
|
)
|
||||||
|
module = __import__("importlib.util").util.module_from_spec(module_spec)
|
||||||
|
assert module_spec and module_spec.loader
|
||||||
|
with patch.dict(sys.modules, stub_modules):
|
||||||
|
module_spec.loader.exec_module(module)
|
||||||
|
return module._DownloaderBase
|
||||||
|
|
||||||
|
|
||||||
|
def _load_transmission_module():
|
||||||
|
repo_root = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
app_module = types.ModuleType("app")
|
||||||
|
app_module.__path__ = []
|
||||||
|
core_module = types.ModuleType("app.core")
|
||||||
|
core_module.__path__ = []
|
||||||
|
cache_module = types.ModuleType("app.core.cache")
|
||||||
|
modules_module = types.ModuleType("app.modules")
|
||||||
|
modules_module.__path__ = []
|
||||||
|
transmission_package_module = types.ModuleType("app.modules.transmission")
|
||||||
|
transmission_package_module.__path__ = []
|
||||||
|
transmission_client_module = types.ModuleType("app.modules.transmission.transmission")
|
||||||
|
schemas_module = types.ModuleType("app.schemas")
|
||||||
|
schema_types_module = types.ModuleType("app.schemas.types")
|
||||||
|
config_module = types.ModuleType("app.core.config")
|
||||||
|
metainfo_module = types.ModuleType("app.core.metainfo")
|
||||||
|
log_module = types.ModuleType("app.log")
|
||||||
|
utils_module = types.ModuleType("app.utils")
|
||||||
|
utils_module.__path__ = []
|
||||||
|
string_module = types.ModuleType("app.utils.string")
|
||||||
|
transmission_rpc_module = types.ModuleType("transmission_rpc")
|
||||||
|
torrentool_module = types.ModuleType("torrentool")
|
||||||
|
torrentool_module.__path__ = []
|
||||||
|
torrentool_torrent_module = types.ModuleType("torrentool.torrent")
|
||||||
|
|
||||||
|
class _ModuleBase:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class _DownloaderBase:
|
||||||
|
def __class_getitem__(cls, _item):
|
||||||
|
return cls
|
||||||
|
|
||||||
|
class _TransferTorrent:
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
|
class _DownloadingTorrent:
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
|
class TorrentStatus(Enum):
|
||||||
|
TRANSFER = "transfer"
|
||||||
|
DOWNLOADING = "downloading"
|
||||||
|
|
||||||
|
class _Logger:
|
||||||
|
def debug(self, *_args, **_kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def info(self, *_args, **_kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def error(self, *_args, **_kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class _MetaInfo:
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.year = None
|
||||||
|
self.season_episode = ""
|
||||||
|
self.episode_list = []
|
||||||
|
|
||||||
|
class _StringUtils:
|
||||||
|
@staticmethod
|
||||||
|
def is_magnet_link(value):
|
||||||
|
return isinstance(value, str) and value.startswith("magnet:")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_random_str(_length):
|
||||||
|
return "tmp-tag-01"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def str_filesize(value):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def str_secends(value):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
class _FileCache:
|
||||||
|
def get(self, *_args, **_kwargs):
|
||||||
|
return None
|
||||||
|
|
||||||
|
transmission_client_module.Transmission = object
|
||||||
|
cache_module.FileCache = _FileCache
|
||||||
|
schemas_module.TransferTorrent = _TransferTorrent
|
||||||
|
schemas_module.DownloadingTorrent = _DownloadingTorrent
|
||||||
|
schemas_module.DownloaderInfo = object
|
||||||
|
schema_types_module.TorrentStatus = TorrentStatus
|
||||||
|
schema_types_module.ModuleType = Enum("ModuleType", {"Downloader": "downloader"})
|
||||||
|
schema_types_module.DownloaderType = Enum(
|
||||||
|
"DownloaderType", {"Transmission": "Transmission"}
|
||||||
|
)
|
||||||
|
config_module.settings = SimpleNamespace(TORRENT_TAG="moviepilot-tag")
|
||||||
|
metainfo_module.MetaInfo = _MetaInfo
|
||||||
|
log_module.logger = _Logger()
|
||||||
|
modules_module._ModuleBase = _ModuleBase
|
||||||
|
modules_module._DownloaderBase = _DownloaderBase
|
||||||
|
string_module.StringUtils = _StringUtils
|
||||||
|
transmission_rpc_module.File = object
|
||||||
|
torrentool_torrent_module.Torrent = SimpleNamespace(
|
||||||
|
from_string=lambda _content: SimpleNamespace(name="test", total_size=1)
|
||||||
|
)
|
||||||
|
|
||||||
|
app_module.core = core_module
|
||||||
|
app_module.modules = modules_module
|
||||||
|
app_module.schemas = schemas_module
|
||||||
|
app_module.utils = utils_module
|
||||||
|
core_module.cache = cache_module
|
||||||
|
core_module.config = config_module
|
||||||
|
core_module.metainfo = metainfo_module
|
||||||
|
modules_module.transmission = transmission_package_module
|
||||||
|
transmission_package_module.transmission = transmission_client_module
|
||||||
|
schemas_module.types = schema_types_module
|
||||||
|
utils_module.string = string_module
|
||||||
|
torrentool_module.torrent = torrentool_torrent_module
|
||||||
|
|
||||||
|
stub_modules = {
|
||||||
|
"app": app_module,
|
||||||
|
"app.core": core_module,
|
||||||
|
"app.core.cache": cache_module,
|
||||||
|
"app.core.config": config_module,
|
||||||
|
"app.core.metainfo": metainfo_module,
|
||||||
|
"app.log": log_module,
|
||||||
|
"app.modules": modules_module,
|
||||||
|
"app.modules.transmission": transmission_package_module,
|
||||||
|
"app.modules.transmission.transmission": transmission_client_module,
|
||||||
|
"app.schemas": schemas_module,
|
||||||
|
"app.schemas.types": schema_types_module,
|
||||||
|
"app.utils": utils_module,
|
||||||
|
"app.utils.string": string_module,
|
||||||
|
"transmission_rpc": transmission_rpc_module,
|
||||||
|
"torrentool": torrentool_module,
|
||||||
|
"torrentool.torrent": torrentool_torrent_module,
|
||||||
|
}
|
||||||
|
|
||||||
|
module_path = repo_root / "app" / "modules" / "transmission" / "__init__.py"
|
||||||
|
module_spec = __import__("importlib.util").util.spec_from_file_location(
|
||||||
|
"_test_transmission_module",
|
||||||
|
module_path,
|
||||||
|
)
|
||||||
|
module = __import__("importlib.util").util.module_from_spec(module_spec)
|
||||||
|
assert module_spec and module_spec.loader
|
||||||
|
with patch.dict(sys.modules, stub_modules):
|
||||||
|
module_spec.loader.exec_module(module)
|
||||||
|
return module.TransmissionModule, TorrentStatus
|
||||||
|
|
||||||
|
|
||||||
|
DownloaderBase = _load_downloader_base()
|
||||||
|
TransmissionModule, TransmissionTorrentStatus = _load_transmission_module()
|
||||||
|
|
||||||
|
|
||||||
|
class DownloaderPathMappingTest(unittest.TestCase):
|
||||||
|
def _build_base(self, path_mapping):
|
||||||
|
downloader = DownloaderBase.__new__(DownloaderBase)
|
||||||
|
downloader.get_config = MagicMock(
|
||||||
|
return_value=SimpleNamespace(path_mapping=path_mapping)
|
||||||
|
)
|
||||||
|
return downloader
|
||||||
|
|
||||||
|
def test_normalize_path_maps_moviepilot_path_to_downloader_path(self):
|
||||||
|
downloader = self._build_base(
|
||||||
|
[("/media", "/mnt/raid5/home_lt999lt")]
|
||||||
|
)
|
||||||
|
|
||||||
|
result = downloader.normalize_path(
|
||||||
|
Path("/media/video/downloads/movie"), "tr"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result, "/mnt/raid5/home_lt999lt/video/downloads/movie")
|
||||||
|
|
||||||
|
def test_normalize_return_path_maps_downloader_path_back_to_moviepilot_path(self):
|
||||||
|
downloader = self._build_base(
|
||||||
|
[("/media", "/mnt/raid5/home_lt999lt")]
|
||||||
|
)
|
||||||
|
|
||||||
|
result = downloader.normalize_return_path(
|
||||||
|
Path("/mnt/raid5/home_lt999lt/video/downloads/TV/Show.mkv"), "tr"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result, "/media/video/downloads/TV/Show.mkv")
|
||||||
|
|
||||||
|
def test_path_mapping_matches_complete_path_segment_only(self):
|
||||||
|
downloader = self._build_base([("/media", "/mnt/media")])
|
||||||
|
|
||||||
|
result = downloader.normalize_return_path(
|
||||||
|
Path("/mnt/media2/Show.mkv"), "tr"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result, "/mnt/media2/Show.mkv")
|
||||||
|
|
||||||
|
def test_blank_path_mapping_entry_is_ignored(self):
|
||||||
|
downloader = self._build_base(
|
||||||
|
[("", "/downloads"), ("/media2", ""), ("/media", "/mnt/media")]
|
||||||
|
)
|
||||||
|
|
||||||
|
result = downloader.normalize_return_path(Path("/mnt/media/Show.mkv"), "tr")
|
||||||
|
|
||||||
|
self.assertEqual(result, "/media/Show.mkv")
|
||||||
|
|
||||||
|
def test_normalize_path_strips_storage_prefix_after_mapping(self):
|
||||||
|
downloader = self._build_base([("local:/media", "/downloads")])
|
||||||
|
|
||||||
|
result = downloader.normalize_path(Path("local:/media/movie"), "qb")
|
||||||
|
|
||||||
|
self.assertEqual(result, "/downloads/movie")
|
||||||
|
|
||||||
|
|
||||||
|
class TransmissionPathMappingTest(unittest.TestCase):
|
||||||
|
def _build_module(self, server):
|
||||||
|
module = TransmissionModule.__new__(TransmissionModule)
|
||||||
|
module.get_instances = MagicMock(return_value={"tr": server})
|
||||||
|
module.get_instance = MagicMock(return_value=server)
|
||||||
|
module.normalize_return_path = MagicMock(
|
||||||
|
side_effect=lambda path, _downloader: str(path).replace(
|
||||||
|
"/mnt/raid5/home_lt999lt", "/media", 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return module
|
||||||
|
|
||||||
|
def test_completed_torrents_return_moviepilot_accessible_path(self):
|
||||||
|
server = MagicMock()
|
||||||
|
server.get_completed_torrents.return_value = [
|
||||||
|
SimpleNamespace(
|
||||||
|
name="Show.S01E01.mkv",
|
||||||
|
download_dir="/mnt/raid5/home_lt999lt/video/downloads/TV",
|
||||||
|
hashString="hash-tr",
|
||||||
|
labels=[],
|
||||||
|
progress=100,
|
||||||
|
status="seeding",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
module = self._build_module(server)
|
||||||
|
|
||||||
|
torrents = module.list_torrents(status=TransmissionTorrentStatus.TRANSFER)
|
||||||
|
|
||||||
|
self.assertEqual(torrents[0].path, Path("/media/video/downloads/TV/Show.S01E01.mkv"))
|
||||||
|
module.normalize_return_path.assert_called_once_with(
|
||||||
|
Path("/mnt/raid5/home_lt999lt/video/downloads/TV/Show.S01E01.mkv"),
|
||||||
|
"tr",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_hash_lookup_return_moviepilot_accessible_path(self):
|
||||||
|
server = MagicMock()
|
||||||
|
server.get_torrents.return_value = (
|
||||||
|
[
|
||||||
|
SimpleNamespace(
|
||||||
|
name="Movie",
|
||||||
|
download_dir="/mnt/raid5/home_lt999lt/video/downloads/movie",
|
||||||
|
hashString="hash-tr",
|
||||||
|
total_size=1024,
|
||||||
|
labels=[],
|
||||||
|
progress=100,
|
||||||
|
status="seeding",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
module = self._build_module(server)
|
||||||
|
|
||||||
|
torrents = module.list_torrents(hashs=["hash-tr"], downloader="tr")
|
||||||
|
|
||||||
|
self.assertEqual(torrents[0].path, Path("/media/video/downloads/movie/Movie"))
|
||||||
|
module.normalize_return_path.assert_called_once_with(
|
||||||
|
Path("/mnt/raid5/home_lt999lt/video/downloads/movie/Movie"),
|
||||||
|
"tr",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user