Files
Aniworld/src/server/api/scheduler.py
Lukas c39ae9d0fc feat(scheduler): add folder_scan_enabled toggle to SchedulerConfig
- Add folder_scan_enabled boolean field (default false) to SchedulerConfig
- Update data/config.json example with new field
- Add checkbox to setup.html and include in JS payload
- Handle field in auth.py setup endpoint
- Expose field in scheduler API response
- Log and return field in scheduler_service.py
- Update docs/CONFIGURATION.md and docs/ARCHITECTURE.md
- Update index.html UI, app.js and scheduler-config.js handlers
- Verified backward compatibility: old configs load with default False
2026-05-11 21:02:05 +02:00

157 lines
5.3 KiB
Python

"""Scheduler API endpoints for Aniworld.
This module provides endpoints for managing scheduled tasks such as
automatic anime library rescans.
"""
import logging
from typing import Any, Dict, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from src.server.models.config import SchedulerConfig
from src.server.services.config_service import ConfigServiceError, get_config_service
from src.server.services.scheduler_service import get_scheduler_service
from src.server.utils.dependencies import require_auth
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/scheduler", tags=["scheduler"])
def _build_response(config: SchedulerConfig) -> Dict[str, Any]:
"""Build a standardised GET/POST response combining config + runtime status."""
scheduler_service = get_scheduler_service()
runtime = scheduler_service.get_status()
return {
"success": True,
"config": {
"enabled": config.enabled,
"interval_minutes": config.interval_minutes,
"schedule_time": config.schedule_time,
"schedule_days": config.schedule_days,
"auto_download_after_rescan": config.auto_download_after_rescan,
"folder_scan_enabled": config.folder_scan_enabled,
},
"status": {
"is_running": runtime.get("is_running", False),
"next_run": runtime.get("next_run"),
"last_run": runtime.get("last_run"),
"scan_in_progress": runtime.get("scan_in_progress", False),
},
}
@router.get("/config")
def get_scheduler_config(
auth: Optional[dict] = Depends(require_auth),
) -> Dict[str, Any]:
"""Get current scheduler configuration along with runtime status.
Returns:
Combined config and status response.
Raises:
HTTPException: 500 if configuration cannot be loaded.
"""
try:
config_service = get_config_service()
app_config = config_service.load_config()
return _build_response(app_config.scheduler)
except ConfigServiceError as exc:
logger.error("Failed to load scheduler config: %s", exc)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to load scheduler configuration: {exc}",
) from exc
@router.post("/config")
def update_scheduler_config(
scheduler_config: SchedulerConfig,
auth: dict = Depends(require_auth),
) -> Dict[str, Any]:
"""Update scheduler configuration and apply changes immediately.
Accepts the full SchedulerConfig body; any fields not supplied default
to their model defaults (backward compatible).
Returns:
Combined config and status response reflecting the saved config.
Raises:
HTTPException: 422 on validation errors (handled by FastAPI/Pydantic),
500 on save or scheduler failure.
"""
try:
config_service = get_config_service()
app_config = config_service.load_config()
app_config.scheduler = scheduler_config
config_service.save_config(app_config)
logger.info(
"Scheduler config updated by %s: time=%s days=%s auto_dl=%s",
auth.get("username", "unknown"),
scheduler_config.schedule_time,
scheduler_config.schedule_days,
scheduler_config.auto_download_after_rescan,
)
# Apply changes to the running scheduler without restart
try:
sched_svc = get_scheduler_service()
sched_svc.reload_config(scheduler_config)
except Exception as sched_exc: # pylint: disable=broad-exception-caught
logger.error("Scheduler reload after config update failed: %s", sched_exc)
# Config was saved — don't fail the request, just warn
return _build_response(scheduler_config)
except ConfigServiceError as exc:
logger.error("Failed to update scheduler config: %s", exc)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to update scheduler configuration: {exc}",
) from exc
@router.post("/trigger-rescan", response_model=Dict[str, str])
async def trigger_rescan(auth: dict = Depends(require_auth)) -> Dict[str, str]:
"""Manually trigger a library rescan (and auto-download if configured).
Args:
auth: Authentication token (required)
Returns:
Dict with success message
Raises:
HTTPException: If rescan cannot be triggered
"""
try:
from src.server.utils.dependencies import get_series_app # noqa: PLC0415
series_app = get_series_app()
if not series_app:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="SeriesApp not initialized",
)
logger.info(
"Manual rescan triggered by %s", auth.get("username", "unknown")
)
from src.server.api.anime import trigger_rescan as do_rescan # noqa: PLC0415
return await do_rescan()
except HTTPException:
raise
except Exception as exc:
logger.exception("Failed to trigger manual rescan")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to trigger rescan: {exc}",
) from exc