Files
MoviePilot/app/application/search/state.py
T

136 lines
5.1 KiB
Python

"""搜索参数与结果缓存的应用服务。"""
from typing import Any, Awaitable, Callable, Dict, List, Optional
from app.schemas.media import parse_media_key, resolve_media_identity
from app.schemas.types import MediaSource, MediaType
def stringify_sites(sites: Optional[List[int]]) -> str:
"""将站点 ID 列表转换为前端可复用的逗号分隔值。"""
return ",".join(str(site) for site in sites) if sites else ""
def normalize_search_params(
params: Optional[Dict[str, Any]],
) -> Optional[Dict[str, str]]:
"""把搜索缓存归一为前端重新搜索使用的稳定字段。"""
if not isinstance(params, dict):
return None
media_source, media_id = resolve_media_identity(
media_source=params.get("media_source"),
media_id=params.get("media_id"),
)
keyword = str(params.get("keyword") or "")
if not media_source and keyword:
media_source, media_id = parse_media_key(keyword)
if media_source and media_id:
keyword = ""
normalized = {
"keyword": keyword,
"media_source": str(media_source) if media_source else "",
"media_id": media_id or "",
"type": str(params.get("type") or ""),
"area": str(params.get("area") or ""),
"title": str(params.get("title") or ""),
"year": str(params.get("year") or ""),
"season": str(params["season"]) if params.get("season") is not None else "",
"episode": str(params.get("episode") or ""),
"sites": str(params.get("sites") or ""),
"result_type": str(params.get("result_type") or "torrent"),
}
if params.get("music_type"):
normalized["music_type"] = str(params["music_type"])
return normalized if normalized["keyword"] or media_id else None
class SearchStateService:
"""通过注入的缓存端口保存和读取搜索状态。"""
def __init__(
self,
save_cache: Callable[[Any, str], None],
load_cache: Callable[[str], Any],
async_save_cache: Callable[[Any, str], Awaitable[None]],
async_load_cache: Callable[[str], Awaitable[Any]],
params_key: str,
result_key: str,
subtitle_result_key: str,
) -> None:
"""保存缓存端口和兼容缓存键。"""
self._save_cache = save_cache
self._load_cache = load_cache
self._async_save_cache = async_save_cache
self._async_load_cache = async_load_cache
self._params_key = params_key
self._result_key = result_key
self._subtitle_result_key = subtitle_result_key
@staticmethod
def build_params(
*,
keyword: Optional[str] = None,
media_source: Optional[MediaSource] = None,
media_id: Optional[str] = None,
mtype: Optional[MediaType] = None,
area: Optional[str] = "title",
title: Optional[str] = None,
year: Optional[str] = None,
season: Optional[int] = None,
episode: Optional[int] = None,
sites: Optional[List[int]] = None,
music_type: Optional[str] = None,
result_type: Optional[str] = "torrent",
) -> Optional[Dict[str, str]]:
"""把公开搜索参数构造成可持久化的兼容字典。"""
return normalize_search_params(
{
"keyword": keyword,
"media_source": media_source,
"media_id": media_id,
"type": mtype.value if isinstance(mtype, MediaType) else mtype,
"area": area,
"title": title,
"year": year,
"season": season,
"episode": episode,
"sites": stringify_sites(sites),
"music_type": music_type,
"result_type": result_type or "torrent",
}
)
def save_params(self, **kwargs: Any) -> None:
"""同步保存最后一次有效搜索参数。"""
params = self.build_params(**kwargs)
if params:
self._save_cache(params, self._params_key)
async def async_save_params(self, **kwargs: Any) -> None:
"""异步保存最后一次有效搜索参数。"""
params = self.build_params(**kwargs)
if params:
await self._async_save_cache(params, self._params_key)
def load_params(self) -> Optional[Dict[str, str]]:
"""同步读取并归一化最后一次搜索参数。"""
return normalize_search_params(self._load_cache(self._params_key))
async def async_load_params(self) -> Optional[Dict[str, str]]:
"""异步读取并归一化最后一次搜索参数。"""
return normalize_search_params(await self._async_load_cache(self._params_key))
def load_results(self) -> Any:
"""同步读取最后一次资源搜索结果。"""
return self._load_cache(self._result_key)
async def async_load_results(self) -> Any:
"""异步读取最后一次资源搜索结果。"""
return await self._async_load_cache(self._result_key)
async def async_load_subtitle_results(self) -> Any:
"""异步读取最后一次字幕搜索结果。"""
return await self._async_load_cache(self._subtitle_result_key)