From 2888a5dda144fd72540bc345233629800abe874d Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Wed, 18 Mar 2026 09:09:45 -0700 Subject: [PATCH] Fix Soulseek test crash, dedup query, and reorganize missing year - Add null guard for soulseek_client in test-connection endpoint (#190) - Fix single_album_dedup query: use al.record_type and al.track_count (al.album_type and al.total_tracks don't exist on main albums table) - Fix library reorganize missing year: pre-load album years from DB as fallback when file tags lack the date field (common with playlist syncs) --- core/repair_jobs/library_reorganize.py | 36 ++++++++++++++++++++++++++ core/repair_jobs/single_album_dedup.py | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/core/repair_jobs/library_reorganize.py b/core/repair_jobs/library_reorganize.py index 5538b62b..f77fa80a 100644 --- a/core/repair_jobs/library_reorganize.py +++ b/core/repair_jobs/library_reorganize.py @@ -330,6 +330,11 @@ class LibraryReorganizeJob(RepairJob): max_disc = max((file_tags[fp].get('disc_number', 1) for fp in fpaths), default=1) album_total_discs[key] = max_disc + # Pre-load album years from DB for files missing year tags + db_album_years = {} # (artist, album) -> year string + if '$year' in (album_template + single_template): + db_album_years = self._load_album_years(context.db) + # Track claimed destinations to detect in-batch collisions claimed_destinations = set() # Track src_dir -> dest_dir for post-pass sidecar cleanup @@ -366,6 +371,12 @@ class LibraryReorganizeJob(RepairJob): disc_number = tags.get('disc_number', 1) or 1 year = tags.get('year', '') + # Fallback: if file tags have no year, try the DB album year + if not year and db_album_years: + year = db_album_years.get((artist.lower(), (album or title).lower()), '') + if not year: + year = db_album_years.get((albumartist.lower(), (album or title).lower()), '') + # Read quality for $quality template variable quality = _get_audio_quality(fpath) @@ -624,6 +635,31 @@ class LibraryReorganizeJob(RepairJob): return context.config_manager.get(f'repair.jobs.{self.job_id}.settings.{key}', default) return default + def _load_album_years(self, db) -> dict: + """Load all album years from DB in one query. Returns {(artist_lower, album_lower): year_str}.""" + years = {} + conn = None + try: + conn = db._get_connection() + cursor = conn.cursor() + cursor.execute(""" + SELECT ar.name, al.title, al.year + FROM albums al + JOIN artists ar ON ar.id = al.artist_id + WHERE al.year IS NOT NULL AND al.year != 0 + """) + for row in cursor.fetchall(): + artist_name, album_title, year = row + if artist_name and album_title and year: + key = (artist_name.lower(), album_title.lower()) + years[key] = str(year)[:4] + except Exception as e: + logger.debug("Failed to load album years from DB: %s", e) + finally: + if conn: + conn.close() + return years + def _update_db_path(self, db, old_path: str, new_path: str, transfer_folder: str = ''): """Update file_path in the tracks table when a file is moved. diff --git a/core/repair_jobs/single_album_dedup.py b/core/repair_jobs/single_album_dedup.py index 78c471fc..2008d7c7 100644 --- a/core/repair_jobs/single_album_dedup.py +++ b/core/repair_jobs/single_album_dedup.py @@ -51,7 +51,7 @@ class SingleAlbumDedupJob(RepairJob): conn = context.db._get_connection() cursor = conn.cursor() cursor.execute(""" - SELECT t.id, t.title, ar.name, al.title, al.album_type, al.total_tracks, + SELECT t.id, t.title, ar.name, al.title, al.record_type, al.track_count, t.file_path, t.bitrate, t.duration, al.thumb_url, ar.thumb_url, t.track_number FROM tracks t