ge-tool/backend/routes/downloads_routes.py

402 lines
13 KiB
Python
Raw Permalink Normal View History

2025-12-10 06:41:43 +00:00
"""
Downloads Routes - File-centric RESTful API for download management.
Each endpoint operates on individual file downloads, not batches.
"""
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel
from typing import List, Dict, Optional
import logging
from ..services import downloads_service
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api", tags=["Downloads"])
# ==================== REQUEST MODELS ====================
class FileInfo(BaseModel):
"""Single file information."""
name: str
path: str
isdir: bool = False
is_folder: Optional[bool] = None # Alias for isdir
class CreateBatchRequest(BaseModel):
"""Request to create a batch of file downloads (API mode)."""
files: List[FileInfo]
ge_id: str
lang: str
class CreateSharingBatchRequest(BaseModel):
"""Request to create batch downloads from sharing link."""
sharing_id: str
files: List[FileInfo]
ge_id: Optional[str] = None
lang: Optional[str] = None
class UpdateDownloadRequest(BaseModel):
"""Request to update a single download."""
action: str # "retry" or "cancel"
# ==================== DOWNLOAD ENDPOINTS ====================
@router.get('/downloads')
def get_all_downloads(
status: Optional[str] = Query(None, description="Filter by status"),
mode: Optional[str] = Query(
None, description="Filter by mode (api/sharing)"),
limit: int = Query(100, description="Max number of downloads to return")
):
"""
Get all file downloads with optional filtering.
Query params:
- status: pending, downloading, completed, failed, cancelled
- mode: api, sharing
- limit: Max results (default: 100)
Returns list of individual file downloads (not batched).
Frontend groups by batch_id for display.
"""
try:
downloads = downloads_service.get_all_downloads(
status=status,
mode=mode,
limit=limit
)
return {
"success": True,
"downloads": downloads,
"count": len(downloads)
}
except Exception as e:
logger.error(f"Error getting downloads: {e}")
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {str(e)}")
@router.get('/downloads/{download_id}')
def get_download_by_id(download_id: int):
"""Get a single file download by ID."""
try:
download = downloads_service.get_download_by_id(download_id)
if not download:
raise HTTPException(
status_code=404, detail="Download không tồn tại")
return {
"success": True,
"download": download
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting download {download_id}: {e}")
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {str(e)}")
@router.get('/batches/{batch_id}')
def get_batch_downloads(batch_id: str):
"""Get all downloads in a specific batch."""
try:
downloads = downloads_service.get_downloads_by_batch(batch_id)
summary = downloads_service.get_batch_summary(batch_id)
if not downloads:
raise HTTPException(status_code=404, detail="Batch không tồn tại")
return {
"success": True,
"batch": summary,
"downloads": downloads,
"count": len(downloads)
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting batch {batch_id}: {e}")
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {str(e)}")
@router.post('/batches/api')
def create_api_batch(payload: CreateBatchRequest):
"""
Create a batch of downloads for API mode (direct NAS access).
Each file becomes a separate download record.
"""
try:
from ..common import get_download_destination_path
from ..services import mongodb_service
logger.debug(
f"Creating API batch: {payload.ge_id}_{payload.lang}, {len(payload.files)} files")
if not payload.files:
raise HTTPException(
status_code=400, detail="Không có file nào được chọn")
# Get MongoDB path
mongodb_path = mongodb_service.get_path_from_tms_data(
payload.ge_id, payload.lang)
# Calculate destination
destination_path = get_download_destination_path(
payload.ge_id, payload.lang)
# Convert FileInfo to dicts
files_data = [
{
"name": f.name,
"path": f.path,
"isdir": f.isdir or f.is_folder or False
}
for f in payload.files
]
# Create batch
result = downloads_service.create_downloads_batch(
files=files_data,
ge_id=payload.ge_id,
lang=payload.lang,
mode='api',
mongodb_path=mongodb_path,
destination_path=destination_path
)
if not result["success"]:
raise HTTPException(status_code=500, detail=result["message"])
logger.debug(
f"Created API batch {result['batch_id']}: {result['file_count']} files")
return {
"success": True,
"batch_id": result["batch_id"],
"download_ids": result["download_ids"],
"file_count": result["file_count"],
"destination_path": destination_path,
"mongodb_path": mongodb_path,
"message": f"Đã tạo {result['file_count']} downloads"
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error creating API batch: {e}")
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {str(e)}")
@router.post('/batches/sharing')
def create_sharing_batch(payload: CreateSharingBatchRequest):
"""
Create a batch of downloads for Sharing mode (sharing link).
Each file becomes a separate download record.
"""
try:
from ..common import get_download_destination_path
from ..services import nas_service
logger.debug(
f"Creating Sharing batch: {payload.sharing_id}, {len(payload.files)} files")
if not payload.files:
raise HTTPException(
status_code=400, detail="Không có file nào được chọn")
# Determine GE ID and destination
if payload.ge_id and payload.lang:
from ..services import mongodb_service
destination_path = get_download_destination_path(
payload.ge_id, payload.lang)
# For sharing mode, mongodb_path = sharing link (linkRaw) from MongoDB
mongodb_path = mongodb_service.get_sharing_link_from_tms_data(
payload.ge_id, payload.lang)
ge_id = payload.ge_id
lang = payload.lang
else:
destination_path = nas_service.DESTINATION_PATH
mongodb_path = None
ge_id = f"SHARING_{payload.sharing_id}"
lang = "LINK"
# Convert FileInfo to dicts
files_data = [
{
"name": f.name,
"path": f.path,
"isdir": f.isdir or f.is_folder or False
}
for f in payload.files
]
# Create batch
result = downloads_service.create_downloads_batch(
files=files_data,
ge_id=ge_id,
lang=lang,
mode='sharing',
sharing_id=payload.sharing_id,
mongodb_path=mongodb_path,
destination_path=destination_path
)
if not result["success"]:
raise HTTPException(status_code=500, detail=result["message"])
logger.debug(
f"Created Sharing batch {result['batch_id']}: {result['file_count']} files")
return {
"success": True,
"batch_id": result["batch_id"],
"download_ids": result["download_ids"],
"file_count": result["file_count"],
"destination_path": destination_path,
"sharing_id": payload.sharing_id,
"message": f"Đã tạo {result['file_count']} downloads"
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error creating Sharing batch: {e}")
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {str(e)}")
@router.patch('/downloads/{download_id}')
def update_download(download_id: int, payload: UpdateDownloadRequest):
"""
Update a file download (cancel or retry).
Actions:
- "cancel": Cancel pending/downloading file
- "retry": Retry failed file
"""
try:
if payload.action == "cancel":
success = downloads_service.cancel_download(download_id)
if success:
return {
"success": True,
"message": f"Download {download_id} đã được hủy"
}
else:
raise HTTPException(
status_code=404, detail="Download không tồn tại")
elif payload.action == "retry":
success = downloads_service.retry_download(download_id)
if success:
return {
"success": True,
"message": f"Download {download_id} đã được đưa vào queue"
}
else:
raise HTTPException(
status_code=404, detail="Download không tồn tại")
else:
raise HTTPException(
status_code=400,
detail=f"Action không hợp lệ: {payload.action}. Chỉ chấp nhận 'cancel' hoặc 'retry'"
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error updating download {download_id}: {e}")
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {str(e)}")
@router.delete('/downloads/{download_id}')
def delete_download(download_id: int):
"""
Delete a file download record.
Only completed/failed/cancelled downloads can be deleted.
"""
try:
# Check download exists and status
download = downloads_service.get_download_by_id(download_id)
if not download:
raise HTTPException(
status_code=404, detail="Download không tồn tại")
# Only delete terminal status
if download["status"] not in ["completed", "failed", "cancelled"]:
raise HTTPException(
status_code=400,
detail="Chỉ có thể xóa downloads đã hoàn thành hoặc thất bại"
)
success = downloads_service.delete_download(download_id)
if success:
return {
"success": True,
"message": f"Download {download_id} đã được xóa"
}
else:
raise HTTPException(
status_code=500, detail="Không thể xóa download")
except HTTPException:
raise
except Exception as e:
logger.error(f"Error deleting download {download_id}: {e}")
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {str(e)}")
@router.delete('/batches/{batch_id}')
def delete_batch(batch_id: str):
"""
Delete all downloads in a batch.
Only works if all downloads are in terminal status.
"""
try:
# Check all downloads in batch
downloads = downloads_service.get_downloads_by_batch(batch_id)
if not downloads:
raise HTTPException(status_code=404, detail="Batch không tồn tại")
# Verify all are terminal
active_count = sum(
1 for d in downloads
if d["status"] in ["pending", "downloading"]
)
if active_count > 0:
raise HTTPException(
status_code=400,
detail=f"Batch còn {active_count} downloads đang active"
)
success = downloads_service.delete_batch(batch_id)
if success:
return {
"success": True,
"message": f"Batch {batch_id} đã được xóa ({len(downloads)} files)"
}
else:
raise HTTPException(status_code=500, detail="Không thể xóa batch")
except HTTPException:
raise
except Exception as e:
logger.error(f"Error deleting batch {batch_id}: {e}")
raise HTTPException(status_code=500, detail=f"Lỗi hệ thống: {str(e)}")