Files
Canto/qwen3-tts-backend/api/cache.py
bdim404 80513a3258 init commit
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-26 15:34:31 +08:00

157 lines
4.3 KiB
Python

import logging
import json
from pathlib import Path
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session
from slowapi import Limiter
from slowapi.util import get_remote_address
from core.config import settings
from core.database import get_db
from core.cache_manager import VoiceCacheManager
from api.auth import get_current_user
from db.crud import list_cache_entries, delete_cache_entry
from db.models import VoiceCache, User
from utils.metrics import cache_metrics
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/cache", tags=["cache"])
limiter = Limiter(key_func=get_remote_address)
@router.get("/voices")
@limiter.limit("30/minute")
async def list_user_caches(
request: Request,
skip: int = 0,
limit: int = 100,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
caches = list_cache_entries(db, current_user.id, skip=skip, limit=limit)
result = []
for cache in caches:
meta_data = json.loads(cache.meta_data) if cache.meta_data else {}
cache_file = Path(cache.cache_path)
file_size_mb = cache_file.stat().st_size / (1024 * 1024) if cache_file.exists() else 0
result.append({
'id': cache.id,
'ref_audio_hash': cache.ref_audio_hash,
'created_at': cache.created_at.isoformat(),
'last_accessed': cache.last_accessed.isoformat(),
'access_count': cache.access_count,
'metadata': meta_data,
'size_mb': round(file_size_mb, 2)
})
return {
'caches': result,
'total': len(result)
}
@router.delete("/voices/{cache_id}")
@limiter.limit("30/minute")
async def delete_user_cache(
request: Request,
cache_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
cache = db.query(VoiceCache).filter(
VoiceCache.id == cache_id,
VoiceCache.user_id == current_user.id
).first()
if not cache:
raise HTTPException(status_code=404, detail="Cache not found")
cache_file = Path(cache.cache_path)
if cache_file.exists():
cache_file.unlink()
success = delete_cache_entry(db, cache_id, current_user.id)
if not success:
raise HTTPException(status_code=500, detail="Failed to delete cache")
logger.info(f"Cache deleted: id={cache_id}, user={current_user.id}")
return {
'message': 'Cache deleted successfully',
'cache_id': cache_id
}
@router.delete("/voices")
@limiter.limit("10/minute")
async def cleanup_expired_caches(
request: Request,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
cache_manager = await VoiceCacheManager.get_instance()
deleted_count = await cache_manager.cleanup_expired(db)
logger.info(f"Expired cache cleanup: user={current_user.id}, deleted={deleted_count}")
return {
'message': 'Expired caches cleaned up',
'deleted_count': deleted_count
}
@router.post("/voices/prune")
@limiter.limit("10/minute")
async def prune_caches(
request: Request,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
cache_manager = await VoiceCacheManager.get_instance()
deleted_count = await cache_manager.enforce_max_entries(current_user.id, db)
logger.info(f"LRU prune: user={current_user.id}, deleted={deleted_count}")
return {
'message': 'LRU pruning completed',
'deleted_count': deleted_count
}
@router.get("/stats")
@limiter.limit("30/minute")
async def get_cache_stats(
request: Request,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
stats = cache_metrics.get_stats(db, settings.CACHE_DIR)
user_stats = None
for user_stat in stats['users']:
if user_stat['user_id'] == current_user.id:
user_stats = user_stat
break
if user_stats is None:
user_cache_count = db.query(VoiceCache).filter(
VoiceCache.user_id == current_user.id
).count()
user_stats = {
'user_id': current_user.id,
'hits': 0,
'misses': 0,
'hit_rate': 0.0,
'cache_entries': user_cache_count
}
return {
'global': stats['global'],
'user': user_stats
}