mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-11 08:35:58 +08:00
fix(u115): handle missing path response
This commit is contained in:
@@ -29,6 +29,8 @@ lock = Lock()
|
||||
|
||||
MIN_U115_UPLOAD_PART_SIZE = 1 * 1024 * 1024
|
||||
U115_UPLOAD_PART_COUNT_TARGET = 96
|
||||
U115_DEFAULT_ACCEPTED_CODES = (0, 20004)
|
||||
U115_GET_INFO_ACCEPTED_CODES = (*U115_DEFAULT_ACCEPTED_CODES, 430004)
|
||||
U115_UPLOAD_PART_SIZE_STEPS = (
|
||||
10 * 1024 * 1024,
|
||||
16 * 1024 * 1024,
|
||||
@@ -298,10 +300,18 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
|
||||
return result.get("data")
|
||||
|
||||
def _request_api(
|
||||
self, method: str, endpoint: str, result_key: Optional[str] = None, **kwargs
|
||||
self,
|
||||
method: str,
|
||||
endpoint: str,
|
||||
result_key: Optional[str] = None,
|
||||
*,
|
||||
accepted_codes: Tuple[int, ...] = U115_DEFAULT_ACCEPTED_CODES,
|
||||
**kwargs,
|
||||
) -> Optional[Union[dict, list]]:
|
||||
"""
|
||||
带错误处理和速率限制的API请求
|
||||
|
||||
:param accepted_codes: 当前接口可确认处理的业务码
|
||||
"""
|
||||
# 检查会话
|
||||
self._check_session()
|
||||
@@ -358,7 +368,13 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
|
||||
time.sleep(self.limit_sleep_seconds)
|
||||
kwargs["retry_limit"] = retry_times - 1
|
||||
kwargs["no_error_log"] = no_error_log
|
||||
return self._request_api(method, endpoint, result_key, **kwargs)
|
||||
return self._request_api(
|
||||
method,
|
||||
endpoint,
|
||||
result_key,
|
||||
accepted_codes=accepted_codes,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# 处理请求错误
|
||||
try:
|
||||
@@ -376,11 +392,17 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
|
||||
f"【115】{method} 请求 {endpoint} 错误 {e},等待 {sleep_duration} 秒后重试..."
|
||||
)
|
||||
time.sleep(sleep_duration)
|
||||
return self._request_api(method, endpoint, result_key, **kwargs)
|
||||
return self._request_api(
|
||||
method,
|
||||
endpoint,
|
||||
result_key,
|
||||
accepted_codes=accepted_codes,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# 返回数据
|
||||
ret_data = resp.json()
|
||||
if ret_data.get("code") not in (0, 20004):
|
||||
if ret_data.get("code") not in accepted_codes:
|
||||
error_msg = ret_data.get("message", "")
|
||||
if not no_error_log:
|
||||
logger.warn(f"【115】{method} 请求 {endpoint} 出错:{error_msg}")
|
||||
@@ -402,7 +424,13 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
|
||||
time.sleep(self.limit_sleep_seconds)
|
||||
kwargs["retry_limit"] = retry_times - 1
|
||||
kwargs["no_error_log"] = no_error_log
|
||||
return self._request_api(method, endpoint, result_key, **kwargs)
|
||||
return self._request_api(
|
||||
method,
|
||||
endpoint,
|
||||
result_key,
|
||||
accepted_codes=accepted_codes,
|
||||
**kwargs,
|
||||
)
|
||||
return None
|
||||
|
||||
if result_key:
|
||||
@@ -910,20 +938,22 @@ class U115Pan(StorageBase, metaclass=WeakSingleton):
|
||||
def __get_info_item(self, path: Path) -> Optional[schemas.FileItem]:
|
||||
"""
|
||||
查询指定路径的文件/目录项,无法确认状态时抛出 StorageQueryError。
|
||||
接口业务码 20004(记录不存在)与 0 一样视为确认结果,其余错误
|
||||
(网络失败、限流重试用尽、未知业务错误)均无法确认目标状态。
|
||||
接口业务码 20004(记录不存在)、430004(路径不存在)与 0 一样
|
||||
视为确认结果,其余错误(网络失败、限流重试用尽、未知业务错误)
|
||||
均无法确认目标状态。
|
||||
"""
|
||||
resp = self._request_api(
|
||||
"POST",
|
||||
"/open/folder/get_info",
|
||||
data={"path": path.as_posix()},
|
||||
no_error_log=True,
|
||||
accepted_codes=U115_GET_INFO_ACCEPTED_CODES,
|
||||
)
|
||||
if resp is None:
|
||||
raise StorageQueryError(f"【115】无法确认文件状态(请求失败或接口错误): {path}")
|
||||
data = resp.get("data") if isinstance(resp, dict) else None
|
||||
if not data or not data.get("file_id"):
|
||||
# code 20004(记录不存在)等场景,确认目标不存在
|
||||
# 115 对记录不存在和路径不存在返回不同业务码,两者都可确认目标不存在
|
||||
return None
|
||||
return schemas.FileItem(
|
||||
storage=self.schema.value,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from pathlib import Path
|
||||
from threading import Lock
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
@@ -24,6 +25,24 @@ def _u115() -> U115Pan:
|
||||
return object.__new__(U115Pan)
|
||||
|
||||
|
||||
def _u115_with_api_payload(payload: dict) -> U115Pan:
|
||||
"""
|
||||
构造返回固定业务响应的 115 存储实例。
|
||||
"""
|
||||
storage = _u115()
|
||||
response = MagicMock(status_code=200)
|
||||
response.json.return_value = payload
|
||||
storage.session = MagicMock()
|
||||
storage.session.request.return_value = response
|
||||
storage._check_session = MagicMock()
|
||||
storage._download_limiter = MagicMock()
|
||||
storage._api_limiter = MagicMock()
|
||||
storage._rate_stats = MagicMock()
|
||||
storage._limit_lock = Lock()
|
||||
storage._limit_until = 0.0
|
||||
return storage
|
||||
|
||||
|
||||
def _alipan(monkeypatch) -> AliPan:
|
||||
"""
|
||||
构造阿里云盘存储实例(跳过初始化,_default_drive_id 为只读属性需在类级替换)。
|
||||
@@ -111,6 +130,41 @@ def test_u115_strict_confirmed_absent_returns_none():
|
||||
assert storage.get_item_strict(Path("/movie.mkv")) is None
|
||||
|
||||
|
||||
def test_u115_strict_path_not_found_returns_none():
|
||||
"""
|
||||
115 查询路径返回 430004 时应确认为不存在,允许首次整理。
|
||||
"""
|
||||
storage = _u115_with_api_payload(
|
||||
{"state": False, "code": 430004, "message": "路径不存在", "data": {}}
|
||||
)
|
||||
|
||||
assert storage.get_item_strict(Path("/movie.mkv")) is None
|
||||
|
||||
|
||||
def test_u115_strict_empty_list_returns_none():
|
||||
"""
|
||||
115 查询不存在目标返回空列表时也应确认为不存在。
|
||||
"""
|
||||
storage = _u115_with_api_payload(
|
||||
{"state": True, "code": 0, "message": "", "data": []}
|
||||
)
|
||||
|
||||
assert storage.get_item_strict(Path("/movie.mkv")) is None
|
||||
|
||||
|
||||
def test_u115_path_not_found_code_is_not_globally_accepted():
|
||||
"""
|
||||
非路径查询接口返回 430004 时仍应视为业务错误。
|
||||
"""
|
||||
storage = _u115_with_api_payload(
|
||||
{"state": False, "code": 430004, "message": "路径不存在", "data": {}}
|
||||
)
|
||||
|
||||
result = storage._request_api("POST", "/open/folder/add", data={"file_name": "TV"})
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_u115_strict_returns_item():
|
||||
"""
|
||||
115 返回有效文件数据时应构造文件项。
|
||||
|
||||
Reference in New Issue
Block a user