diff --git a/linedance-app/local/acoustid_worker.py b/linedance-app/local/acoustid_worker.py index 30b99890..b9f95d6b 100644 --- a/linedance-app/local/acoustid_worker.py +++ b/linedance-app/local/acoustid_worker.py @@ -159,11 +159,11 @@ def run_acoustid_scan(db_path: str, api_key: str = "", on_progress=None, stop_ev conn.row_factory = sqlite3.Row rows = conn.execute(""" - SELECT id, local_path, title, artist - FROM songs - WHERE (mbid IS NULL OR mbid = '') - AND file_missing = 0 - AND local_path IS NOT NULL AND local_path != '' + SELECT s.id, s.title, s.artist, f.local_path + FROM songs s + JOIN files f ON f.song_id = s.id AND f.file_missing = 0 + WHERE (s.mbid IS NULL OR s.mbid = '') + AND f.local_path IS NOT NULL AND f.local_path != '' ORDER BY RANDOM() LIMIT ? """, (MAX_PER_SESSION,)).fetchall() @@ -284,4 +284,4 @@ class AcoustIDWorker: self._stop_event.set() def is_running(self) -> bool: - return self._running + return self._running \ No newline at end of file diff --git a/linedance-app/local/scanner.py b/linedance-app/local/scanner.py index 2af684ca..b85a5817 100644 --- a/linedance-app/local/scanner.py +++ b/linedance-app/local/scanner.py @@ -11,6 +11,62 @@ from pathlib import Path logger = logging.getLogger(__name__) SUPPORTED = {'.mp3', '.flac', '.m4a', '.ogg', '.wav', '.aiff', '.wma'} +import uuid as _uuid_module + + +def _find_or_create_song_conn(conn, title, artist, album, bpm, + duration_sec, mbid, acoustid) -> str: + """Find eller opret sang via eksisterende forbindelse.""" + if mbid: + row = conn.execute("SELECT id FROM songs WHERE mbid=?", (mbid,)).fetchone() + if row: + return row["id"] + if acoustid: + row = conn.execute("SELECT id FROM songs WHERE acoustid=?", (acoustid,)).fetchone() + if row: + if mbid: + conn.execute("UPDATE songs SET mbid=? WHERE id=? AND mbid IS NULL", (mbid, row["id"])) + return row["id"] + if title: + row = conn.execute( + "SELECT id FROM songs WHERE title=? AND artist=?", (title, artist) + ).fetchone() + if row: + if mbid: + conn.execute("UPDATE songs SET mbid=? WHERE id=? AND mbid IS NULL", (mbid, row["id"])) + return row["id"] + new_id = str(_uuid_module.uuid4()) + conn.execute( + "INSERT INTO songs (id, title, artist, album, bpm, duration_sec, mbid, acoustid) " + "VALUES (?,?,?,?,?,?,?,?)", + (new_id, title, artist, album, bpm, duration_sec, mbid or None, acoustid or None) + ) + return new_id + + +def _upsert_file_conn(conn, song_id, local_path, file_format, + file_modified_at, extra_tags) -> str: + """Opret eller opdater fil-post via eksisterende forbindelse.""" + existing = conn.execute( + "SELECT id FROM files WHERE local_path=?", (local_path,) + ).fetchone() + if existing: + conn.execute(""" + UPDATE files SET song_id=?, file_missing=0, + file_format=?, file_modified_at=?, extra_tags=? + WHERE id=? + """, (song_id, file_format, file_modified_at, extra_tags, existing["id"])) + return existing["id"] + else: + file_id = str(_uuid_module.uuid4()) + conn.execute( + "INSERT INTO files (id, song_id, local_path, file_format, file_modified_at, extra_tags) " + "VALUES (?,?,?,?,?,?)", + (file_id, song_id, local_path, file_format, file_modified_at, extra_tags) + ) + return file_id + + def is_supported(path) -> bool: @@ -33,7 +89,6 @@ def scan_library(library_id: int, library_path: str, db_path: str, """ import sqlite3 from local.tag_reader import read_tags - from local.local_db import find_or_create_song, upsert_file base = Path(library_path) if not base.exists(): @@ -84,16 +139,16 @@ def scan_library(library_id: int, library_path: str, db_path: str, acoustid = tags.get("acoustid", "") duration_sec = tags.get("duration_sec", 0) file_format = tags.get("file_format", fp.suffix.lstrip(".").lower()) - extra_tags = tags.get("extra_tags", "{}") + import json as _json + _extra = tags.get("extra_tags", {}) + extra_tags = _json.dumps(_extra) if isinstance(_extra, dict) else (_extra or "{}") - # Find eller opret sang i global katalog - song_id = find_or_create_song( - title=title, artist=artist, album=album, - bpm=bpm, duration_sec=duration_sec, - mbid=mbid, acoustid=acoustid, + # Find eller opret sang — alt via samme conn + song_id = _find_or_create_song_conn( + conn, title, artist, album, bpm, duration_sec, mbid, acoustid ) - # Opdater BPM på sangen hvis vi har bedre data + # Opdater BPM if bpm and bpm > 0: conn.execute( "UPDATE songs SET bpm=? WHERE id=? AND (bpm=0 OR bpm IS NULL)", @@ -101,13 +156,7 @@ def scan_library(library_id: int, library_path: str, db_path: str, ) # Opret eller opdater fil-post - upsert_file( - song_id=song_id, - local_path=path_str, - file_format=file_format, - file_modified_at=mtime, - extra_tags=extra_tags, - ) + _upsert_file_conn(conn, song_id, path_str, file_format, mtime, extra_tags) # Dans-tags fra fil file_dances = tags.get("dances", []) diff --git a/linedance-app/ui/library_manager.py b/linedance-app/ui/library_manager.py index b10d6aba..8a52ef64 100644 --- a/linedance-app/ui/library_manager.py +++ b/linedance-app/ui/library_manager.py @@ -79,7 +79,7 @@ class LibraryManagerDialog(QDialog): conn = sqlite3.connect(self._db_path) conn.row_factory = sqlite3.Row libs = conn.execute( - "SELECT id, path, last_full_scan FROM libraries " + "SELECT id, path FROM libraries " "WHERE is_active=1 ORDER BY path" ).fetchall() @@ -87,15 +87,16 @@ class LibraryManagerDialog(QDialog): bpm_missing = {} for lib in libs: counts[lib["id"]] = conn.execute( - "SELECT COUNT(*) FROM songs " - "WHERE library_id=? AND file_missing=0", - (lib["id"],) + "SELECT COUNT(*) FROM files " + "WHERE file_missing=0 AND local_path LIKE ?", + (lib["path"] + "%",) ).fetchone()[0] bpm_missing[lib["id"]] = conn.execute( - "SELECT COUNT(*) FROM songs " - "WHERE library_id=? AND file_missing=0 " - "AND (bpm IS NULL OR bpm=0)", - (lib["id"],) + "SELECT COUNT(*) FROM files f " + "JOIN songs s ON s.id = f.song_id " + "WHERE f.file_missing=0 AND f.local_path LIKE ? " + "AND (s.bpm IS NULL OR s.bpm=0)", + (lib["path"] + "%",) ).fetchone()[0] conn.close() @@ -122,9 +123,7 @@ class LibraryManagerDialog(QDialog): lib_id = lib["id"] path = lib["path"] exists = Path(path).exists() - last = lib.get("last_full_scan") or "aldrig" - if isinstance(last, str) and len(last) > 16: - last = last[:16] + last = "—" frame = QFrame() frame.setObjectName("track_display") @@ -246,11 +245,12 @@ class LibraryManagerDialog(QDialog): try: conn = sqlite3.connect(self._db_path) - # Slet sange fra biblioteket + # Marker filer fra denne mappe som missing conn.execute( - "DELETE FROM songs WHERE library_id=?", (lib["id"],) + "UPDATE files SET file_missing=1 WHERE local_path LIKE ?", + (lib["path"] + "%",) ) - # Slet selve biblioteks-rækken helt + # Slet selve biblioteks-rækken conn.execute( "DELETE FROM libraries WHERE id=?", (lib["id"],) ) @@ -297,4 +297,4 @@ class LibraryManagerDialog(QDialog): for w in list(self._workers.values()): w.cancel() w.wait(2000) # Vent max 2 sek på at tråden stopper - event.accept() + event.accept() \ No newline at end of file diff --git a/linedance-app/ui/main_window.py b/linedance-app/ui/main_window.py index 78e8d0bf..44dadf67 100644 --- a/linedance-app/ui/main_window.py +++ b/linedance-app/ui/main_window.py @@ -505,19 +505,19 @@ class MainWindow(QMainWindow): conn.row_factory = sqlite3.Row rows = conn.execute(""" SELECT s.id, s.title, s.artist, s.album, s.bpm, - s.duration_sec, s.local_path, s.file_format, - s.file_missing, + s.duration_sec, + f.id as file_id, f.local_path, f.file_format, f.file_missing, GROUP_CONCAT(d.name, ',') AS dance_names, GROUP_CONCAT(COALESCE(dl.name,''), ',') AS dance_levels, GROUP_CONCAT(COALESCE(d.choreographer,''), ',') AS dance_choreographers, GROUP_CONCAT(DISTINCT ad.name) AS alt_dance_names FROM songs s + JOIN files f ON f.song_id = s.id AND f.file_missing = 0 LEFT JOIN song_dances sd ON sd.song_id = s.id LEFT JOIN dances d ON d.id = sd.dance_id LEFT JOIN dance_levels dl ON dl.id = d.level_id LEFT JOIN song_alt_dances sad ON sad.song_id = s.id LEFT JOIN dances ad ON ad.id = sad.dance_id - WHERE s.file_missing = 0 GROUP BY s.id ORDER BY s.artist, s.title """).fetchall() @@ -536,6 +536,7 @@ class MainWindow(QMainWindow): "album": row["album"], "bpm": row["bpm"], "duration_sec": row["duration_sec"], + "file_id": row["file_id"], "local_path": row["local_path"], "file_format": row["file_format"], "file_missing": bool(row["file_missing"]), @@ -1476,4 +1477,4 @@ class MainWindow(QMainWindow): self._watchdog_proc.terminate() except Exception: pass - event.accept() + event.accept() \ No newline at end of file