diff --git a/.env.example b/.env.example index 6da7f80..3c22283 100644 --- a/.env.example +++ b/.env.example @@ -29,10 +29,9 @@ PORT=8080 # Download settings # DOWNLOAD_PARALLEL_LIMIT: Max concurrent downloads # DOWNLOAD_RATE_LIMIT: Seconds between requests (to avoid rate limiting) -# DEFAULT_CHECK_INTERVAL: Seconds between automatic checks for new content +# Check interval is configured per-source in the web UI (Settings page) DOWNLOAD_PARALLEL_LIMIT=3 DOWNLOAD_RATE_LIMIT=3.0 -DEFAULT_CHECK_INTERVAL=3600 # Logging level (DEBUG, INFO, WARNING, ERROR) LOG_LEVEL=INFO diff --git a/backend/app/api/settings.py b/backend/app/api/settings.py index 64f185b..576a1f7 100644 --- a/backend/app/api/settings.py +++ b/backend/app/api/settings.py @@ -31,6 +31,29 @@ async def get_or_create_extension_api_key(session: AsyncSession) -> str: return new_key +async def get_setting_value(session: AsyncSession, key: str, default=None): + """Get a setting value from the database, falling back to defaults. + + Args: + session: Database session + key: Setting key (e.g., 'download.schedule_interval') + default: Fallback if not in database or DEFAULT_SETTINGS + + Returns: + The setting value + """ + result = await session.execute( + select(Setting).where(Setting.key == key) + ) + setting = result.scalar() + + if setting: + return setting.value + + # Fall back to DEFAULT_SETTINGS, then to provided default + return DEFAULT_SETTINGS.get(key, default) + + @bp.route("", methods=["GET"]) async def get_all_settings(): """Get all application settings.""" diff --git a/backend/app/api/sources.py b/backend/app/api/sources.py index f01f8b4..1f781bf 100644 --- a/backend/app/api/sources.py +++ b/backend/app/api/sources.py @@ -8,6 +8,7 @@ from sqlalchemy.orm import selectinload from app.models.source import Source from app.models.subscription import Subscription from app.tasks.downloads import download_source +from app.api.settings import get_setting_value bp = Blueprint("sources", __name__) @@ -100,12 +101,17 @@ async def create_source(): if existing.scalar(): return jsonify({"error": "Source with this platform and URL already exists"}), 409 + # Get default check interval from database settings + default_interval = await get_setting_value( + session, "download.schedule_interval", 3600 + ) + source = Source( subscription_id=data["subscription_id"], platform=data["platform"], url=data["url"], enabled=data.get("enabled", True), - check_interval=data.get("check_interval", 3600), + check_interval=data.get("check_interval", default_interval), metadata_=data.get("metadata", {}), ) diff --git a/backend/app/api/subscriptions.py b/backend/app/api/subscriptions.py index 8bacb07..23ab742 100644 --- a/backend/app/api/subscriptions.py +++ b/backend/app/api/subscriptions.py @@ -8,6 +8,7 @@ from sqlalchemy.orm import selectinload from app.models.subscription import Subscription from app.models.source import Source from app.tasks.downloads import download_source +from app.api.settings import get_setting_value bp = Blueprint("subscriptions", __name__) @@ -85,6 +86,11 @@ async def create_subscription(): metadata_=data.get("metadata", {}), ) + # Get default check interval from database settings + default_interval = await get_setting_value( + session, "download.schedule_interval", 3600 + ) + # Add sources if provided sources_data = data.get("sources", []) for source_data in sources_data: @@ -94,7 +100,7 @@ async def create_subscription(): platform=source_data["platform"], url=source_data["url"], enabled=source_data.get("enabled", True), - check_interval=source_data.get("check_interval", 3600), + check_interval=source_data.get("check_interval", default_interval), metadata_=source_data.get("metadata", {}), ) subscription.sources.append(source) @@ -254,12 +260,17 @@ async def add_source_to_subscription(subscription_id: int): # Store name before commit (attributes expire after commit) subscription_name = subscription.name + # Get default check interval from database settings + default_interval = await get_setting_value( + session, "download.schedule_interval", 3600 + ) + source = Source( subscription_id=subscription_id, platform=data["platform"], url=data["url"], enabled=data.get("enabled", True), - check_interval=data.get("check_interval", 3600), + check_interval=data.get("check_interval", default_interval), metadata_=data.get("metadata", {}), ) diff --git a/backend/app/config.py b/backend/app/config.py index a52235b..4c16160 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -25,7 +25,7 @@ class Settings(BaseSettings): # Download settings download_parallel_limit: int = 3 download_rate_limit: float = 3.0 - default_check_interval: int = 3600 # seconds + # Note: check_interval is now per-source and stored in database # Server host: str = "0.0.0.0" diff --git a/backend/app/tasks/celery_app.py b/backend/app/tasks/celery_app.py index 2eab98e..69abda9 100644 --- a/backend/app/tasks/celery_app.py +++ b/backend/app/tasks/celery_app.py @@ -28,10 +28,13 @@ celery_app.conf.update( ) # Celery Beat schedule for periodic tasks +# Note: The scheduler runs frequently (every 60s) but only triggers downloads +# for sources whose individual check_interval has elapsed. Per-source intervals +# are stored in the database and can be configured via the UI. celery_app.conf.beat_schedule = { - "check-sources-hourly": { + "check-sources-scheduler": { "task": "app.tasks.downloads.scheduled_check", - "schedule": settings.default_check_interval, # Default: every hour + "schedule": 60, # Run every 60 seconds to check for due sources }, "cleanup-old-downloads-daily": { "task": "app.tasks.downloads.cleanup_old_downloads", diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 14b0e4d..e97976a 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -22,7 +22,6 @@ services: - SECRET_KEY=${SECRET_KEY:-dev-secret-key-not-for-production} - DOWNLOAD_PARALLEL_LIMIT=${DOWNLOAD_PARALLEL_LIMIT:-3} - DOWNLOAD_RATE_LIMIT=${DOWNLOAD_RATE_LIMIT:-3.0} - - DEFAULT_CHECK_INTERVAL=${DEFAULT_CHECK_INTERVAL:-3600} - LOG_LEVEL=DEBUG - DEBUG=true volumes: @@ -47,7 +46,6 @@ services: - SECRET_KEY=${SECRET_KEY:-dev-secret-key-not-for-production} - DOWNLOAD_PARALLEL_LIMIT=${DOWNLOAD_PARALLEL_LIMIT:-3} - DOWNLOAD_RATE_LIMIT=${DOWNLOAD_RATE_LIMIT:-3.0} - - DEFAULT_CHECK_INTERVAL=${DEFAULT_CHECK_INTERVAL:-3600} - LOG_LEVEL=DEBUG - DEBUG=true volumes: diff --git a/docker-compose.yml b/docker-compose.yml index 00979de..94402b8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,6 @@ services: - SECRET_KEY=${SECRET_KEY} - DOWNLOAD_PARALLEL_LIMIT=${DOWNLOAD_PARALLEL_LIMIT:-3} - DOWNLOAD_RATE_LIMIT=${DOWNLOAD_RATE_LIMIT:-3.0} - - DEFAULT_CHECK_INTERVAL=${DEFAULT_CHECK_INTERVAL:-3600} - LOG_LEVEL=${LOG_LEVEL:-INFO} volumes: - ${DATA_DOWNLOADS:-downloads}:/data/downloads @@ -43,7 +42,6 @@ services: - SECRET_KEY=${SECRET_KEY} - DOWNLOAD_PARALLEL_LIMIT=${DOWNLOAD_PARALLEL_LIMIT:-3} - DOWNLOAD_RATE_LIMIT=${DOWNLOAD_RATE_LIMIT:-3.0} - - DEFAULT_CHECK_INTERVAL=${DEFAULT_CHECK_INTERVAL:-3600} - LOG_LEVEL=${LOG_LEVEL:-INFO} volumes: - ${DATA_DOWNLOADS:-downloads}:/data/downloads