Add source provenance and AcoustID result to download history

Track original source filename, track ID, and AcoustID verification
result for every download. Helps debug wrong-file downloads from
streaming sources like Tidal. Each column migrated independently
for crash safety. Frontend shows source detail line and color-coded
AcoustID badge per entry. Button renamed to "Download History".
pull/289/head
Broque Thomas 1 month ago
parent afd5125262
commit f8fbcb507c

@ -509,6 +509,10 @@ class MusicDatabase:
if 'download_source' not in lh_cols:
cursor.execute("ALTER TABLE library_history ADD COLUMN download_source TEXT")
logger.info("Added download_source column to library_history")
for _col in ['source_track_id', 'source_track_title', 'source_filename', 'acoustid_result']:
if _col not in lh_cols:
cursor.execute(f"ALTER TABLE library_history ADD COLUMN {_col} TEXT")
logger.info(f"Added {_col} column to library_history")
# Sync history table — tracks the last 100 sync operations with cached context for re-trigger
cursor.execute("""
@ -9617,16 +9621,19 @@ class MusicDatabase:
def add_library_history_entry(self, event_type, title, artist_name=None, album_name=None,
quality=None, server_source=None, file_path=None, thumb_url=None,
download_source=None):
download_source=None, source_track_id=None, source_track_title=None,
source_filename=None, acoustid_result=None):
"""Record a download or import event to the library history table."""
try:
conn = self._get_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT INTO library_history (event_type, title, artist_name, album_name,
quality, server_source, file_path, thumb_url, download_source)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (event_type, title, artist_name, album_name, quality, server_source, file_path, thumb_url, download_source))
quality, server_source, file_path, thumb_url, download_source,
source_track_id, source_track_title, source_filename, acoustid_result)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (event_type, title, artist_name, album_name, quality, server_source, file_path, thumb_url,
download_source, source_track_id, source_track_title, source_filename, acoustid_result))
conn.commit()
return True
except Exception as e:

@ -1910,6 +1910,19 @@ def _record_library_history_download(context):
if isinstance(album_info, dict):
thumb_url = album_info.get('album_image_url', '')
# Source provenance — what file/track was actually downloaded
source_filename = search_result.get('filename', '')
# Track ID: try search result first, then track_info (Spotify ID used for streaming lookups)
source_track_id = (search_result.get('track_id', '')
or search_result.get('id', '')
or ti.get('id', ''))
# Source track title: only save if it differs from expected title (otherwise it's just noise)
_src_title = search_result.get('title', '') or search_result.get('name', '')
source_track_title = _src_title if _src_title and _src_title != title else ''
# AcoustID verification result
acoustid_result = context.get('_acoustid_result', '')
db = get_database()
db.add_library_history_entry(
event_type='download',
@ -1919,7 +1932,11 @@ def _record_library_history_download(context):
quality=quality,
file_path=file_path,
thumb_url=thumb_url,
download_source=download_source
download_source=download_source,
source_track_id=source_track_id,
source_track_title=source_track_title,
source_filename=source_filename,
acoustid_result=acoustid_result
)
except Exception:
pass # Non-critical, never block download flow
@ -19588,6 +19605,7 @@ def _post_process_matched_download(context_key, context, file_path):
context
)
print(f"🔍 AcoustID verification result: {verification_result.value} - {verification_msg}")
context['_acoustid_result'] = verification_result.value
if verification_result == VerificationResult.FAIL:
# Move to quarantine instead of Transfer
@ -19629,11 +19647,14 @@ def _post_process_matched_download(context_key, context, file_path):
return # NEVER continue processing a known-wrong file
else:
print(f"⚠️ AcoustID verification skipped: missing track/artist info")
context['_acoustid_result'] = 'skip'
else:
print(f" AcoustID verification not available: {available_reason}")
context['_acoustid_result'] = 'disabled'
except Exception as verify_error:
# Any verification error should NOT block the download - fail open
print(f"⚠️ AcoustID verification error (continuing normally): {verify_error}")
context['_acoustid_result'] = 'error'
# --- END ACOUSTID VERIFICATION ---
# --- SIMPLE DOWNLOAD HANDLING ---
@ -20887,6 +20908,17 @@ def get_version_info():
"title": "What's New in SoulSync",
"subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes",
"sections": [
{
"title": "📋 Download History — Source Provenance",
"description": "Download history now tracks the original source file info for every download",
"features": [
"• Source filename, track ID, and original track title saved with each download",
"• AcoustID verification result (Verified/Failed/Skipped/Off) shown as a badge per entry",
"• Source details displayed in monospace under each history entry for easy debugging",
"• Settings Connections tab redesigned with collapsible accordion services and brand-colored dots"
],
"usage_note": "Click 'Download History' on the Dashboard to see source provenance for new downloads."
},
{
"title": "🗺️ Artist Map — Visualize Your Music Universe",
"description": "Three interactive canvas-based visualization modes on the Discover page",

@ -1094,7 +1094,7 @@
<div class="dashboard-section">
<div class="section-title-row">
<h3 class="section-title">Recent Activity</h3>
<button class="library-history-btn" onclick="openLibraryHistoryModal()" title="View full library history">History</button>
<button class="library-history-btn" onclick="openLibraryHistoryModal()" title="View full library history">Download History</button>
</div>
<div class="activity-feed-container" id="dashboard-activity-feed">
<div class="activity-item">

@ -21081,15 +21081,36 @@ function renderHistoryEntry(entry) {
badge = `<span class="library-history-badge import">${escapeHtml(sourceName)}</span>`;
}
// AcoustID badge
let acoustidBadge = '';
if (entry.acoustid_result) {
const _aidColors = { pass: '#4caf50', fail: '#ef5350', skip: '#ff9800', disabled: '#666', error: '#ef5350' };
const _aidLabels = { pass: 'Verified', fail: 'Failed', skip: 'Skipped', disabled: 'Off', error: 'Error' };
const color = _aidColors[entry.acoustid_result] || '#666';
const label = _aidLabels[entry.acoustid_result] || entry.acoustid_result;
acoustidBadge = `<span class="library-history-badge" style="border-color:${color};color:${color}">AcoustID: ${label}</span>`;
}
const meta = [entry.artist_name, entry.album_name].filter(Boolean).join(' — ');
// Source provenance detail line
let sourceDetail = '';
if (entry.event_type === 'download' && (entry.source_filename || entry.source_track_title)) {
const parts = [];
if (entry.source_filename) parts.push(`File: ${escapeHtml(entry.source_filename)}`);
else if (entry.source_track_title) parts.push(`Source track: ${escapeHtml(entry.source_track_title)}`);
if (entry.source_track_id) parts.push(`ID: ${escapeHtml(entry.source_track_id)}`);
sourceDetail = `<div class="library-history-entry-source">${parts.join(' · ')}</div>`;
}
return `<div class="library-history-entry">
${thumb}
<div class="library-history-entry-text">
<div class="library-history-entry-title">${escapeHtml(entry.title || 'Unknown')}</div>
<div class="library-history-entry-meta">${escapeHtml(meta)}</div>
${sourceDetail}
</div>
${badge}
${badge}${acoustidBadge}
<div class="library-history-entry-time">${formatHistoryTime(entry.created_at)}</div>
</div>`;
}

@ -9423,6 +9423,16 @@ body.helper-mode-active #dashboard-activity-feed:hover {
margin-top: 2px;
}
.library-history-entry-source {
font-size: 10px;
color: rgba(255, 255, 255, 0.25);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 2px;
font-family: monospace;
}
.library-history-badge {
font-size: 10px;
font-weight: 600;

Loading…
Cancel
Save