mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-07-08 17:02:41 +08:00
fix downloader task status queries
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""查询下载工具"""
|
||||
|
||||
import json
|
||||
from typing import Any, Dict, List, Optional, Type, Union
|
||||
from typing import Any, Dict, List, Optional, Type
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -10,8 +10,8 @@ from app.agent.tools.tags import ToolTag
|
||||
from app.chain.download import DownloadChain
|
||||
from app.db.downloadhistory_oper import DownloadHistoryOper
|
||||
from app.log import logger
|
||||
from app.schemas import TransferTorrent, DownloadingTorrent
|
||||
from app.schemas.types import TorrentStatus, media_type_to_agent
|
||||
from app.schemas import DownloaderTorrent
|
||||
from app.schemas.types import TorrentQueryStatus, media_type_to_agent
|
||||
|
||||
|
||||
class QueryDownloadTasksInput(BaseModel):
|
||||
@@ -21,6 +21,10 @@ class QueryDownloadTasksInput(BaseModel):
|
||||
description="Name of specific downloader to query (optional, if not provided queries all configured downloaders)")
|
||||
status: Optional[str] = Field("all",
|
||||
description="Filter downloads by status: 'downloading' for active downloads, 'completed' for finished downloads, 'paused' for paused downloads, 'all' for all downloads")
|
||||
include_all_tags: Optional[bool] = Field(
|
||||
False,
|
||||
description="Include tasks without the MoviePilot built-in tag. Default false keeps the normal MoviePilot task scope.",
|
||||
)
|
||||
hash: Optional[str] = Field(None, description="Query specific download task by hash (optional, if provided will search for this specific task regardless of status)")
|
||||
title: Optional[str] = Field(None, description="Query download tasks by title/name (optional, supports partial match, searches all tasks if provided)")
|
||||
tag: Optional[str] = Field(None, description="Filter download tasks by tag (optional, supports partial match, e.g. 'movie' will match tasks with tag 'movie' or 'movie_2024')")
|
||||
@@ -36,26 +40,45 @@ class QueryDownloadTasksTool(MoviePilotTool):
|
||||
args_schema: Type[BaseModel] = QueryDownloadTasksInput
|
||||
|
||||
@staticmethod
|
||||
def _get_all_torrents(download_chain: DownloadChain, downloader: Optional[str] = None) -> List[Union[TransferTorrent, DownloadingTorrent]]:
|
||||
def _normalize_query_status(status: Optional[str]) -> TorrentQueryStatus:
|
||||
"""
|
||||
归一下载任务查询状态。
|
||||
"""
|
||||
status_value = str(status or "").strip().lower()
|
||||
if not status_value or status_value == TorrentQueryStatus.ALL.value:
|
||||
return TorrentQueryStatus.ALL
|
||||
if status_value in {"completed", "complete", "seeding"}:
|
||||
return TorrentQueryStatus.COMPLETED
|
||||
if status_value in {"paused", "pause"}:
|
||||
return TorrentQueryStatus.PAUSED
|
||||
if status_value == TorrentQueryStatus.DOWNLOADING.value:
|
||||
return TorrentQueryStatus.DOWNLOADING
|
||||
return TorrentQueryStatus.ALL
|
||||
|
||||
@staticmethod
|
||||
def _normalize_include_all_tags(include_all_tags: Any) -> bool:
|
||||
"""
|
||||
归一全部标签查询开关。
|
||||
"""
|
||||
if isinstance(include_all_tags, bool):
|
||||
return include_all_tags
|
||||
if isinstance(include_all_tags, str):
|
||||
return include_all_tags.strip().lower() in {"1", "true", "yes", "on", "是"}
|
||||
return bool(include_all_tags)
|
||||
|
||||
@staticmethod
|
||||
def _get_all_torrents(
|
||||
download_chain: DownloadChain,
|
||||
downloader: Optional[str] = None,
|
||||
include_all_tags: bool = False,
|
||||
) -> List[DownloaderTorrent]:
|
||||
"""
|
||||
查询所有状态的任务(包括下载中和已完成的任务)
|
||||
"""
|
||||
all_torrents = []
|
||||
# 查询下载的任务
|
||||
downloading_torrents = download_chain.list_torrents(
|
||||
downloader=downloader,
|
||||
status=TorrentStatus.DOWNLOADING
|
||||
) or []
|
||||
all_torrents.extend(downloading_torrents)
|
||||
|
||||
# 查询已完成的任务(可转移状态)
|
||||
transfer_torrents = download_chain.list_torrents(
|
||||
return download_chain.list_torrents(
|
||||
downloader=downloader,
|
||||
status=TorrentStatus.TRANSFER
|
||||
include_all_tags=include_all_tags,
|
||||
) or []
|
||||
all_torrents.extend(transfer_torrents)
|
||||
|
||||
return all_torrents
|
||||
|
||||
@staticmethod
|
||||
def _format_progress(progress: Optional[float]) -> Optional[str]:
|
||||
@@ -71,7 +94,7 @@ class QueryDownloadTasksTool(MoviePilotTool):
|
||||
|
||||
@staticmethod
|
||||
def _apply_download_history(
|
||||
torrent: Union[TransferTorrent, DownloadingTorrent], history: Any
|
||||
torrent: DownloaderTorrent, history: Any
|
||||
) -> None:
|
||||
"""将下载历史中的补充信息回填到下载任务结果中。"""
|
||||
if not history:
|
||||
@@ -91,7 +114,7 @@ class QueryDownloadTasksTool(MoviePilotTool):
|
||||
|
||||
@classmethod
|
||||
def _load_history_map(
|
||||
cls, torrents: List[Union[TransferTorrent, DownloadingTorrent]]
|
||||
cls, torrents: List[DownloaderTorrent]
|
||||
) -> Dict[str, Any]:
|
||||
"""批量加载下载历史,避免逐条查询形成 N+1。"""
|
||||
hashes = [torrent.hash for torrent in torrents if getattr(torrent, "hash", None)]
|
||||
@@ -107,15 +130,22 @@ class QueryDownloadTasksTool(MoviePilotTool):
|
||||
hash_value: Optional[str] = None,
|
||||
title: Optional[str] = None,
|
||||
tag: Optional[str] = None,
|
||||
include_all_tags: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
同步查询下载器和下载历史,整个链路放在线程池中执行。
|
||||
"""
|
||||
download_chain = DownloadChain()
|
||||
query_status = cls._normalize_query_status(status)
|
||||
include_all_tags = cls._normalize_include_all_tags(include_all_tags)
|
||||
|
||||
if hash_value:
|
||||
torrents = (
|
||||
download_chain.list_torrents(downloader=downloader, hashs=[hash_value])
|
||||
download_chain.list_torrents(
|
||||
downloader=downloader,
|
||||
hashs=[hash_value],
|
||||
include_all_tags=include_all_tags,
|
||||
)
|
||||
or []
|
||||
)
|
||||
if not torrents:
|
||||
@@ -128,7 +158,11 @@ class QueryDownloadTasksTool(MoviePilotTool):
|
||||
cls._apply_download_history(torrent, history_map.get(torrent.hash))
|
||||
filtered_downloads = list(torrents)
|
||||
elif title:
|
||||
all_torrents = cls._get_all_torrents(download_chain, downloader)
|
||||
all_torrents = cls._get_all_torrents(
|
||||
download_chain,
|
||||
downloader,
|
||||
include_all_tags=include_all_tags,
|
||||
)
|
||||
history_map = cls._load_history_map(all_torrents)
|
||||
filtered_downloads = []
|
||||
title_lower = title.lower()
|
||||
@@ -150,7 +184,7 @@ class QueryDownloadTasksTool(MoviePilotTool):
|
||||
if not filtered_downloads:
|
||||
return {"message": f"未找到标题包含 '{title}' 的下载任务"}
|
||||
else:
|
||||
if status == "downloading":
|
||||
if query_status == TorrentQueryStatus.DOWNLOADING and not include_all_tags:
|
||||
downloads = download_chain.downloading(name=downloader) or []
|
||||
filtered_downloads = [
|
||||
dl
|
||||
@@ -158,19 +192,12 @@ class QueryDownloadTasksTool(MoviePilotTool):
|
||||
if not downloader or dl.downloader == downloader
|
||||
]
|
||||
else:
|
||||
all_torrents = cls._get_all_torrents(download_chain, downloader)
|
||||
filtered_downloads = []
|
||||
for torrent in all_torrents:
|
||||
if downloader and torrent.downloader != downloader:
|
||||
continue
|
||||
if status == "completed" and torrent.state not in [
|
||||
"seeding",
|
||||
"completed",
|
||||
]:
|
||||
continue
|
||||
if status == "paused" and torrent.state != "paused":
|
||||
continue
|
||||
filtered_downloads.append(torrent)
|
||||
list_status = None if query_status == TorrentQueryStatus.ALL else query_status.value
|
||||
filtered_downloads = download_chain.list_torrents(
|
||||
downloader=downloader,
|
||||
status=list_status,
|
||||
include_all_tags=include_all_tags,
|
||||
) or []
|
||||
|
||||
history_map = cls._load_history_map(filtered_downloads)
|
||||
for torrent in filtered_downloads:
|
||||
@@ -195,6 +222,9 @@ class QueryDownloadTasksTool(MoviePilotTool):
|
||||
status = kwargs.get("status", "all")
|
||||
hash_value = kwargs.get("hash")
|
||||
title = kwargs.get("title")
|
||||
include_all_tags = self._normalize_include_all_tags(
|
||||
kwargs.get("include_all_tags", False)
|
||||
)
|
||||
|
||||
parts = ["查询下载任务"]
|
||||
|
||||
@@ -213,6 +243,8 @@ class QueryDownloadTasksTool(MoviePilotTool):
|
||||
tag = kwargs.get("tag")
|
||||
if tag:
|
||||
parts.append(f"标签: {tag}")
|
||||
if include_all_tags:
|
||||
parts.append("范围: 全部标签")
|
||||
|
||||
return " | ".join(parts) if len(parts) > 1 else parts[0]
|
||||
|
||||
@@ -220,8 +252,13 @@ class QueryDownloadTasksTool(MoviePilotTool):
|
||||
status: Optional[str] = "all",
|
||||
hash: Optional[str] = None,
|
||||
title: Optional[str] = None,
|
||||
tag: Optional[str] = None, **kwargs) -> str:
|
||||
logger.info(f"执行工具: {self.name}, 参数: downloader={downloader}, status={status}, hash={hash}, title={title}, tag={tag}")
|
||||
tag: Optional[str] = None,
|
||||
include_all_tags: Optional[bool] = False,
|
||||
**kwargs) -> str:
|
||||
logger.info(
|
||||
f"执行工具: {self.name}, 参数: downloader={downloader}, status={status}, "
|
||||
f"hash={hash}, title={title}, tag={tag}, include_all_tags={include_all_tags}"
|
||||
)
|
||||
try:
|
||||
payload = await self.run_blocking(
|
||||
"downloader",
|
||||
@@ -231,6 +268,7 @@ class QueryDownloadTasksTool(MoviePilotTool):
|
||||
hash,
|
||||
title,
|
||||
tag,
|
||||
self._normalize_include_all_tags(include_all_tags),
|
||||
)
|
||||
if payload.get("message"):
|
||||
return payload["message"]
|
||||
|
||||
Reference in New Issue
Block a user