mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
fix(tvdb): 解决无网络环境时,tvdb 模块初始化时,仍然会进入超长等待的问题
- 改为惰性初始化,启动时不再执行 `auth` ,调用方法时,再进行 `auth` (保留 auth_token 过期检查重新 `auth` 的功能); - 使用 双重检查锁定 的方式,保证线程安全; - 统一通过一个 `timeout` 值进行设置,默认值从30秒降为15秒,保持与tmdb相同。
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
from threading import Lock
|
||||
from typing import Optional, Tuple, Union
|
||||
|
||||
from app.core.config import settings
|
||||
@@ -8,41 +9,83 @@ from app.schemas.types import ModuleType, MediaRecognizeType
|
||||
|
||||
|
||||
class TheTvDbModule(_ModuleBase):
|
||||
tvdb: tvdb_v4_official.TVDB = None
|
||||
"""
|
||||
TVDB媒体信息匹配
|
||||
"""
|
||||
__timeout: int = 15
|
||||
tvdb: Optional[tvdb_v4_official.TVDB] = None
|
||||
__auth_lock = Lock()
|
||||
|
||||
def init_module(self) -> None:
|
||||
self._initialize_tvdb_session()
|
||||
pass
|
||||
|
||||
def _initialize_tvdb_session(self) -> None:
|
||||
def _initialize_tvdb_session(self, is_retry: bool = False) -> None:
|
||||
"""
|
||||
创建或刷新 TVDB 登录会话
|
||||
创建或刷新 TVDB 登录会话。
|
||||
:param is_retry: 是否是由于token失效后的重试登录
|
||||
"""
|
||||
action = "刷新" if is_retry else "创建"
|
||||
logger.info(f"开始{action}TVDB登录会话...")
|
||||
try:
|
||||
if not settings.TVDB_V4_API_KEY:
|
||||
raise ConnectionError("TVDB API Key 未配置,无法初始化会话。")
|
||||
self.tvdb = tvdb_v4_official.TVDB(apikey=settings.TVDB_V4_API_KEY,
|
||||
pin=settings.TVDB_V4_API_PIN,
|
||||
proxy=settings.PROXY)
|
||||
proxy=settings.PROXY,
|
||||
timeout=self.__timeout)
|
||||
if self.tvdb:
|
||||
logger.info(f"TVDB登录会话{action}成功。")
|
||||
else:
|
||||
raise ValueError(f"TVDB登录会话{action}后实例仍为None。")
|
||||
except Exception as e:
|
||||
logger.error(f"TVDB 登录失败: {str(e)}")
|
||||
self.tvdb = None
|
||||
raise ConnectionError(f"TVDB登录会话{action}失败: {str(e)}") from e
|
||||
|
||||
def _handle_tvdb_call(self, func, *args, **kwargs):
|
||||
def _ensure_tvdb_session(self, is_retry: bool = False) -> None:
|
||||
"""
|
||||
确保TVDB会话存在。如果不存在或需要强制重新初始化,则进行初始化。
|
||||
:param is_retry: 是否重新初始化(例如token失效时)
|
||||
"""
|
||||
# 第一次检查 (无锁),提高性能,避免不必要锁竞争
|
||||
if not self.tvdb or is_retry:
|
||||
with self.__auth_lock:
|
||||
# 第二次检查 (有锁),防止多个线程都通过第一次检查后重复初始化
|
||||
if not self.tvdb or is_retry:
|
||||
self._initialize_tvdb_session(is_retry=is_retry)
|
||||
|
||||
def _handle_tvdb_call(self, method_name: str, *args, **kwargs):
|
||||
"""
|
||||
包裹 TVDB 调用,处理 token 失效情况并尝试重新初始化
|
||||
:param method_name: 要在 self.tvdb 实例上调用的方法的名称 (字符串)
|
||||
"""
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
self._ensure_tvdb_session()
|
||||
actual_method = getattr(self.tvdb, method_name)
|
||||
return actual_method(*args, **kwargs)
|
||||
except ValueError as e:
|
||||
# 检查错误信息中是否包含 token 失效相关描述
|
||||
if "Unauthorized" in str(e):
|
||||
logger.warning("TVDB Token 可能已失效,正在尝试重新登录...")
|
||||
self._initialize_tvdb_session()
|
||||
return func(*args, **kwargs)
|
||||
elif "NotFoundException" in str(e):
|
||||
logger.warning("TVDB 剧集不存在")
|
||||
try:
|
||||
self._ensure_tvdb_session(is_retry=True)
|
||||
actual_method = getattr(self.tvdb, method_name)
|
||||
return actual_method(*args, **kwargs)
|
||||
except ConnectionError as conn_err:
|
||||
logger.error(f"TVDB Token失效后重新登录失败: {conn_err}")
|
||||
raise
|
||||
elif "NotFoundException" in str(e) or "ID not found" in str(e):
|
||||
logger.warning(f"TVDB 资源未找到 (调用 {method_name}): {e}")
|
||||
return None
|
||||
else:
|
||||
logger.error(f"TVDB 调用 ({method_name}) 时发生未处理的 ValueError: {str(e)}")
|
||||
raise
|
||||
except ConnectionError as e:
|
||||
logger.error(f"TVDB 连接会话错误: {str(e)}")
|
||||
raise
|
||||
except AttributeError as e:
|
||||
logger.error(f"TVDB 实例上没有方法 '{method_name}': {e}")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"TVDB 调用出错: {str(e)}")
|
||||
logger.error(f"TVDB 调用时发生未知错误: {str(e)}", exc_info=True)
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
@@ -71,16 +114,16 @@ class TheTvDbModule(_ModuleBase):
|
||||
return 4
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
logger.info("TheTvDbModule 停止。正在清除 TVDB 会话。")
|
||||
with self.__auth_lock:
|
||||
self.tvdb = None
|
||||
|
||||
def test(self) -> Tuple[bool, str]:
|
||||
"""
|
||||
测试模块连接性
|
||||
"""
|
||||
if not self.tvdb:
|
||||
return False, "TheTVDB 连接失败"
|
||||
try:
|
||||
self._handle_tvdb_call(self.tvdb.get_series, 81189)
|
||||
self._handle_tvdb_call("get_series", 81189)
|
||||
return True, ""
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
@@ -96,7 +139,7 @@ class TheTvDbModule(_ModuleBase):
|
||||
"""
|
||||
try:
|
||||
logger.info(f"开始获取TVDB信息: {tvdbid} ...")
|
||||
return self._handle_tvdb_call(self.tvdb.get_series_extended, tvdbid)
|
||||
return self._handle_tvdb_call("get_series_extended", tvdbid)
|
||||
except Exception as err:
|
||||
logger.error(f"获取TVDB信息失败: {str(err)}")
|
||||
return None
|
||||
@@ -109,8 +152,13 @@ class TheTvDbModule(_ModuleBase):
|
||||
"""
|
||||
try:
|
||||
logger.info(f"开始用标题搜索TVDB剧集: {title} ...")
|
||||
res = self._handle_tvdb_call(self.tvdb.search, title)
|
||||
return [item for item in res if item.get("type") == "series"]
|
||||
res = self._handle_tvdb_call("search", title)
|
||||
if res is None:
|
||||
return []
|
||||
if not isinstance(res, list):
|
||||
logger.warning(f"TVDB 搜索 '{title}' 未返回列表:{type(res)}")
|
||||
return []
|
||||
return [item for item in res if isinstance(item, dict) and item.get("type") == "series"]
|
||||
except Exception as err:
|
||||
logger.error(f"用标题搜索TVDB剧集失败: {str(err)}")
|
||||
logger.error(f"用标题搜索TVDB剧集失败 ({title}): {str(err)}")
|
||||
return []
|
||||
|
||||
Reference in New Issue
Block a user