mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
refactor: 收敛 factory 模块级副作用并统一 async 路径进度为异步后端
- 将 configure_token_codec/configure_plugin_routes 移入 create_app(),消除 import 期副作用 - runtime 层新增 AsyncCacheProxy/AsyncTTLCache/AsyncProgressHelper,共享 progress region - search/system/history/scheduler/dashboard 的事件循环路径切换异步进度后端 - 同步进度回调经事件循环提交或线程池执行,避免阻塞事件循环
This commit is contained in:
@@ -1158,6 +1158,143 @@ class CacheProxy:
|
||||
self._cache_backend.close()
|
||||
|
||||
|
||||
class AsyncCacheProxy:
|
||||
"""
|
||||
异步缓存代理类,将异步缓存后端的方法直接代理到实例上
|
||||
|
||||
与同步 CacheProxy 的唯一差异是方法均为 async,默认绑定构造时指定的 region。
|
||||
"""
|
||||
|
||||
def __init__(self, cache_backend: AsyncCacheBackend, region: str):
|
||||
"""
|
||||
初始化异步缓存代理
|
||||
|
||||
:param cache_backend: 异步缓存后端实例
|
||||
:param region: 缓存区域
|
||||
"""
|
||||
self._cache_backend = cache_backend
|
||||
self._region = region
|
||||
|
||||
def is_redis(self) -> bool:
|
||||
"""
|
||||
检查当前缓存后端是否为 Redis(纯状态判断,无需 await)
|
||||
"""
|
||||
return self._cache_backend.is_redis()
|
||||
|
||||
async def get(self, key: str, **kwargs) -> Any:
|
||||
"""
|
||||
获取缓存值
|
||||
"""
|
||||
kwargs.setdefault('region', self._region)
|
||||
return await self._cache_backend.get(key, **kwargs)
|
||||
|
||||
async def set(self, key: str, value: Any, **kwargs) -> None:
|
||||
"""
|
||||
设置缓存值
|
||||
"""
|
||||
kwargs.setdefault('region', self._region)
|
||||
await self._cache_backend.set(key, value, **kwargs)
|
||||
|
||||
async def delete(self, key: str, **kwargs) -> None:
|
||||
"""
|
||||
删除缓存值
|
||||
"""
|
||||
kwargs.setdefault('region', self._region)
|
||||
await self._cache_backend.delete(key, **kwargs)
|
||||
|
||||
async def exists(self, key: str, **kwargs) -> bool:
|
||||
"""
|
||||
检查缓存键是否存在
|
||||
"""
|
||||
kwargs.setdefault('region', self._region)
|
||||
return await self._cache_backend.exists(key, **kwargs)
|
||||
|
||||
async def clear(self, **kwargs) -> None:
|
||||
"""
|
||||
清除缓存
|
||||
"""
|
||||
kwargs.setdefault('region', self._region)
|
||||
await self._cache_backend.clear(**kwargs)
|
||||
|
||||
async def items(self, **kwargs):
|
||||
"""
|
||||
获取所有缓存项
|
||||
"""
|
||||
kwargs.setdefault('region', self._region)
|
||||
async for item in self._cache_backend.items(**kwargs):
|
||||
yield item
|
||||
|
||||
async def keys(self, **kwargs):
|
||||
"""
|
||||
获取所有缓存键
|
||||
"""
|
||||
kwargs.setdefault('region', self._region)
|
||||
async for key in self._cache_backend.keys(**kwargs):
|
||||
yield key
|
||||
|
||||
async def values(self, **kwargs):
|
||||
"""
|
||||
获取所有缓存值
|
||||
"""
|
||||
kwargs.setdefault('region', self._region)
|
||||
async for value in self._cache_backend.values(**kwargs):
|
||||
yield value
|
||||
|
||||
async def update(self, other: Dict[str, Any], **kwargs) -> None:
|
||||
"""
|
||||
更新缓存
|
||||
"""
|
||||
kwargs.setdefault('region', self._region)
|
||||
await self._cache_backend.update(other, **kwargs)
|
||||
|
||||
async def pop(self, key: str, default: Any = None, **kwargs) -> Any:
|
||||
"""
|
||||
弹出缓存项
|
||||
"""
|
||||
kwargs.setdefault('region', self._region)
|
||||
return await self._cache_backend.pop(key, default, **kwargs)
|
||||
|
||||
async def popitem(self, **kwargs) -> Tuple[str, Any]:
|
||||
"""
|
||||
弹出最后一个缓存项
|
||||
"""
|
||||
kwargs.setdefault('region', self._region)
|
||||
return await self._cache_backend.popitem(**kwargs)
|
||||
|
||||
async def setdefault(self, key: str, default: Any = None, **kwargs) -> Any:
|
||||
"""
|
||||
设置默认值
|
||||
"""
|
||||
kwargs.setdefault('region', self._region)
|
||||
return await self._cache_backend.setdefault(key, default, **kwargs)
|
||||
|
||||
async def close(self) -> None:
|
||||
"""
|
||||
关闭缓存连接
|
||||
"""
|
||||
await self._cache_backend.close()
|
||||
|
||||
|
||||
class AsyncTTLCache(AsyncCacheProxy):
|
||||
"""
|
||||
基于 TTL 的异步缓存类,与同步 TTLCache 使用同一 region 语义,
|
||||
内存后端共享进程内存储,Redis 后端共享同一键空间
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
region: Optional[str] = DEFAULT_CACHE_REGION,
|
||||
maxsize: Optional[int] = DEFAULT_CACHE_SIZE,
|
||||
ttl: Optional[int] = DEFAULT_CACHE_TTL):
|
||||
"""
|
||||
初始化异步 TTL 缓存
|
||||
|
||||
:param maxsize: 缓存的最大条目数
|
||||
:param ttl: 缓存的存活时间,单位秒
|
||||
:param region: 缓存的区,为 None 时使用默认区
|
||||
"""
|
||||
super().__init__(AsyncCache(cache_type='ttl', maxsize=maxsize, ttl=ttl), region)
|
||||
|
||||
|
||||
class TTLCache(CacheProxy):
|
||||
"""
|
||||
基于 TTL 的缓存类,兼容 cachetools.TTLCache 接口
|
||||
|
||||
+116
-1
@@ -1,7 +1,7 @@
|
||||
from enum import Enum
|
||||
from typing import Optional, Union
|
||||
|
||||
from app.runtime.cache import TTLCache
|
||||
from app.runtime.cache import AsyncTTLCache, TTLCache
|
||||
from app.runtime.localization import LocaleHelper
|
||||
from app.schemas.types import ProgressKey
|
||||
|
||||
@@ -115,3 +115,118 @@ class ProgressHelper:
|
||||
)
|
||||
detail["data"] = localized_data
|
||||
return detail
|
||||
|
||||
|
||||
class AsyncProgressHelper:
|
||||
"""
|
||||
处理进度辅助类(异步)
|
||||
|
||||
与 ProgressHelper 共用同一个进度 region:内存后端共享进程内存储,
|
||||
Redis 后端共享同一键空间,因此同步写入、异步读取(或反之)均互通。
|
||||
供事件循环上的异步调用方使用,避免同步缓存后端阻塞事件循环。
|
||||
"""
|
||||
|
||||
def __init__(self, key: Union[ProgressKey, str]) -> None:
|
||||
"""为指定业务键绑定独立的异步进度缓存区域。"""
|
||||
if isinstance(key, Enum):
|
||||
key = key.value
|
||||
self._key = key
|
||||
self._progress = AsyncTTLCache(region="progress", maxsize=1024, ttl=24 * 60 * 60)
|
||||
|
||||
async def __reset(self) -> None:
|
||||
"""
|
||||
重置进度
|
||||
"""
|
||||
await self._progress.set(self._key, {
|
||||
"enable": False,
|
||||
"value": 0,
|
||||
"text": "请稍候...",
|
||||
"data": {}
|
||||
})
|
||||
|
||||
async def start(self) -> None:
|
||||
"""
|
||||
开始进度
|
||||
"""
|
||||
await self.__reset()
|
||||
current = await self._progress.get(self._key)
|
||||
if not current:
|
||||
return
|
||||
current['enable'] = True
|
||||
await self._progress.set(self._key, current)
|
||||
|
||||
async def end(
|
||||
self,
|
||||
text: Optional[str] = "",
|
||||
data: Optional[dict] = None,
|
||||
value: Optional[Union[float, int]] = 100,
|
||||
) -> None:
|
||||
"""
|
||||
结束进度
|
||||
"""
|
||||
current = await self._progress.get(self._key)
|
||||
if not current:
|
||||
return
|
||||
if data is not None:
|
||||
if not current.get('data'):
|
||||
current['data'] = {}
|
||||
current['data'].update(data)
|
||||
current["enable"] = False
|
||||
if value is not None:
|
||||
current["value"] = max(min(float(value), 100), 0)
|
||||
current["text"] = text or ""
|
||||
await self._progress.set(self._key, current)
|
||||
|
||||
async def update(
|
||||
self,
|
||||
value: Optional[Union[float, int]] = None,
|
||||
text: Optional[str] = None,
|
||||
data: Optional[dict] = None,
|
||||
) -> None:
|
||||
"""
|
||||
更新进度
|
||||
"""
|
||||
current = await self._progress.get(self._key)
|
||||
if not current or not current.get('enable'):
|
||||
return
|
||||
if value is not None:
|
||||
current['value'] = max(min(float(value), 100), 0)
|
||||
if text is not None:
|
||||
current['text'] = text
|
||||
if data is not None:
|
||||
if not current.get('data'):
|
||||
current['data'] = {}
|
||||
current['data'].update(data)
|
||||
await self._progress.set(self._key, current)
|
||||
|
||||
async def get(self, locale: Optional[str] = None) -> Optional[dict]:
|
||||
"""
|
||||
获取当前进度,并按语言补充前端展示字段。
|
||||
|
||||
:param locale: 目标语言,未传入时使用当前请求上下文语言
|
||||
:return: 当前进度字典
|
||||
"""
|
||||
current = await self._progress.get(self._key)
|
||||
if not current:
|
||||
return current
|
||||
|
||||
detail = current.copy()
|
||||
text = detail.get("text")
|
||||
if isinstance(text, str):
|
||||
detail["text_i18n"] = LocaleHelper.translate_text(text, locale=locale)
|
||||
|
||||
data = detail.get("data")
|
||||
if isinstance(data, dict):
|
||||
localized_data = data.copy()
|
||||
error = localized_data.get("error")
|
||||
message = localized_data.get("message")
|
||||
if isinstance(error, str):
|
||||
localized_data["error_i18n"] = LocaleHelper.translate_text(
|
||||
error, locale=locale
|
||||
)
|
||||
if isinstance(message, str):
|
||||
localized_data["message_i18n"] = LocaleHelper.translate_text(
|
||||
message, locale=locale
|
||||
)
|
||||
detail["data"] = localized_data
|
||||
return detail
|
||||
|
||||
Reference in New Issue
Block a user