diff --git a/core/repair_worker.py b/core/repair_worker.py index fd3520ea..0d2fc573 100644 --- a/core/repair_worker.py +++ b/core/repair_worker.py @@ -823,9 +823,36 @@ class RepairWorker: return handler(entity_type, entity_id, file_path, details) def _fix_dead_file(self, entity_type, entity_id, file_path, details): - """Add dead file track to wishlist for re-download, then remove the dead DB entry.""" + """Fix a dead file reference. Action depends on details['_fix_action']: + 'redownload' (default) — add to wishlist + remove DB entry + 'remove' — just remove the dead DB entry without re-downloading + """ if not entity_id: return {'success': False, 'error': 'No track ID associated with this finding'} + + fix_action = details.get('_fix_action', 'redownload') + + # Simple removal — just delete the dead track record + if fix_action == 'remove': + conn = None + try: + conn = self.db._get_connection() + cursor = conn.cursor() + cursor.execute("SELECT title FROM tracks WHERE id = ?", (entity_id,)) + row = cursor.fetchone() + track_name = row['title'] if row else 'Unknown' + cursor.execute("DELETE FROM tracks WHERE id = ?", (entity_id,)) + conn.commit() + return {'success': True, 'action': 'removed', + 'message': f'Removed "{track_name}" from database'} + except Exception as e: + logger.error("Dead file removal failed for track %s: %s", entity_id, e) + return {'success': False, 'error': str(e)} + finally: + if conn: + conn.close() + + # Default: re-download flow conn = None try: conn = self.db._get_connection() diff --git a/webui/static/script.js b/webui/static/script.js index 22427901..b8a9bfb6 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -63005,6 +63005,11 @@ async function fixRepairFinding(id, findingType) { fixAction = await _promptOrphanAction(); if (!fixAction) return; // User cancelled } + // Dead files: re-download or just remove from DB + if (findingType === 'dead_file') { + fixAction = await _promptDeadFileAction(); + if (!fixAction) return; + } const card = document.querySelector(`.repair-finding-card[data-id="${id}"]`); const fixBtn = card ? card.querySelector('.repair-finding-btn.fix') : null; @@ -63073,6 +63078,39 @@ function _promptOrphanAction() { }); } +function _promptDeadFileAction() { + return new Promise(resolve => { + const overlay = document.createElement('div'); + overlay.className = 'modal-overlay'; + overlay.style.cssText = 'display:flex;align-items:center;justify-content:center;z-index:10000;'; + overlay.innerHTML = ` +
+
Dead File Action
+
+ This track's file no longer exists on disk. Choose how to handle it. +
+
+ + +
+ +
+ `; + document.body.appendChild(overlay); + + overlay.querySelector('#_dead-redownload').onclick = () => { overlay.remove(); resolve('redownload'); }; + overlay.querySelector('#_dead-remove').onclick = () => { overlay.remove(); resolve('remove'); }; + overlay.querySelector('#_dead-cancel').onclick = () => { overlay.remove(); resolve(null); }; + overlay.onclick = (e) => { if (e.target === overlay) { overlay.remove(); resolve(null); } }; + }); +} + async function resolveRepairFinding(id) { try { await fetch(`/api/repair/findings/${id}/resolve`, { method: 'POST' }); @@ -63137,15 +63175,29 @@ async function bulkFixFindings() { } } + // If any selected findings are dead files, prompt for action + const selectedDeadCards = ids.filter(id => { + const card = document.querySelector(`.repair-finding-card[data-id="${id}"]`); + return card && card.dataset.jobId === 'dead_file_cleaner'; + }); + let deadFixAction = null; + if (selectedDeadCards.length > 0) { + deadFixAction = await _promptDeadFileAction(); + if (!deadFixAction) return; + } + let fixed = 0, failed = 0, lastError = ''; showToast(`Fixing ${ids.length} findings...`, 'info'); for (const id of ids) { try { - // Determine if this finding is an orphan that needs the action + // Determine if this finding needs a specific action const card = document.querySelector(`.repair-finding-card[data-id="${id}"]`); const isOrphan = card && card.dataset.jobId === 'orphan_file_detector'; - const body = isOrphan && orphanFixAction ? { fix_action: orphanFixAction } : {}; + const isDead = card && card.dataset.jobId === 'dead_file_cleaner'; + let body = {}; + if (isOrphan && orphanFixAction) body = { fix_action: orphanFixAction }; + else if (isDead && deadFixAction) body = { fix_action: deadFixAction }; const response = await fetch(`/api/repair/findings/${id}/fix`, { method: 'POST',