feat: integrate Quark and UC drive APIs

Add optional drive API capabilities for Quark and UC adapters, including directory lookup/creation, rename, move, delete, task polling, Quark recycle cleanup, and UC share staging folder support.\n\nAdd unittest coverage for capability declarations, production transfer path save_dir resolution, staging-folder flow, delete_files behavior, task polling params, and HTTP/JSON error handling.\n\nDocument the Hong Kong test server deployment boundary and verification commands.
This commit is contained in:
2026-05-22 19:02:46 +08:00
parent d79c11fb15
commit 4c161b63c6
5 changed files with 768 additions and 73 deletions
+72 -2
View File
@@ -76,6 +76,18 @@ class BaseCloudDriveAdapter(ABC):
# URL匹配正则(子类覆盖)
URL_PATTERNS: List[str] = []
# 可选 Drive API 能力;子类按需覆盖为 True 并实现对应方法。
capabilities: Dict[str, bool] = {
"ensure_dir": False,
"save_files": False,
"poll_task": False,
"rename": False,
"move_files": False,
"delete_files": False,
"cleanup_recycle": False,
"share_staging_folder": False,
}
# 默认请求头
DEFAULT_HEADERS: Dict[str, str] = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
@@ -235,6 +247,40 @@ class BaseCloudDriveAdapter(ABC):
"""广告过滤(默认不实现,子类可覆盖)"""
return file_ids
# ─── Optional Drive API capability protocol ─────────────────────
def _unsupported_capability(self, capability: str) -> None:
raise TransferError(
TransferErrorCode.NETWORK_ERROR,
message=f"{self.PLATFORM_KEY or self.PLATFORM_NAME} 不支持 Drive API 能力: {capability}",
platform=self.PLATFORM_KEY,
)
def ensure_dir(self, dir_path: str) -> str:
self._unsupported_capability("ensure_dir")
def get_fids(self, file_paths: List[str]) -> List[Dict[str, Any]]:
self._unsupported_capability("get_fids")
def mkdir(self, dir_path: str) -> Dict[str, Any]:
self._unsupported_capability("mkdir")
def rename(self, fid: str, file_name: str) -> Dict[str, Any]:
self._unsupported_capability("rename")
def move_files(self, fids: List[str], to_pdir_fid: str) -> Dict[str, Any]:
self._unsupported_capability("move_files")
def delete_files(self, fids: List[str]) -> Dict[str, Any]:
self._unsupported_capability("delete_files")
def query_task(self, task_id: str) -> Dict[str, Any]:
self._unsupported_capability("poll_task")
def cleanup_recycle(self, fids: List[str]) -> Dict[str, Any]:
self._unsupported_capability("cleanup_recycle")
# ─── HTTP 工具方法 ─────────────────────────────────────
def _get(self, url: str, params: dict = None, headers: dict = None,
@@ -271,6 +317,28 @@ class BaseCloudDriveAdapter(ABC):
raise TransferError(TransferErrorCode.NETWORK_ERROR,
message=str(last_exc), platform=self.PLATFORM_KEY)
def _drive_api_json(self, resp: requests.Response, context: str = "网盘 API") -> Dict[str, Any]:
"""Validate HTTP response and decode JSON for drive helper APIs."""
try:
resp.raise_for_status()
except requests.HTTPError as exc:
raise TransferError(
TransferErrorCode.NETWORK_ERROR,
message=f"{context} HTTP错误: {exc}",
platform=self.PLATFORM_KEY,
details={"status_code": getattr(resp, "status_code", None)},
) from exc
try:
return resp.json()
except ValueError as exc:
text = getattr(resp, "text", "") or ""
raise TransferError(
TransferErrorCode.NETWORK_ERROR,
message=f"{context} JSON解析失败: {text[:200]}",
platform=self.PLATFORM_KEY,
) from exc
def _poll_task(self, task_url: str, task_id: str,
status_field: str = "status",
success_value: Any = 2,
@@ -292,10 +360,12 @@ class BaseCloudDriveAdapter(ABC):
details={"task_id": task_id})
try:
params = query_params or {}
base_params = query_params(attempt) if callable(query_params) else (query_params or {})
params = dict(base_params)
params["task_id"] = task_id
resp = self._get(task_url, params=params, retry=1)
data = resp.json().get("data", resp.json())
payload = resp.json()
data = payload.get("data", payload)
current_status = data.get(status_field)
if current_status == success_value: