diff --git a/core/repair_worker.py b/core/repair_worker.py index 50d90b27..3885341b 100644 --- a/core/repair_worker.py +++ b/core/repair_worker.py @@ -12,6 +12,7 @@ import os import re import shutil import sys +import sqlite3 import threading import time import uuid @@ -2231,6 +2232,46 @@ class RepairWorker: album_folder, filename_pattern, download_folder): """Move or copy a candidate track into the album folder and update DB.""" try: + def _fallback_server_source(): + if getattr(candidate, 'server_source', None): + return candidate.server_source + if self._config_manager: + getter = getattr(self._config_manager, 'get_active_media_server', None) + if callable(getter): + return getter() or 'plex' + return self._config_manager.get('active_media_server', 'plex') + return 'plex' + + def _resolve_target_context(cursor): + cursor.execute( + """ + SELECT artist_id, server_source + FROM tracks + WHERE album_id = ? + ORDER BY track_number, title + LIMIT 1 + """, + (album_id,), + ) + row = cursor.fetchone() + if row: + return row[0] or candidate.artist_id, row[1] or _fallback_server_source() + + try: + cursor.execute( + "SELECT artist_id, server_source FROM albums WHERE id = ? LIMIT 1", + (album_id,), + ) + except sqlite3.OperationalError: + row = None + else: + row = cursor.fetchone() + + if row: + return row[0] or candidate.artist_id, row[1] or _fallback_server_source() + + return candidate.artist_id, _fallback_server_source() + # Resolve source file src_path = _resolve_file_path(candidate.file_path, self.transfer_folder, download_folder, config_manager=self._config_manager) if not src_path or not os.path.exists(src_path): @@ -2264,18 +2305,15 @@ class RepairWorker: # Update existing DB record to point to new album and path conn = self.db._get_connection() cursor = conn.cursor() - # Get the target album's artist_id for consistency - cursor.execute("SELECT artist_id FROM tracks WHERE album_id = ? LIMIT 1", (album_id,)) - artist_row = cursor.fetchone() - target_artist_id = artist_row[0] if artist_row else candidate.artist_id + target_artist_id, target_server_source = _resolve_target_context(cursor) cursor.execute(""" UPDATE tracks SET album_id = ?, artist_id = ?, title = ?, - file_path = ?, track_number = ?, + file_path = ?, track_number = ?, server_source = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? """, (album_id, target_artist_id, track_name, - target_path, track_number, candidate.id)) + target_path, track_number, target_server_source, candidate.id)) # Clean up the source single's album if it's now empty cursor.execute("SELECT COUNT(*) FROM tracks WHERE album_id = ?", (candidate.album_id,)) @@ -2300,17 +2338,14 @@ class RepairWorker: conn = self.db._get_connection() cursor = conn.cursor() - # Get artist_id from existing album tracks - cursor.execute("SELECT artist_id FROM tracks WHERE album_id = ? LIMIT 1", (album_id,)) - artist_row = cursor.fetchone() - target_artist_id = artist_row[0] if artist_row else candidate.artist_id + target_artist_id, target_server_source = _resolve_target_context(cursor) cursor.execute(""" INSERT INTO tracks (id, album_id, artist_id, title, track_number, duration, - file_path, bitrate, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + file_path, bitrate, server_source, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) """, (new_track_id, album_id, target_artist_id, track_name, track_number, - candidate.duration, target_path, candidate.bitrate)) + candidate.duration, target_path, candidate.bitrate, target_server_source)) conn.commit() finally: diff --git a/tests/test_repair_worker_album_fill.py b/tests/test_repair_worker_album_fill.py index e8ebc23c..57a44bcd 100644 --- a/tests/test_repair_worker_album_fill.py +++ b/tests/test_repair_worker_album_fill.py @@ -62,6 +62,7 @@ def test_perform_album_fill_copy_branch_generates_track_id(tmp_path, monkeypatch duration INTEGER, file_path TEXT, bitrate INTEGER, + server_source TEXT, created_at TEXT, updated_at TEXT ) @@ -69,10 +70,10 @@ def test_perform_album_fill_copy_branch_generates_track_id(tmp_path, monkeypatch ) conn.execute( """ - INSERT INTO tracks (id, album_id, artist_id, title, track_number, duration, file_path, bitrate, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + INSERT INTO tracks (id, album_id, artist_id, title, track_number, duration, file_path, bitrate, server_source, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) """, - ("target-track-1", "target-album", "target-artist", "Existing Track", 1, 180000, str(src_path), 320), + ("target-track-1", "target-album", "target-artist", "Existing Track", 1, 180000, str(src_path), 320, "navidrome"), ) conn.commit() @@ -89,6 +90,7 @@ def test_perform_album_fill_copy_branch_generates_track_id(tmp_path, monkeypatch duration=180000, file_path=str(src_path), bitrate=320, + server_source="soulsync", ), SimpleNamespace( id="source-track-2", @@ -97,6 +99,7 @@ def test_perform_album_fill_copy_branch_generates_track_id(tmp_path, monkeypatch duration=181000, file_path=str(src_path), bitrate=320, + server_source="soulsync", ), ] @@ -120,6 +123,7 @@ def test_perform_album_fill_copy_branch_generates_track_id(tmp_path, monkeypatch duration=180000, file_path=str(src_path), bitrate=320, + server_source="soulsync", ), album_id="target-album", album_title="Target Album", @@ -137,7 +141,7 @@ def test_perform_album_fill_copy_branch_generates_track_id(tmp_path, monkeypatch with sqlite3.connect(db_path) as verify_conn: row = verify_conn.execute( - "SELECT id, title, file_path FROM tracks WHERE title = ?", + "SELECT id, title, file_path, server_source FROM tracks WHERE title = ?", ("New Track",), ).fetchone() assert row is not None @@ -145,4 +149,5 @@ def test_perform_album_fill_copy_branch_generates_track_id(tmp_path, monkeypatch assert row[0].startswith("album_fill_source-track-1_deadbeef") assert row[1] == "New Track" assert Path(row[2]).exists() + assert row[3] == "navidrome" assert verify_conn.execute("SELECT COUNT(*) FROM tracks WHERE id IS NULL").fetchone()[0] == 0