mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-12 02:47:11 +08:00
fix: avoid blocking Ugreen startup on library preload
Delay Ugreen library loading until it is needed and cap poster wall pagination so a single Ugreen server cannot hang backend startup.\n\nFixes #5740
This commit is contained in:
@@ -15,6 +15,8 @@ from app.utils.url import UrlUtils
|
||||
|
||||
|
||||
class Ugreen:
|
||||
LIBRARY_PATH_PAGE_LIMIT = 200
|
||||
|
||||
_username: Optional[str] = None
|
||||
_password: Optional[str] = None
|
||||
|
||||
@@ -171,11 +173,13 @@ class Ugreen:
|
||||
if not self.is_configured():
|
||||
return False
|
||||
|
||||
self._libraries = {}
|
||||
self._library_paths = {}
|
||||
|
||||
# 关闭旧连接(不主动登出,避免破坏可复用会话)
|
||||
self.disconnect(logout=False)
|
||||
|
||||
if self.__restore_persisted_session():
|
||||
self.get_librarys()
|
||||
return True
|
||||
|
||||
self._api = Api(host=self._host, verify_ssl=self._verify_ssl)
|
||||
@@ -191,7 +195,6 @@ class Ugreen:
|
||||
# 登录成功后持久化参数,下次优先复用
|
||||
self.__save_persisted_session()
|
||||
logger.debug(f"{self._username} 成功登录绿联影视")
|
||||
self.get_librarys()
|
||||
return True
|
||||
|
||||
def disconnect(self, logout: bool = False):
|
||||
@@ -204,6 +207,8 @@ class Ugreen:
|
||||
self._api = None
|
||||
self._userinfo = None
|
||||
logger.debug(f"{self._username} 已断开绿联影视")
|
||||
self._libraries = {}
|
||||
self._library_paths = {}
|
||||
|
||||
@staticmethod
|
||||
def __normalize_dir_path(path: Union[str, Path, None]) -> str:
|
||||
@@ -487,7 +492,7 @@ class Ugreen:
|
||||
|
||||
paths: dict[str, str] = {}
|
||||
page = 1
|
||||
while True:
|
||||
while page <= self.LIBRARY_PATH_PAGE_LIMIT:
|
||||
data = self._api.poster_wall_get_folder(page=page, page_size=100)
|
||||
if not data:
|
||||
break
|
||||
@@ -502,6 +507,12 @@ class Ugreen:
|
||||
break
|
||||
page += 1
|
||||
|
||||
if page > self.LIBRARY_PATH_PAGE_LIMIT:
|
||||
# 部分固件分页标志异常时会无限返回下一页,这里加硬限制避免阻塞调用方。
|
||||
logger.warning(
|
||||
f"绿联影视 {self._username} 媒体库目录分页超过上限 {self.LIBRARY_PATH_PAGE_LIMIT} 页,停止继续加载"
|
||||
)
|
||||
|
||||
return paths
|
||||
|
||||
def get_librarys(self, hidden: Optional[bool] = False) -> List[schemas.MediaServerLibrary]:
|
||||
|
||||
@@ -101,6 +101,50 @@ class _FakeUgreenApi:
|
||||
return {"total_num": 0}
|
||||
|
||||
|
||||
class _FakeReconnectApi:
|
||||
token = "test-token"
|
||||
|
||||
@staticmethod
|
||||
def login(_username, _password):
|
||||
return "test-token"
|
||||
|
||||
@staticmethod
|
||||
def current_user():
|
||||
return {"name": "tester"}
|
||||
|
||||
@staticmethod
|
||||
def close():
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def export_session_state():
|
||||
return {"token": "test-token", "public_key": "public-key"}
|
||||
|
||||
|
||||
class _PagedFolderApi:
|
||||
def __init__(self, stop_after: int | None = None):
|
||||
self.calls = 0
|
||||
self.pages = []
|
||||
self.stop_after = stop_after
|
||||
|
||||
def poster_wall_get_folder(self, page: int, page_size: int = 100):
|
||||
self.calls += 1
|
||||
self.pages.append(page)
|
||||
if self.stop_after is not None and page >= self.stop_after:
|
||||
return {
|
||||
"folder_arr": [
|
||||
{"media_lib_set_id": page, "path": f"/library/{page}"},
|
||||
],
|
||||
"is_last_page": True,
|
||||
}
|
||||
return {
|
||||
"folder_arr": [
|
||||
{"media_lib_set_id": page, "path": f"/library/{page}"},
|
||||
],
|
||||
"is_last_page": False,
|
||||
}
|
||||
|
||||
|
||||
class UgreenScanModeTest(unittest.TestCase):
|
||||
def test_resolve_scan_type(self):
|
||||
resolve = Ugreen._Ugreen__resolve_scan_type
|
||||
@@ -148,6 +192,53 @@ class UgreenStatisticTest(unittest.TestCase):
|
||||
self.assertIsNone(stat.episode_count)
|
||||
|
||||
|
||||
class UgreenReconnectTest(unittest.TestCase):
|
||||
def test_reconnect_does_not_eagerly_load_libraries(self):
|
||||
ugreen = Ugreen.__new__(Ugreen)
|
||||
ugreen._host = "http://127.0.0.1:9999"
|
||||
ugreen._username = "tester"
|
||||
ugreen._password = "secret"
|
||||
ugreen._verify_ssl = True
|
||||
ugreen._libraries = {"old": {"id": "old"}}
|
||||
ugreen._library_paths = {"old": "/old"}
|
||||
ugreen._api = None
|
||||
ugreen._userinfo = None
|
||||
|
||||
with patch.object(Ugreen, "_Ugreen__restore_persisted_session", return_value=False), patch(
|
||||
"_test_ugreen_module.Api", return_value=_FakeReconnectApi()
|
||||
), patch.object(Ugreen, "_Ugreen__save_persisted_session", return_value=None), patch.object(
|
||||
Ugreen, "disconnect", wraps=ugreen.disconnect
|
||||
), patch.object(Ugreen, "get_librarys") as mocked_get_librarys:
|
||||
self.assertTrue(ugreen.reconnect())
|
||||
|
||||
mocked_get_librarys.assert_not_called()
|
||||
self.assertEqual(ugreen._libraries, {})
|
||||
self.assertEqual(ugreen._library_paths, {})
|
||||
|
||||
|
||||
class UgreenLibraryPathLimitTest(unittest.TestCase):
|
||||
def test_load_library_paths_stops_at_last_page(self):
|
||||
ugreen = Ugreen.__new__(Ugreen)
|
||||
ugreen._username = "tester"
|
||||
ugreen._api = _PagedFolderApi(stop_after=3)
|
||||
|
||||
paths = ugreen._Ugreen__load_library_paths()
|
||||
|
||||
self.assertEqual(ugreen._api.pages, [1, 2, 3])
|
||||
self.assertEqual(paths["3"], "/library/3")
|
||||
|
||||
def test_load_library_paths_respects_page_limit(self):
|
||||
ugreen = Ugreen.__new__(Ugreen)
|
||||
ugreen._username = "tester"
|
||||
ugreen._api = _PagedFolderApi()
|
||||
|
||||
paths = ugreen._Ugreen__load_library_paths()
|
||||
|
||||
self.assertEqual(ugreen._api.calls, Ugreen.LIBRARY_PATH_PAGE_LIMIT)
|
||||
self.assertEqual(len(paths), Ugreen.LIBRARY_PATH_PAGE_LIMIT)
|
||||
self.assertIn(str(Ugreen.LIBRARY_PATH_PAGE_LIMIT), paths)
|
||||
|
||||
|
||||
class DashboardStatisticTest(unittest.TestCase):
|
||||
@unittest.skipIf(dashboard_endpoint is None, "dashboard endpoint dependencies are missing")
|
||||
def test_statistic_all_episode_missing(self):
|
||||
|
||||
Reference in New Issue
Block a user