From 378ba51f4de6d24e77249e6e9efebcff30848043 Mon Sep 17 00:00:00 2001 From: stkevintan Date: Mon, 8 Dec 2025 14:00:39 +0800 Subject: [PATCH] support path_mapping for downloader --- app/chain/__init__.py | 6 ++---- app/chain/download.py | 11 +++++------ app/modules/__init__.py | 25 +++++++++++++++++++++++++ app/modules/qbittorrent/__init__.py | 2 +- app/modules/subtitle/__init__.py | 9 ++++++--- app/modules/transmission/__init__.py | 2 +- app/schemas/system.py | 2 ++ 7 files changed, 42 insertions(+), 15 deletions(-) diff --git a/app/chain/__init__.py b/app/chain/__init__.py index c0dbe2a9..2ee4f0e5 100644 --- a/app/chain/__init__.py +++ b/app/chain/__init__.py @@ -707,19 +707,17 @@ class ChainBase(metaclass=ABCMeta): cookie=cookie, episodes=episodes, category=category, label=label, downloader=downloader) - def download_added(self, context: Context, download_dir: Path, storage: str, torrent_content: Union[str, bytes] = None) -> None: + def download_added(self, context: Context, download_dir: Path, torrent_content: Union[str, bytes] = None) -> None: """ 添加下载任务成功后,从站点下载字幕,保存到下载目录 :param context: 上下文,包括识别信息、媒体信息、种子信息 :param download_dir: 下载目录 - :param storage: 存储类型 :param torrent_content: 种子内容,如果有则直接使用该内容,否则从context中获取种子文件路径 :return: None,该方法可被多个模块同时处理 """ return self.run_module("download_added", context=context, torrent_content=torrent_content, - download_dir=download_dir, - storage=storage) + download_dir=download_dir) def list_torrents(self, status: TorrentStatus = None, hashs: Union[list, str] = None, diff --git a/app/chain/download.py b/app/chain/download.py index f687228f..18672fb0 100644 --- a/app/chain/download.py +++ b/app/chain/download.py @@ -19,7 +19,7 @@ from app.db.mediaserver_oper import MediaServerOper from app.helper.directory import DirectoryHelper from app.helper.torrent import TorrentHelper from app.log import logger -from app.schemas import ExistMediaInfo, NotExistMediaInfo, DownloadingTorrent, Notification, ResourceSelectionEventData, \ +from app.schemas import ExistMediaInfo, FileURI, NotExistMediaInfo, DownloadingTorrent, Notification, ResourceSelectionEventData, \ ResourceDownloadEventData from app.schemas.types import MediaType, TorrentStatus, EventType, MessageChannel, NotificationType, ContentType, \ ChainEventType @@ -235,10 +235,7 @@ class DownloadChain(ChainBase): storage = 'local' # 下载目录 if save_path: - uri = schemas.FileURI.from_uri(save_path) - # 下载目录使用自定义的 - download_dir = Path(uri.path) - storage = uri.storage + download_dir = Path(save_path) else: # 根据媒体信息查询下载目录配置 dir_info = DirectoryHelper().get_dir(_media, include_unsorted=True) @@ -263,6 +260,8 @@ class DownloadChain(ChainBase): self.messagehelper.put(f"{_media.type.value} {_media.title_year} 未找到下载目录!", title="下载失败", role="system") return None + fileURI = FileURI(storage=storage, path=download_dir.as_posix()) + download_dir = Path(fileURI.uri) # 添加下载 result: Optional[tuple] = self.download(content=torrent_content, @@ -362,7 +361,7 @@ class DownloadChain(ChainBase): username=username, ) # 下载成功后处理 - self.download_added(context=context, download_dir=download_dir, storage=storage, torrent_content=torrent_content) + self.download_added(context=context, download_dir=download_dir, torrent_content=torrent_content) # 广播事件 self.eventmanager.send_event(EventType.DownloadAdded, { "hash": _hash, diff --git a/app/modules/__init__.py b/app/modules/__init__.py index 89fe2add..2fef7dc9 100644 --- a/app/modules/__init__.py +++ b/app/modules/__init__.py @@ -1,5 +1,6 @@ from abc import abstractmethod, ABCMeta from typing import Generic, Tuple, Union, TypeVar, Type, Dict, Optional, Callable +from pathlib import Path from app.helper.service import ServiceConfigHelper from app.schemas import Notification, NotificationConf, MediaServerConf, DownloaderConf @@ -290,6 +291,30 @@ class _DownloaderBase(ServiceBase[TService, DownloaderConf]): 重置默认配置名称 """ self._default_config_name = None + + def normalize_path(self, path: Path, downloader: Optional[str]) -> str: + """ + 根据下载器配置和路径映射,规范化下载路径 + + :param path: 原始路径 + :param conf: 下载器配置 + :return: 规范化后的路径 + """ + dir = path.as_posix() + conf = self.get_config(downloader) + if conf and conf.path_mapping: + for (src, dst) in conf.path_mapping: + src = Path(src.strip()).as_posix() + dst = Path(dst.strip()).as_posix() + if dir.startswith(src): + dir = dir.replace(src, dst, 1) + break + # 处理存储协议前缀 + for s in StorageSchema: + prefix = f"{s.value}:" + if dir.startswith(prefix): + return dir[len(prefix):] + return dir class _MediaServerBase(ServiceBase[TService, MediaServerConf]): diff --git a/app/modules/qbittorrent/__init__.py b/app/modules/qbittorrent/__init__.py index 4ee36440..ddb1fdeb 100644 --- a/app/modules/qbittorrent/__init__.py +++ b/app/modules/qbittorrent/__init__.py @@ -150,7 +150,7 @@ class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]): # 添加任务 state = server.add_torrent( content=content, - download_dir=download_dir.as_posix(), + download_dir=self.normalize_path(download_dir, downloader), is_paused=is_paused, tag=tags, cookie=cookie, diff --git a/app/modules/subtitle/__init__.py b/app/modules/subtitle/__init__.py index 8108d240..5b437aeb 100644 --- a/app/modules/subtitle/__init__.py +++ b/app/modules/subtitle/__init__.py @@ -11,7 +11,7 @@ from app.core.context import Context from app.helper.torrent import TorrentHelper from app.log import logger from app.modules import _ModuleBase -from app.schemas.file import FileItem +from app.schemas.file import FileURI from app.schemas.types import ModuleType, OtherModulesType from app.utils.http import RequestUtils from app.utils.string import StringUtils @@ -65,12 +65,11 @@ class SubtitleModule(_ModuleBase): def test(self): pass - def download_added(self, context: Context, download_dir: Path, storage: str, torrent_content: Union[str, bytes] = None): + def download_added(self, context: Context, download_dir: Path, torrent_content: Union[str, bytes] = None): """ 添加下载任务成功后,从站点下载字幕,保存到下载目录 :param context: 上下文,包括识别信息、媒体信息、种子信息 :param download_dir: 下载目录 - :param storage: 存储类型 :param torrent_content: 种子内容,如果是种子文件,则为文件内容,否则为种子字符串 :return: None,该方法可被多个模块同时处理 """ @@ -93,6 +92,10 @@ class SubtitleModule(_ModuleBase): storageChain = StorageChain() # 等待目录存在 working_dir_item = None + # split download_dir into storage and path + fileURI = FileURI.from_uri(download_dir.as_posix()) + storage = fileURI.storage + download_dir = Path(fileURI.path) for _ in range(30): found = storageChain.get_file_item(storage, download_dir / folder_name) if found: diff --git a/app/modules/transmission/__init__.py b/app/modules/transmission/__init__.py index def26677..8b2da1a9 100644 --- a/app/modules/transmission/__init__.py +++ b/app/modules/transmission/__init__.py @@ -151,7 +151,7 @@ class TransmissionModule(_ModuleBase, _DownloaderBase[Transmission]): # 添加任务 torrent = server.add_torrent( content=content, - download_dir=download_dir.as_posix(), + download_dir=self.normalize_path(download_dir, downloader), is_paused=is_paused, labels=labels, cookie=cookie diff --git a/app/schemas/system.py b/app/schemas/system.py index 0e943b8c..2b8e9222 100644 --- a/app/schemas/system.py +++ b/app/schemas/system.py @@ -51,6 +51,8 @@ class DownloaderConf(BaseModel): config: Optional[dict] = Field(default_factory=dict) # 是否启用 enabled: Optional[bool] = False + # 路径映射 + path_mapping: Optional[list[tuple[str, str]]] = Field(default_factory=list) class NotificationConf(BaseModel):