improve ux, moved functionality for better ui experience on app and extension.

This commit is contained in:
Bryan Van Deusen
2026-01-26 11:38:51 -05:00
parent 74b1ecf2cf
commit 2a0e37a73d
12 changed files with 369 additions and 78 deletions
+5 -1
View File
@@ -11,7 +11,7 @@ celery_app = Celery(
"gallery_subscriber",
broker=settings.redis_url,
backend=settings.redis_url,
include=["app.tasks.downloads"],
include=["app.tasks.downloads", "app.tasks.maintenance"],
)
# Celery configuration
@@ -40,4 +40,8 @@ celery_app.conf.beat_schedule = {
"task": "app.tasks.downloads.cleanup_old_downloads",
"schedule": crontab(hour=3, minute=0), # Daily at 3 AM
},
"update-storage-stats": {
"task": "tasks.update_storage_stats",
"schedule": crontab(minute="*/30"), # Every 30 minutes
},
}
+127
View File
@@ -0,0 +1,127 @@
"""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