Files
LitePan/api/cross_transfer.py
T
2026-06-11 03:52:14 +08:00

191 lines
6.4 KiB
Python

"""跨盘秒传管理接口。"""
import asyncio
import json
from typing import List, Optional
from fastapi import APIRouter, Depends, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from api.deps import require_admin_auth
from api.responses import error_response as _error_response, success_response as _success_response
from cross_transfer import build_routes, execute_stream, probe_stream, scan_source
from cross_transfer.relay_task_manager import relay_task_manager
from core.log_manager import LogModule, get_writer
router = APIRouter(prefix="/api/cross-transfer", tags=["跨盘秒传"])
def _log():
return get_writer(LogModule.API)
class ScanRequest(BaseModel):
source_account_id: int
source_parent_id: str
method: str
source_display_path: str = ""
class ProbeFile(BaseModel):
source_file_id: Optional[str] = ""
rel_path: Optional[str] = ""
name: str
size: int = 0
hash: str = ""
class ProbeRequest(BaseModel):
source_account_id: int
target_account_id: int
target_parent_id: str
method: str
files: List[ProbeFile]
class TransferFile(BaseModel):
source_file_id: Optional[str] = ""
rel_path: Optional[str] = ""
rel_dir: Optional[str] = ""
name: str
size: int = 0
hash: str
class ExecuteRequest(BaseModel):
source_account_id: int
source_account_name: str = ""
source_driver_type: str = ""
target_account_id: int
target_account_name: str = ""
target_driver_type: str = ""
target_parent_id: str
target_display_path: str = ""
method: str
files: List[TransferFile]
conflict: str = "rename"
fallback: bool = False
class RelayTaskBatchDeleteRequest(BaseModel):
task_ids: List[str]
@router.get("/routes")
async def get_routes(session_data: dict = Depends(require_admin_auth)):
try:
routes = build_routes()
return _success_response(data=routes, message=f"可用线路 {len(routes)} 条")
except Exception as exc:
_log().error(f"获取跨盘秒传线路失败: {exc}")
return _error_response(message=f"获取线路失败: {exc}", data=[])
@router.post("/scan")
async def scan(req: ScanRequest, session_data: dict = Depends(require_admin_auth)):
try:
result = await scan_source(
source_account_id=req.source_account_id,
source_parent_id=req.source_parent_id,
method_id=req.method,
source_display_path=req.source_display_path,
)
return _success_response(data=result, message=f"已扫描 {result['total']} 个文件")
except Exception as exc:
_log().error(f"跨盘秒传扫描失败: {exc}")
return _error_response(message=f"扫描失败: {exc}")
@router.post("/probe")
async def probe(req: ProbeRequest, session_data: dict = Depends(require_admin_auth)):
async def generate():
try:
async for msg in probe_stream(
source_account_id=req.source_account_id,
target_account_id=req.target_account_id,
target_parent_id=req.target_parent_id,
method_id=req.method,
files=[f.model_dump() for f in req.files],
):
yield json.dumps(msg, ensure_ascii=False) + "\n"
except Exception as exc:
_log().error(f"跨盘秒传试探失败: {exc}")
yield json.dumps({"event": "error", "message": str(exc)}, ensure_ascii=False) + "\n"
return StreamingResponse(generate(), media_type="application/x-ndjson; charset=utf-8")
@router.post("/execute")
async def execute(req: ExecuteRequest, session_data: dict = Depends(require_admin_auth)):
async def generate():
try:
async for msg in execute_stream(
source_account_id=req.source_account_id,
source_account_name=req.source_account_name,
source_driver_type=req.source_driver_type,
target_account_id=req.target_account_id,
target_account_name=req.target_account_name,
target_driver_type=req.target_driver_type,
target_parent_id=req.target_parent_id,
target_display_path=req.target_display_path,
method_id=req.method,
files=[f.model_dump() for f in req.files],
conflict=req.conflict,
fallback=req.fallback,
):
yield json.dumps(msg, ensure_ascii=False) + "\n"
except Exception as exc:
_log().error(f"跨盘秒传执行失败: {exc}")
yield json.dumps({"event": "error", "message": str(exc)}, ensure_ascii=False) + "\n"
return StreamingResponse(generate(), media_type="application/x-ndjson; charset=utf-8")
@router.get("/relay/tasks")
async def list_relay_tasks(session_data: dict = Depends(require_admin_auth)):
try:
tasks = await relay_task_manager.list_tasks()
return _success_response(data=tasks, message="获取跨盘任务成功")
except Exception as exc:
return _error_response(message=f"获取跨盘任务失败: {exc}", data=[])
@router.get("/relay/tasks/stream")
async def stream_relay_tasks(request: Request, session_data: dict = Depends(require_admin_auth)):
async def event_stream():
queue = await relay_task_manager.subscribe_task_stream()
try:
while True:
if await request.is_disconnected():
break
try:
payload = await asyncio.wait_for(queue.get(), timeout=15.0)
yield f"event: tasks\ndata: {payload}\n\n"
except asyncio.TimeoutError:
yield "event: ping\ndata: {}\n\n"
finally:
await relay_task_manager.unsubscribe_task_stream(queue)
return StreamingResponse(
event_stream(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"X-Accel-Buffering": "no",
},
)
@router.post("/relay/tasks/batch-delete")
async def batch_delete_relay_tasks(
req: RelayTaskBatchDeleteRequest,
session_data: dict = Depends(require_admin_auth),
):
try:
removed = await relay_task_manager.delete_tasks(req.task_ids or [])
return _success_response(data={"removed": removed}, message=f"已删除 {removed} 个跨盘任务")
except Exception as exc:
return _error_response(message=f"删除跨盘任务失败: {exc}")