Files
Mediatracker/backend/scheduler.py
snoopy 6f551ba439 Initial release — MediaTracker v1.0
Dockerized media library scanner for NAS drives.
Identifies movies and series from filenames, fetches posters
via the public IMDB suggestion API (no key required), and
exposes a dark-themed web UI with multilingual search support.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 11:40:44 +00:00

96 lines
2.7 KiB
Python

"""
scheduler.py — Background scan scheduler using APScheduler.
"""
import logging
import threading
from apscheduler.schedulers.background import BackgroundScheduler
from config import Settings
from scanner import (
create_scan_record,
has_any_scan_completed,
run_full_scan,
run_incremental_scan,
)
logger = logging.getLogger(__name__)
_scheduler: BackgroundScheduler | None = None
_scan_lock = threading.Lock()
_active_scan_id: int | None = None
def get_active_scan_id() -> int | None:
return _active_scan_id
def is_scan_running() -> bool:
return _active_scan_id is not None
def _run_scan_protected(scan_type: str, settings: Settings) -> int | None:
"""Try to acquire lock and run a scan. Returns scan_id or None if already running."""
global _active_scan_id
if not _scan_lock.acquire(blocking=False):
logger.warning("Scan already in progress — skipping %s scan request", scan_type)
return None
scan_id = create_scan_record(scan_type)
_active_scan_id = scan_id
logger.info("Scan %s started (id=%d)", scan_type, scan_id)
def _run():
global _active_scan_id
try:
if scan_type == "full":
run_full_scan(settings, scan_id)
else:
run_incremental_scan(settings, scan_id)
finally:
_active_scan_id = None
_scan_lock.release()
logger.info("Scan %s finished (id=%d)", scan_type, scan_id)
thread = threading.Thread(target=_run, daemon=True, name=f"scan-{scan_type}-{scan_id}")
thread.start()
return scan_id
def trigger_full_scan(settings: Settings) -> int | None:
return _run_scan_protected("full", settings)
def trigger_incremental_scan(settings: Settings) -> int | None:
return _run_scan_protected("incremental", settings)
def start_scheduler(settings: Settings) -> None:
global _scheduler
_scheduler = BackgroundScheduler(timezone="UTC")
_scheduler.add_job(
func=lambda: trigger_incremental_scan(settings),
trigger="interval",
hours=settings.scan_interval_hours,
id="incremental_scan",
replace_existing=True,
)
_scheduler.start()
logger.info(
"Scheduler started — incremental scan every %d hour(s)",
settings.scan_interval_hours,
)
# Trigger initial full scan if the database has never been scanned
if not has_any_scan_completed():
logger.info("No prior completed scan found — launching initial full scan")
trigger_full_scan(settings)
def stop_scheduler() -> None:
if _scheduler and _scheduler.running:
_scheduler.shutdown(wait=False)
logger.info("Scheduler stopped")