mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
refactor:新增文件缓存组合
This commit is contained in:
+35
-108
@@ -7,13 +7,11 @@ from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Optional, Any, Tuple, List, Set, Union, Dict
|
||||
|
||||
import aiofiles
|
||||
from anyio import Path as AsyncPath
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from qbittorrentapi import TorrentFilesList
|
||||
from transmission_rpc import File
|
||||
|
||||
from app.core.cache import get_cache_backend
|
||||
from app.core.cache import get_file_cache_backend, get_async_file_cache_backend
|
||||
from app.core.config import settings
|
||||
from app.core.context import Context, MediaInfo, TorrentInfo
|
||||
from app.core.event import EventManager
|
||||
@@ -48,137 +46,66 @@ class ChainBase(metaclass=ABCMeta):
|
||||
send_callback=self.run_module
|
||||
)
|
||||
self.pluginmanager = PluginManager()
|
||||
# 文件类缓存,保留1
|
||||
self._cache = get_cache_backend(ttl=30 * 24 * 3600)
|
||||
self.filecache = get_file_cache_backend()
|
||||
self.async_filecache = get_async_file_cache_backend()
|
||||
|
||||
def load_cache(self, filename: str) -> Any:
|
||||
"""
|
||||
加载缓存,优先从Redis读取,没有数据时从本地读取(兼容存量未迁移数据)
|
||||
加载缓存
|
||||
"""
|
||||
# 如果Redis可用,优先从Redis读取
|
||||
if self._cache.is_redis():
|
||||
try:
|
||||
cache_data = self._cache.get(filename, region="chain_cache")
|
||||
if cache_data is not None:
|
||||
logger.debug(f"从Redis加载缓存: {filename}")
|
||||
return cache_data
|
||||
except Exception as e:
|
||||
logger.warning(f"从Redis加载缓存 {filename} 失败: {e}")
|
||||
|
||||
# 从本地文件读取(兼容存量数据)
|
||||
cache_path = settings.TEMP_PATH / filename
|
||||
if cache_path.exists():
|
||||
try:
|
||||
with open(cache_path, 'rb') as f:
|
||||
return pickle.load(f)
|
||||
except Exception as err:
|
||||
logger.error(f"加载缓存 {filename} 出错:{str(err)}")
|
||||
return None
|
||||
content = self.filecache.get(filename)
|
||||
if not content:
|
||||
return None
|
||||
try:
|
||||
return pickle.loads(content)
|
||||
except Exception as err:
|
||||
logger.error(f"加载缓存 {filename} 出错:{str(err)}")
|
||||
return None
|
||||
|
||||
async def async_load_cache(self, filename: str) -> Any:
|
||||
"""
|
||||
异步加载缓存,优先从Redis读取,没有数据时从本地读取(兼容存量未迁移数据)
|
||||
异步加载缓存
|
||||
"""
|
||||
# 如果Redis可用,优先从Redis读取
|
||||
if self._cache.is_redis():
|
||||
try:
|
||||
cache_data = self._cache.get(filename, region="chain_cache")
|
||||
if cache_data is not None:
|
||||
logger.debug(f"从Redis异步加载缓存: {filename}")
|
||||
return cache_data
|
||||
except Exception as e:
|
||||
logger.warning(f"从Redis异步加载缓存 {filename} 失败: {e}")
|
||||
|
||||
# 从本地文件读取(兼容存量数据)
|
||||
cache_path = settings.TEMP_PATH / filename
|
||||
if cache_path.exists():
|
||||
try:
|
||||
async with aiofiles.open(cache_path, 'rb') as f:
|
||||
content = await f.read()
|
||||
return pickle.loads(content)
|
||||
except Exception as err:
|
||||
logger.error(f"异步加载缓存 {filename} 出错:{str(err)}")
|
||||
return None
|
||||
content = await self.async_filecache.get(filename)
|
||||
if not content:
|
||||
return None
|
||||
try:
|
||||
return pickle.loads(content)
|
||||
except Exception as err:
|
||||
logger.error(f"异步加载缓存 {filename} 出错:{str(err)}")
|
||||
return None
|
||||
|
||||
async def async_save_cache(self, cache: Any, filename: str) -> None:
|
||||
"""
|
||||
异步保存缓存,优先保存到Redis,同时保存到本地作为备份
|
||||
异步保存缓存
|
||||
"""
|
||||
# 如果Redis可用,优先保存到Redis
|
||||
if self._cache.is_redis():
|
||||
try:
|
||||
self._cache.set(filename, cache, region="chain_cache")
|
||||
logger.debug(f"异步保存缓存到Redis: {filename}")
|
||||
except Exception as e:
|
||||
logger.warning(f"异步保存缓存到Redis失败: {e}")
|
||||
else:
|
||||
# 保存到本地
|
||||
try:
|
||||
async with aiofiles.open(settings.TEMP_PATH / filename, 'wb') as f:
|
||||
await f.write(pickle.dumps(cache))
|
||||
except Exception as err:
|
||||
logger.error(f"异步保存缓存到本地 {filename} 出错:{str(err)}")
|
||||
try:
|
||||
await self.async_filecache.set(filename, pickle.dumps(cache))
|
||||
except Exception as err:
|
||||
logger.error(f"异步保存缓存 {filename} 出错:{str(err)}")
|
||||
return
|
||||
|
||||
def save_cache(self, cache: Any, filename: str) -> None:
|
||||
"""
|
||||
保存缓存,优先保存到Redis,同时保存到本地作为备份
|
||||
保存缓存
|
||||
"""
|
||||
# 如果Redis可用,优先保存到Redis
|
||||
if self._cache.is_redis():
|
||||
try:
|
||||
self._cache.set(filename, cache, region="chain_cache")
|
||||
logger.debug(f"保存缓存到Redis: {filename}")
|
||||
except Exception as e:
|
||||
logger.warning(f"保存缓存到Redis失败: {e}")
|
||||
else:
|
||||
# 保存到本地
|
||||
try:
|
||||
with open(settings.TEMP_PATH / filename, 'wb') as f:
|
||||
pickle.dump(cache, f) # noqa
|
||||
except Exception as err:
|
||||
logger.error(f"保存缓存到本地 {filename} 出错:{str(err)}")
|
||||
try:
|
||||
self.filecache.set(filename, pickle.dumps(cache))
|
||||
except Exception as err:
|
||||
logger.error(f"保存缓存 {filename} 出错:{str(err)}")
|
||||
return
|
||||
|
||||
def remove_cache(self, filename: str) -> None:
|
||||
"""
|
||||
删除缓存,同时删除Redis和本地缓存
|
||||
"""
|
||||
# 如果Redis可用,删除Redis缓存
|
||||
if self._cache.is_redis():
|
||||
try:
|
||||
self._cache.delete(filename, region="chain_cache")
|
||||
logger.debug(f"删除Redis缓存: {filename}")
|
||||
except Exception as e:
|
||||
logger.warning(f"删除Redis缓存失败: {e}")
|
||||
|
||||
# 删除本地缓存
|
||||
cache_path = settings.TEMP_PATH / filename
|
||||
if cache_path.exists():
|
||||
try:
|
||||
cache_path.unlink()
|
||||
logger.debug(f"删除本地缓存: {filename}")
|
||||
except Exception as e:
|
||||
logger.warning(f"删除本地缓存失败: {e}")
|
||||
self.filecache.delete(filename)
|
||||
|
||||
async def async_remove_cache(self, filename: str) -> None:
|
||||
"""
|
||||
异步删除缓存,同时删除Redis和本地缓存
|
||||
"""
|
||||
# 如果Redis可用,删除Redis缓存
|
||||
if self._cache.is_redis():
|
||||
try:
|
||||
self._cache.delete(filename, region="chain_cache")
|
||||
logger.debug(f"异步删除Redis缓存: {filename}")
|
||||
except Exception as e:
|
||||
logger.warning(f"异步删除Redis缓存失败: {e}")
|
||||
|
||||
# 删除本地缓存
|
||||
cache_path = AsyncPath(settings.TEMP_PATH) / filename
|
||||
if await cache_path.exists():
|
||||
try:
|
||||
await cache_path.unlink()
|
||||
logger.debug(f"异步删除本地缓存: {filename}")
|
||||
except Exception as err:
|
||||
logger.error(f"异步删除本地缓存 {filename} 出错:{str(err)}")
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def __is_valid_empty(ret):
|
||||
|
||||
+17
-42
@@ -1,5 +1,4 @@
|
||||
import io
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
@@ -10,7 +9,7 @@ from app.chain import ChainBase
|
||||
from app.chain.bangumi import BangumiChain
|
||||
from app.chain.douban import DoubanChain
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.core.cache import cached
|
||||
from app.core.cache import cached, get_file_cache_backend
|
||||
from app.core.config import settings, global_vars
|
||||
from app.log import logger
|
||||
from app.schemas import MediaType
|
||||
@@ -37,8 +36,6 @@ class RecommendChain(ChainBase, metaclass=Singleton):
|
||||
刷新推荐
|
||||
"""
|
||||
logger.debug("Starting to refresh Recommend data.")
|
||||
self._cache.clear(region=self.recommend_cache_region)
|
||||
logger.debug("Recommend Cache has been cleared.")
|
||||
|
||||
# 推荐来源方法
|
||||
recommend_methods = [
|
||||
@@ -100,33 +97,26 @@ class RecommendChain(ChainBase, metaclass=Singleton):
|
||||
logger.debug(f"Caching poster image: {poster_url}")
|
||||
self.__fetch_and_save_image(poster_url)
|
||||
|
||||
def __fetch_and_save_image(self, url: str):
|
||||
@staticmethod
|
||||
def __fetch_and_save_image(url: str):
|
||||
"""
|
||||
请求并保存图片
|
||||
:param url: 图片路径
|
||||
"""
|
||||
# 生成缓存路径
|
||||
sanitized_path = SecurityUtils.sanitize_url_path(url)
|
||||
cache_path = settings.CACHE_PATH / "images" / sanitized_path
|
||||
cache_path = Path("images") / sanitized_path
|
||||
# 没有文件类型,则添加后缀,在恶意文件类型和实际需求下的折衷选择
|
||||
if not cache_path.suffix:
|
||||
cache_path = cache_path.with_suffix(".jpg")
|
||||
|
||||
if self._cache.is_redis():
|
||||
if self._cache.get(sanitized_path, region=self.recommend_cache_region):
|
||||
logger.debug(f"Cache hit: Image already exists for URL: {url}")
|
||||
return
|
||||
else:
|
||||
# 没有文件类型,则添加后缀,在恶意文件类型和实际需求下的折衷选择
|
||||
if not cache_path.suffix:
|
||||
cache_path = cache_path.with_suffix(".jpg")
|
||||
# 获取缓存后端
|
||||
cache_backend = get_file_cache_backend(base=settings.CACHE_PATH)
|
||||
|
||||
# 确保缓存路径和文件类型合法
|
||||
if not SecurityUtils.is_safe_path(settings.CACHE_PATH, cache_path, settings.SECURITY_IMAGE_SUFFIXES):
|
||||
logger.debug(f"Invalid cache path or file type for URL: {url}, sanitized path: {sanitized_path}")
|
||||
return
|
||||
|
||||
# 本地存在缓存图片,则直接跳过
|
||||
if cache_path.exists():
|
||||
logger.debug(f"Cache hit: Image already exists at {cache_path}")
|
||||
return
|
||||
# 本地存在缓存图片,则直接跳过
|
||||
if cache_backend.get(cache_path.as_posix(), region="images"):
|
||||
logger.debug(f"Cache hit: Image already exists at {cache_path}")
|
||||
return
|
||||
|
||||
# 请求远程图片
|
||||
referer = "https://movie.douban.com/" if "doubanio.com" in url else None
|
||||
@@ -142,25 +132,10 @@ class RecommendChain(ChainBase, metaclass=Singleton):
|
||||
except Exception as e:
|
||||
logger.debug(f"Invalid image format for URL {url}: {e}")
|
||||
return
|
||||
if self._cache.is_redis():
|
||||
# 如果是Redis缓存,直接存储到缓存中
|
||||
try:
|
||||
self._cache.set(sanitized_path, response.content, region=self.recommend_cache_region)
|
||||
logger.debug(f"Successfully cached image for URL: {url} in Redis.")
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to cache image for URL {url} in Redis: {e}")
|
||||
else:
|
||||
# 如果是本地文件缓存,写入到指定路径
|
||||
try:
|
||||
if not cache_path.parent.exists():
|
||||
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.NamedTemporaryFile(dir=cache_path.parent, delete=False) as tmp_file:
|
||||
tmp_file.write(response.content)
|
||||
temp_path = Path(tmp_file.name)
|
||||
temp_path.replace(cache_path)
|
||||
logger.debug(f"Successfully cached image at {cache_path} for URL: {url}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to write cache file {cache_path} for URL {url}: {e}")
|
||||
|
||||
# 保存缓存
|
||||
cache_backend.set(cache_path.as_posix(), response.content, region="images")
|
||||
logger.debug(f"Successfully cached image at {cache_path} for URL: {url}")
|
||||
|
||||
@log_execution_time(logger=logger)
|
||||
@cached(ttl=recommend_ttl, region=recommend_cache_region)
|
||||
|
||||
Reference in New Issue
Block a user