diff --git a/core/downloads/status.py b/core/downloads/status.py index f425cceb..4e4fec84 100644 --- a/core/downloads/status.py +++ b/core/downloads/status.py @@ -796,6 +796,13 @@ def build_unified_downloads_response(limit: int, deps: StatusDeps) -> dict: 'progress': progress, 'error': task.get('error_message'), 'verification_status': task.get('verification_status'), + # library_history row id (set at import) so the Unverified review + # queue can act on a still-live completed task before it becomes + # a persistent-history row. + 'history_id': task.get('history_id'), + # Real probed audio quality (mutagen-read from the actual file), + # surfaced so the Downloads page can show what was downloaded. + 'quality': task.get('quality') or '', 'retry_info': task.get('retry_info'), 'retry_trigger': task.get('retry_trigger'), 'batch_id': batch_id, diff --git a/core/imports/pipeline.py b/core/imports/pipeline.py index 464c5ce5..068e2146 100644 --- a/core/imports/pipeline.py +++ b/core/imports/pipeline.py @@ -1157,6 +1157,10 @@ def post_process_matched_download_with_verification(context_key, context, file_p _mark_task_completed(task_id, context.get('track_info')) if context.get('_verification_status'): download_tasks[task_id]['verification_status'] = context['_verification_status'] + if context.get('_history_id'): + download_tasks[task_id]['history_id'] = context['_history_id'] + if context.get('_audio_quality'): + download_tasks[task_id]['quality'] = context['_audio_quality'] with matched_context_lock: if context_key in matched_downloads_context: del matched_downloads_context[context_key] @@ -1171,6 +1175,10 @@ def post_process_matched_download_with_verification(context_key, context, file_p download_tasks[task_id]['metadata_enhanced'] = True if context.get('_verification_status'): download_tasks[task_id]['verification_status'] = context['_verification_status'] + if context.get('_history_id'): + download_tasks[task_id]['history_id'] = context['_history_id'] + if context.get('_audio_quality'): + download_tasks[task_id]['quality'] = context['_audio_quality'] redownload_ctx = download_tasks[task_id].get('_redownload_context') with matched_context_lock: diff --git a/core/imports/side_effects.py b/core/imports/side_effects.py index 20fe0017..0898fdcb 100644 --- a/core/imports/side_effects.py +++ b/core/imports/side_effects.py @@ -252,7 +252,7 @@ def record_library_history_download(context: Dict[str, Any]) -> None: origin, origin_context = derive_download_origin(context) db = get_database() - db.add_library_history_entry( + _history_id = db.add_library_history_entry( event_type="download", title=title, artist_name=artist_name, @@ -270,6 +270,10 @@ def record_library_history_download(context: Dict[str, Any]) -> None: origin_context=origin_context, verification_status=context.get("_verification_status"), ) + # Stash the row id so the live download task can link to its + # library_history row (the Unverified review queue needs it). + if isinstance(_history_id, int) and _history_id > 0: + context["_history_id"] = _history_id except Exception as e: logger.debug("library history record failed: %s", e) diff --git a/database/music_database.py b/database/music_database.py index eedc64ad..03360016 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -13062,7 +13062,10 @@ class MusicDatabase: download_source, source_track_id, source_track_title, source_filename, acoustid_result, source_artist, origin, origin_context, verification_status)) conn.commit() - return True + # Return the new row id (truthy on success) so callers can link the + # live download task to its library_history row — e.g. the Unverified + # review queue needs the id for its play/approve/delete actions. + return cursor.lastrowid except Exception as e: logger.debug(f"Error adding library history entry: {e}") return False diff --git a/webui/static/pages-extra.js b/webui/static/pages-extra.js index 5ab860e1..0dd09fc8 100644 --- a/webui/static/pages-extra.js +++ b/webui/static/pages-extra.js @@ -2430,6 +2430,14 @@ function _updateDlNavBadge(count) { } } +function _adlQualityBadge(dl) { + // Show the real audio quality of a completed download (probed from the + // file itself — FLAC bit depth, MP3 bitrate, …), so you can see at a + // glance what was actually fetched. + if (dl.status !== 'completed' || !dl.quality) return ''; + return ` ${_adlEsc(dl.quality)}`; +} + function _adlVerifBadge(dl) { // Verification badge for completed downloads — how this file passed // verification (status comes from library_history / the live task): @@ -2457,9 +2465,15 @@ function _adlVerifBadge(dl) { function verifHistoryId(dl) { // Persistent history rows carry task_id 'history-'. - if (!dl.is_persistent_history || !dl.task_id) return null; - const m = String(dl.task_id).match(/^history-(\d+)$/); - return m ? m[1] : null; + if (dl.is_persistent_history && dl.task_id) { + const m = String(dl.task_id).match(/^history-(\d+)$/); + if (m) return m[1]; + } + // Still-live completed tasks carry the library_history id directly, so the + // review actions (play/audit/approve/delete) work before the task becomes + // a persistent-history row — otherwise the buttons "didn't always load". + if (dl.history_id) return String(dl.history_id); + return null; } function _verifTimeAgo(iso) { @@ -3132,7 +3146,7 @@ function _adlRender() {
- ${statusLabel}${_adlVerifBadge(dl)}${dl.retry_info && (statusClass === 'active' || statusClass === 'queued') ? ` 🔁 ${_adlEsc(String(dl.retry_info))}` : ''} + ${statusLabel}${_adlVerifBadge(dl)}${_adlQualityBadge(dl)}${dl.retry_info && (statusClass === 'active' || statusClass === 'queued') ? ` 🔁 ${_adlEsc(String(dl.retry_info))}` : ''}
${_adlReviewActions(dl)} ${cancelBtnHtml} diff --git a/webui/static/style.css b/webui/static/style.css index 7f3cdea5..0387e792 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -68092,6 +68092,7 @@ body.em-scroll-lock { overflow: hidden; } .verif-badge.verif-unverified { color: #f1c40f; background: rgba(241,196,15,0.14); } .verif-badge.verif-force { color: #e67e22; background: rgba(230,126,34,0.16); } .adl-retry-info { margin-left: 6px; font-size: 11px; color: #e67e22; cursor: help; } +.adl-quality-chip { display: inline-block; margin-left: 6px; font-size: 10px; font-weight: 600; letter-spacing: 0.02em; color: rgba(255,255,255,0.78); background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.12); border-radius: 6px; padding: 0 6px; line-height: 16px; cursor: help; vertical-align: middle; } .verif-badge.verif-human { color: #3498db; background: rgba(52,152,219,0.14); } .verif-actions { display: inline-flex; gap: 6px; margin-left: 10px; align-items: center; } .verif-act { border: 1px solid rgba(255,255,255,0.18); background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.85); border-radius: 6px; padding: 2px 8px; font-size: 12px; cursor: pointer; line-height: 18px; }