""" watchdog_process.py — Kører som selvstændig subprocess. Overvåger musikmapper og opdaterer SQLite ved fil-ændringer. Start: python watchdog_process.py """ import sys import os import time import json import sqlite3 import logging from pathlib import Path logging.basicConfig( level=logging.INFO, format="%(asctime)s [watchdog] %(message)s", stream=sys.stderr ) logger = logging.getLogger(__name__) SUPPORTED = {'.mp3', '.flac', '.m4a', '.ogg', '.wav', '.aiff', '.wma'} def is_supported(path: Path) -> bool: return path.suffix.lower() in SUPPORTED def get_libraries(db_path: str) -> list[dict]: try: conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row libs = conn.execute( "SELECT id, path FROM libraries WHERE is_active=1" ).fetchall() conn.close() return [dict(l) for l in libs] except Exception: return [] def process_file(db_path: str, library_id: int, file_path: str, deleted: bool = False): """Opdater SQLite for én fil.""" try: # Tilføj app-mappen til sys.path så tag_reader kan importeres app_dir = str(Path(__file__).parent.parent) if app_dir not in sys.path: sys.path.insert(0, app_dir) conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row if deleted: conn.execute( "UPDATE songs SET file_missing=1 WHERE local_path=?", (file_path,) ) conn.commit() conn.close() return from local.tag_reader import read_tags import uuid mtime = str(os.path.getmtime(file_path)) tags = read_tags(Path(file_path)) extra = json.dumps(tags.get("extra_tags", {}), ensure_ascii=False) existing = conn.execute( "SELECT id, bpm FROM songs WHERE local_path=?", (file_path,) ).fetchone() if existing: bpm = tags.get("bpm", 0) or existing["bpm"] or 0 conn.execute(""" UPDATE songs SET library_id=?, title=?, artist=?, album=?, bpm=?, duration_sec=?, file_format=?, file_modified_at=?, file_missing=0, extra_tags=? WHERE id=? """, (library_id, tags.get("title",""), tags.get("artist",""), tags.get("album",""), bpm, tags.get("duration_sec",0), tags.get("file_format",""), mtime, extra, existing["id"])) else: conn.execute(""" INSERT OR IGNORE INTO songs (id, library_id, local_path, title, artist, album, bpm, duration_sec, file_format, file_modified_at, extra_tags) VALUES (?,?,?,?,?,?,?,?,?,?,?) """, (str(uuid.uuid4()), library_id, file_path, tags.get("title",""), tags.get("artist",""), tags.get("album",""), tags.get("bpm",0), tags.get("duration_sec",0), tags.get("file_format",""), mtime, extra)) conn.commit() conn.close() logger.info(f"Opdateret: {Path(file_path).name}") except Exception as e: logger.error(f"Fejl ved {file_path}: {e}") def run(db_path: str): try: from watchdog.observers.polling import PollingObserver from watchdog.events import FileSystemEventHandler except ImportError: logger.error("watchdog ikke installeret") sys.exit(1) class Handler(FileSystemEventHandler): def __init__(self, library_id: int): self.library_id = library_id def on_created(self, event): if not event.is_directory and is_supported(Path(event.src_path)): time.sleep(0.5) # Vent til filen er skrevet færdig process_file(db_path, self.library_id, event.src_path) def on_modified(self, event): if not event.is_directory and is_supported(Path(event.src_path)): process_file(db_path, self.library_id, event.src_path) def on_deleted(self, event): if not event.is_directory and is_supported(Path(event.src_path)): process_file(db_path, self.library_id, event.src_path, deleted=True) def on_moved(self, event): if not event.is_directory: if is_supported(Path(event.src_path)): process_file(db_path, self.library_id, event.src_path, deleted=True) if is_supported(Path(event.dest_path)): process_file(db_path, self.library_id, event.dest_path) # Brug 60 sekunders poll-interval — opdager ændringer inden for 1 minut observer = PollingObserver(timeout=60) libraries = get_libraries(db_path) if not libraries: logger.info("Ingen biblioteker — venter...") for lib in libraries: path = Path(lib["path"]) if path.exists(): observer.schedule(Handler(lib["id"]), str(path), recursive=True) logger.info(f"Overvåger: {path}") else: logger.warning(f"Mappe ikke fundet: {path}") observer.start() logger.info("Watchdog kører") try: while True: time.sleep(30) # Tjek om der er kommet nye biblioteker siden start current = get_libraries(db_path) current_paths = {lib["path"] for lib in current} watched_paths = {str(w.path) for w in observer.emitters} for lib in current: if lib["path"] not in watched_paths: path = Path(lib["path"]) if path.exists(): observer.schedule( Handler(lib["id"]), str(path), recursive=True ) logger.info(f"Tilføjet overvågning: {path}") except KeyboardInterrupt: pass finally: observer.stop() observer.join() logger.info("Watchdog stoppet") if __name__ == "__main__": if len(sys.argv) < 2: print("Brug: python watchdog_process.py ") sys.exit(1) run(sys.argv[1])