This repository has been archived on 2026-05-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
GallerySubscriber/backend/app/tasks/maintenance.py
T

128 lines
3.8 KiB
Python

"""System maintenance Celery tasks."""
import asyncio
import logging
import os
from datetime import datetime
from pathlib import Path
from sqlalchemy import select
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from app.config import get_settings
from app.tasks.celery_app import celery_app
from app.models.setting import Setting, STORAGE_STATS_SETTING
logger = logging.getLogger(__name__)
settings = get_settings()
def get_async_session():
"""Get async session factory for Celery tasks."""
engine = create_async_engine(settings.async_database_url, echo=False)
session_factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
return session_factory, engine
async def _cleanup_engine(engine):
"""Properly dispose of the async engine."""
await engine.dispose()
def format_size(size_bytes: int) -> str:
"""Format bytes as human-readable size."""
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if size_bytes < 1024:
return f"{size_bytes:.1f} {unit}"
size_bytes /= 1024
return f"{size_bytes:.1f} PB"
def calculate_storage_stats() -> dict:
"""Calculate storage statistics for the downloads directory.
This runs synchronously since it's a filesystem operation.
"""
downloads_path = Path(settings.download_path)
if not downloads_path.exists():
return {
"total_size": 0,
"total_size_formatted": "0 B",
"file_count": 0,
"calculated_at": datetime.utcnow().isoformat(),
}
total_size = 0
file_count = 0
try:
for root, dirs, files in os.walk(downloads_path):
for f in files:
fp = os.path.join(root, f)
try:
total_size += os.path.getsize(fp)
file_count += 1
except OSError:
pass
except Exception as e:
logger.error(f"Error calculating storage stats: {e}")
return {
"total_size": 0,
"total_size_formatted": "Error",
"file_count": 0,
"error": str(e),
"calculated_at": datetime.utcnow().isoformat(),
}
return {
"total_size": total_size,
"total_size_formatted": format_size(total_size),
"file_count": file_count,
"calculated_at": datetime.utcnow().isoformat(),
}
async def _update_storage_stats_async() -> dict:
"""Async implementation to calculate and store storage stats."""
session_factory, engine = get_async_session()
try:
# Calculate stats (synchronous filesystem operation)
stats = calculate_storage_stats()
logger.info(f"Calculated storage stats: {stats['total_size_formatted']}, {stats['file_count']} files")
# Store in database
async with session_factory() as session:
# Check if setting exists
result = await session.execute(
select(Setting).where(Setting.key == STORAGE_STATS_SETTING)
)
setting = result.scalar_one_or_none()
if setting:
setting.value = stats
else:
setting = Setting(key=STORAGE_STATS_SETTING, value=stats)
session.add(setting)
await session.commit()
return stats
finally:
await _cleanup_engine(engine)
@celery_app.task(name="tasks.update_storage_stats")
def update_storage_stats() -> dict:
"""Celery task to calculate and cache storage stats.
This task should be scheduled to run periodically (e.g., every hour).
"""
logger.info("Starting storage stats calculation...")
result = asyncio.run(_update_storage_stats_async())
logger.info(f"Storage stats updated: {result}")
return result