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>
This commit is contained in:
95
backend/scheduler.py
Normal file
95
backend/scheduler.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
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")
|
||||
Reference in New Issue
Block a user