mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-30 20:05:42 +08:00
fix(mediaserver): 翻页中途失败不再被当成查询结束
新增的关键字翻页逻辑只在第一页失败时返回 None,后续页面请求失败会让生成器 正常结束:get_movies 返回残缺列表、按标题定位剧集返回空串,等于把「连不上」 误报成「确定不在库中」,可能触发重复下载。 改为中途失败抛出私有异常并由调用方转成 None,恢复「连接失败返回 None、 未找到返回空结果」的区分;补 2 条回归,均已确认在修正前失败。
This commit is contained in:
@@ -14,6 +14,14 @@ from app.schemas.mediaserver import RefreshMediaItem as _SchemaRefreshMediaItem
|
||||
from app.schemas.types import MediaSource, MediaType
|
||||
|
||||
|
||||
class _SearchInterrupted(Exception):
|
||||
"""关键字翻页中途请求失败。
|
||||
|
||||
与「查完了」区分开:调用方据此返回 None(服务不可达),而不是空结果
|
||||
(确定不在库中)——后者会让上层误判成需要重新下载。
|
||||
"""
|
||||
|
||||
|
||||
class MediaVault:
|
||||
"""MediaVault 自建媒体库客户端。
|
||||
|
||||
@@ -234,17 +242,20 @@ class MediaVault:
|
||||
return None
|
||||
|
||||
def rows() -> Generator[Dict[str, Any], Any, None]:
|
||||
result: Optional[Dict[str, Any]] = first
|
||||
result: Dict[str, Any] = first
|
||||
page = 1
|
||||
while result is not None:
|
||||
while True:
|
||||
batch = result.get("items") or []
|
||||
yield from batch
|
||||
if len(batch) < self.PAGE_LIMIT:
|
||||
return
|
||||
page += 1
|
||||
result = self.__query_items(
|
||||
following = self.__query_items(
|
||||
keyword=keyword, kinds=kinds, page=page, page_size=self.PAGE_LIMIT
|
||||
)
|
||||
if following is None:
|
||||
raise _SearchInterrupted
|
||||
result = following
|
||||
|
||||
return rows()
|
||||
|
||||
@@ -273,7 +284,11 @@ class MediaVault:
|
||||
if rows is None:
|
||||
return None
|
||||
movies = []
|
||||
for row in rows:
|
||||
try:
|
||||
search_rows = list(rows)
|
||||
except _SearchInterrupted:
|
||||
return None
|
||||
for row in search_rows:
|
||||
item = self.__format_item_info(row)
|
||||
if not item or item.title != title:
|
||||
continue
|
||||
@@ -339,7 +354,11 @@ class MediaVault:
|
||||
rows = self.__search_rows(keyword=title, kinds="Series")
|
||||
if rows is None:
|
||||
return None
|
||||
for row in rows:
|
||||
try:
|
||||
search_rows = list(rows)
|
||||
except _SearchInterrupted:
|
||||
return None
|
||||
for row in search_rows:
|
||||
item = self.__format_item_info(row)
|
||||
if not item or item.title != title:
|
||||
continue
|
||||
|
||||
@@ -531,3 +531,33 @@ def test_play_item_and_backdrop_images_carry_no_credentials():
|
||||
|
||||
assert urls
|
||||
assert all("api_key" not in url for url in urls)
|
||||
|
||||
|
||||
def _failing_second_page(total_rows: list):
|
||||
"""第一页正常、后续页请求失败,模拟翻页中途断连。"""
|
||||
calls = {"n": 0}
|
||||
|
||||
def handler(params, _data):
|
||||
calls["n"] += 1
|
||||
if calls["n"] > 1:
|
||||
return None
|
||||
size = int(params.get("page_size", 40))
|
||||
return Result(True, {"items": total_rows[:size], "total": len(total_rows)})
|
||||
|
||||
return handler
|
||||
|
||||
|
||||
def test_get_movies_reports_unreachable_when_a_later_page_fails():
|
||||
"""翻页中途断连必须返回 None,不能把残缺结果当成「不在库中」。"""
|
||||
rows = [_item_row(i, title=f"沙丘{i}") for i in range(MediaVault.PAGE_LIMIT)]
|
||||
client = _client({"/items": _failing_second_page(rows)})
|
||||
|
||||
assert client.get_movies(title="沙丘") is None
|
||||
|
||||
|
||||
def test_find_series_reports_unreachable_when_a_later_page_fails():
|
||||
"""同上:剧集定位不能把断连误判成整部剧未入库。"""
|
||||
rows = [_item_row(i, kind="Series", title=f"剧{i}") for i in range(MediaVault.PAGE_LIMIT)]
|
||||
client = _client({"/items": _failing_second_page(rows)})
|
||||
|
||||
assert client.get_tv_episodes(title="剧A") == (None, None)
|
||||
|
||||
Reference in New Issue
Block a user