From 5be6a46fb04df0f40f317c90f1ed5a76fd8c8793 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Fri, 10 Apr 2026 17:42:39 -0700 Subject: [PATCH] Fix dismissed findings reappearing and reduce false orphan detections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The finding dedup check only looked for 'pending' and 'resolved' status, missing 'dismissed'. Dismissed findings were recreated as new entries on every scan. Now includes 'dismissed' in the dedup check. Orphan file detector improvements: - Increased path suffix matching depth from 3 to 4 segments (covers Genre/Artist/Album/track.flac paths) - Added filename-based fallback when Mutagen can't read file tags — parses title from "NN - Title [Quality].ext" pattern and matches against parent/grandparent folder names as artist --- core/repair_jobs/orphan_file_detector.py | 34 +++++++++++++++++++++--- core/repair_worker.py | 4 +-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/core/repair_jobs/orphan_file_detector.py b/core/repair_jobs/orphan_file_detector.py index 875b7665..b9f5371b 100644 --- a/core/repair_jobs/orphan_file_detector.py +++ b/core/repair_jobs/orphan_file_detector.py @@ -61,8 +61,9 @@ class OrphanFileDetectorJob(RepairJob): cursor.execute("SELECT file_path FROM tracks WHERE file_path IS NOT NULL AND file_path != ''") for row in cursor.fetchall(): parts = row[0].replace('\\', '/').split('/') - # Store last 1, 2, and 3 path components as lowercase suffixes - for depth in range(1, min(4, len(parts) + 1)): + # Store last 1-4 path components as lowercase suffixes. + # Depth 4 covers Genre/Artist/Album/track.flac scenarios. + for depth in range(1, min(5, len(parts) + 1)): suffix = '/'.join(parts[-depth:]).lower() known_suffixes.add(suffix) @@ -127,7 +128,7 @@ class OrphanFileDetectorJob(RepairJob): # Check if this file matches any known DB path via suffix matching fpath_parts = fpath.replace('\\', '/').split('/') is_known = False - for depth in range(1, min(4, len(fpath_parts) + 1)): + for depth in range(1, min(5, len(fpath_parts) + 1)): suffix = '/'.join(fpath_parts[-depth:]).lower() if suffix in known_suffixes: is_known = True @@ -161,6 +162,33 @@ class OrphanFileDetectorJob(RepairJob): except Exception: pass + # Last resort: parse title from filename pattern "NN - Title [Quality].ext" + # and match against known titles. Catches files with unreadable tags. + if not is_known and known_titles: + try: + fname_base = os.path.splitext(os.path.basename(fpath))[0] + # Strip quality tags like [FLAC 16bit], [MP3-320] + fname_clean = re.sub(r'\s*\[.*?\]\s*$', '', fname_base).strip() + # Strip leading track number: "01 - Title" → "Title" + fname_clean = re.sub(r'^\d{1,3}\s*[-–.]\s*', '', fname_clean).strip() + if fname_clean: + fname_lower = fname_clean.lower() + # Extract artist from parent folder + parent_folder = os.path.basename(os.path.dirname(fpath)).lower().strip() + # Try artist from grandparent (Artist/Album/track.flac) + grandparent = os.path.basename(os.path.dirname(os.path.dirname(fpath))).lower().strip() + for folder_artist in [parent_folder, grandparent]: + if (fname_lower, folder_artist) in known_titles: + is_known = True + break + clean_fn = _strip_extras(fname_lower) + clean_fa = _strip_extras(folder_artist) + if clean_fn and (clean_fn, clean_fa) in known_titles_clean: + is_known = True + break + except Exception: + pass + if not is_known: orphan_files.append(fpath) diff --git a/core/repair_worker.py b/core/repair_worker.py index 163fe461..ff3bd531 100644 --- a/core/repair_worker.py +++ b/core/repair_worker.py @@ -632,11 +632,11 @@ class RepairWorker: conn = self.db._get_connection() cursor = conn.cursor() - # Dedup check: skip if same finding already exists (pending OR recently resolved) + # Dedup check: skip if same finding already exists (pending, resolved, OR dismissed) cursor.execute(""" SELECT id FROM repair_findings WHERE job_id = ? AND finding_type = ? - AND status IN ('pending', 'resolved') + AND status IN ('pending', 'resolved', 'dismissed') AND ((entity_type = ? AND entity_id = ?) OR (file_path = ? AND file_path IS NOT NULL)) LIMIT 1 """, (job_id, finding_type, entity_type, entity_id, file_path))