fix: address Copilot review - align queue threshold, downgrade logs, fix XSS

This commit is contained in:
JohnBaumb 2026-04-24 01:23:46 -07:00
parent f79cb182d4
commit a827ba936a
3 changed files with 112 additions and 48 deletions

View file

@ -2546,7 +2546,8 @@ def _get_file_lock(file_path):
# Thread-safe state tracking for modal download functionality with batch management # Thread-safe state tracking for modal download functionality with batch management
missing_download_executor = ThreadPoolExecutor(max_workers=3, thread_name_prefix="MissingTrackWorker") missing_download_executor = ThreadPoolExecutor(max_workers=3, thread_name_prefix="MissingTrackWorker")
# Separate executor for analysis to prevent starvation when download workers are busy # Separate executor for analysis to prevent starvation when download workers are busy
analysis_executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix="AnalysisWorker") MAX_CONCURRENT_ANALYSIS = 3
analysis_executor = ThreadPoolExecutor(max_workers=MAX_CONCURRENT_ANALYSIS, thread_name_prefix="AnalysisWorker")
download_tasks = {} # task_id -> task state dict download_tasks = {} # task_id -> task state dict
download_batches = {} # batch_id -> {queue, active_count, max_concurrent} download_batches = {} # batch_id -> {queue, active_count, max_concurrent}
tasks_lock = threading.Lock() tasks_lock = threading.Lock()
@ -28987,11 +28988,11 @@ def _on_download_completed(batch_id, task_id, success=True):
def _submit_or_queue_batch(batch_id, playlist_id, tracks): def _submit_or_queue_batch(batch_id, playlist_id, tracks):
"""Submit a batch for analysis, or queue it if 3 analysis slots are full.""" """Submit a batch for analysis, or queue it if all analysis slots are full."""
with tasks_lock: with tasks_lock:
active_analysis_count = sum(1 for b in download_batches.values() active_analysis_count = sum(1 for b in download_batches.values()
if b.get('phase') == 'analysis') if b.get('phase') == 'analysis')
if active_analysis_count >= 3: if active_analysis_count >= MAX_CONCURRENT_ANALYSIS:
download_batches[batch_id]['phase'] = 'queued' download_batches[batch_id]['phase'] = 'queued'
download_batches[batch_id]['_queued_tracks'] = tracks download_batches[batch_id]['_queued_tracks'] = tracks
download_batches[batch_id]['_queued_playlist_id'] = playlist_id download_batches[batch_id]['_queued_playlist_id'] = playlist_id
@ -29006,7 +29007,7 @@ def _promote_queued_batches():
with tasks_lock: with tasks_lock:
active_analysis_count = sum(1 for b in download_batches.values() active_analysis_count = sum(1 for b in download_batches.values()
if b.get('phase') == 'analysis') if b.get('phase') == 'analysis')
if active_analysis_count >= 3: if active_analysis_count >= MAX_CONCURRENT_ANALYSIS:
return return
# Find batches waiting in queue, ordered by creation (dict insertion order) # Find batches waiting in queue, ordered by creation (dict insertion order)
for bid, batch in list(download_batches.items()): for bid, batch in list(download_batches.items()):
@ -29018,7 +29019,7 @@ def _promote_queued_batches():
logger.info(f"[Queue] Promoting batch {bid} ('{batch.get('playlist_name')}') from queued -> analysis") logger.info(f"[Queue] Promoting batch {bid} ('{batch.get('playlist_name')}') from queued -> analysis")
analysis_executor.submit(_run_full_missing_tracks_process, bid, queued_pid, queued_tracks) analysis_executor.submit(_run_full_missing_tracks_process, bid, queued_pid, queued_tracks)
active_analysis_count += 1 active_analysis_count += 1
if active_analysis_count >= 3: if active_analysis_count >= MAX_CONCURRENT_ANALYSIS:
break break
@ -32652,9 +32653,9 @@ def _record_sync_history_completion(batch_id, batch):
completed_count = 0 completed_count = 0
failed_count = len(batch.get('permanently_failed_tracks', [])) failed_count = len(batch.get('permanently_failed_tracks', []))
logger.warning(f"[SyncHistory] Recording completion for batch {batch_id}: " logger.info(f"[SyncHistory] Recording completion for batch {batch_id}: "
f"analysis_results={len(analysis_results)}, tracks_found={tracks_found}, " f"analysis_results={len(analysis_results)}, tracks_found={tracks_found}, "
f"queue_len={len(queue)}, failed={failed_count}") f"queue_len={len(queue)}, failed={failed_count}")
# Build download status map: track_index → status # Build download status map: track_index → status
download_status_map = {} download_status_map = {}
@ -32666,8 +32667,8 @@ def _record_sync_history_completion(batch_id, batch):
if task.get('status') == 'completed': if task.get('status') == 'completed':
completed_count += 1 completed_count += 1
logger.warning(f"[SyncHistory] Batch {batch_id}: completed_downloads={completed_count}, " logger.info(f"[SyncHistory] Batch {batch_id}: completed_downloads={completed_count}, "
f"download_status_map_size={len(download_status_map)}") f"download_status_map_size={len(download_status_map)}")
# Build per-track results from analysis # Build per-track results from analysis
track_results = [] track_results = []
@ -32709,12 +32710,12 @@ def _record_sync_history_completion(batch_id, batch):
db = MusicDatabase() db = MusicDatabase()
updated = db.update_sync_history_completion(batch_id, tracks_found, completed_count, failed_count) updated = db.update_sync_history_completion(batch_id, tracks_found, completed_count, failed_count)
logger.warning(f"[SyncHistory] DB update for batch {batch_id}: updated={updated}") logger.info(f"[SyncHistory] DB update for batch {batch_id}: updated={updated}")
# Save per-track results # Save per-track results
if track_results: if track_results:
tr_updated = db.update_sync_history_track_results(batch_id, json.dumps(track_results)) tr_updated = db.update_sync_history_track_results(batch_id, json.dumps(track_results))
logger.warning(f"[SyncHistory] Track results saved for batch {batch_id}: updated={tr_updated}, count={len(track_results)}") logger.info(f"[SyncHistory] Track results saved for batch {batch_id}: updated={tr_updated}, count={len(track_results)}")
except Exception as e: except Exception as e:
logger.warning(f"Failed to record sync history completion: {e}") logger.warning(f"Failed to record sync history completion: {e}")

View file

@ -9097,43 +9097,108 @@ function renderDiscoverSyncCard(playlist, container, sourceLabel) {
const trackLabel = isEmpty ? 'No tracks yet' : `${playlist.track_count} tracks`; const trackLabel = isEmpty ? 'No tracks yet' : `${playlist.track_count} tracks`;
card.innerHTML = ` // Build card using DOM nodes to avoid XSS from unescaped external data
<div class="discover-sync-card-icon">${playlist.icon}</div> const iconArea = document.createElement('div');
<div class="discover-sync-card-info"> iconArea.className = 'discover-sync-card-icon';
<div class="discover-sync-card-name">${playlist.name} iconArea.textContent = playlist.icon || '';
<span class="discover-sync-card-meta-inline">
<span class="discover-sync-source-badge">${sourceLabel || 'unknown'}</span> const infoArea = document.createElement('div');
<span class="discover-sync-separator">\u00b7</span> infoArea.className = 'discover-sync-card-info';
<span class="discover-sync-track-count">${trackLabel}</span>
<span class="discover-sync-separator">\u00b7</span> const nameEl = document.createElement('div');
<span class="discover-sync-status ${statusClass}">${statusText}</span> nameEl.className = 'discover-sync-card-name';
<span class="discover-sync-separator">\u00b7</span> nameEl.appendChild(document.createTextNode(playlist.name || ''));
<span class="discover-sync-last-synced">${lastSyncedText}</span>
</span> const metaInline = document.createElement('span');
</div> metaInline.className = 'discover-sync-card-meta-inline';
</div>
<div class="discover-sync-card-actions"> const sourceBadge = document.createElement('span');
<div class="discover-sync-toggle-wrapper" title="${isEmpty ? 'No tracks available — visit Discover first' : 'Keep this playlist updated automatically'}"> sourceBadge.className = 'discover-sync-source-badge';
<label class="discover-sync-toggle-label">Keep updated</label> sourceBadge.textContent = sourceLabel || 'unknown';
<label class="discover-sync-toggle">
<input type="checkbox" ${playlist.auto_update ? 'checked' : ''} ${isEmpty ? 'disabled' : ''} const sep1 = document.createElement('span');
onchange="toggleDiscoverAutoUpdate('${playlist.type}', this.checked)"> sep1.className = 'discover-sync-separator';
<span class="discover-sync-toggle-slider"></span> sep1.textContent = '\u00b7';
</label>
</div> const trackCountEl = document.createElement('span');
<button class="discover-sync-btn" id="discover-sync-btn-${playlist.type}" trackCountEl.className = 'discover-sync-track-count';
onclick="syncDiscoverPlaylistFromTab('${playlist.type}', '${playlist.name}')" trackCountEl.textContent = trackLabel;
${playlist.sync_status === 'syncing' || isEmpty ? 'disabled' : ''}>
\u27f3 Sync Now const sep2 = document.createElement('span');
</button> sep2.className = 'discover-sync-separator';
</div> sep2.textContent = '\u00b7';
`;
const statusEl = document.createElement('span');
statusEl.className = `discover-sync-status ${statusClass}`;
statusEl.textContent = statusText;
const sep3 = document.createElement('span');
sep3.className = 'discover-sync-separator';
sep3.textContent = '\u00b7';
const lastSyncedEl = document.createElement('span');
lastSyncedEl.className = 'discover-sync-last-synced';
lastSyncedEl.textContent = lastSyncedText;
metaInline.appendChild(sourceBadge);
metaInline.appendChild(sep1);
metaInline.appendChild(trackCountEl);
metaInline.appendChild(sep2);
metaInline.appendChild(statusEl);
metaInline.appendChild(sep3);
metaInline.appendChild(lastSyncedEl);
nameEl.appendChild(metaInline);
infoArea.appendChild(nameEl);
const actions = document.createElement('div');
actions.className = 'discover-sync-card-actions';
const toggleWrapper = document.createElement('div');
toggleWrapper.className = 'discover-sync-toggle-wrapper';
toggleWrapper.title = isEmpty
? 'No tracks available \u2014 visit Discover first'
: 'Keep this playlist updated automatically';
const toggleLabel = document.createElement('label');
toggleLabel.className = 'discover-sync-toggle-label';
toggleLabel.textContent = 'Keep updated';
const toggle = document.createElement('label');
toggle.className = 'discover-sync-toggle';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = !!playlist.auto_update;
checkbox.disabled = !!isEmpty;
checkbox.addEventListener('change', function () {
toggleDiscoverAutoUpdate(playlist.type, this.checked);
});
const slider = document.createElement('span');
slider.className = 'discover-sync-toggle-slider';
toggle.appendChild(checkbox);
toggle.appendChild(slider);
toggleWrapper.appendChild(toggleLabel);
toggleWrapper.appendChild(toggle);
const syncButton = document.createElement('button');
syncButton.className = 'discover-sync-btn';
syncButton.id = `discover-sync-btn-${playlist.type}`;
syncButton.disabled = playlist.sync_status === 'syncing' || isEmpty;
syncButton.textContent = '\u27f3 Sync Now';
syncButton.addEventListener('click', () => syncDiscoverPlaylistFromTab(playlist.type, playlist.name));
actions.appendChild(toggleWrapper);
actions.appendChild(syncButton);
card.appendChild(iconArea);
card.appendChild(infoArea);
card.appendChild(actions);
// Make the icon + info area clickable to view tracks // Make the icon + info area clickable to view tracks
if (!isEmpty) { if (!isEmpty) {
const clickArea = card.querySelector('.discover-sync-card-info'); [infoArea, iconArea].forEach(el => {
const iconArea = card.querySelector('.discover-sync-card-icon');
[clickArea, iconArea].forEach(el => {
el.style.cursor = 'pointer'; el.style.cursor = 'pointer';
el.addEventListener('click', () => openDiscoverPlaylistModal(playlist.type, playlist.name, playlist.icon)); el.addEventListener('click', () => openDiscoverPlaylistModal(playlist.type, playlist.name, playlist.icon));
}); });

View file

@ -59492,8 +59492,6 @@ body.reduce-effects *::after {
.discover-sync-card-meta { .discover-sync-card-meta {
display: none; display: none;
} }
color: rgba(255, 255, 255, 0.4);
}
.discover-sync-separator { .discover-sync-separator {
opacity: 0.4; opacity: 0.4;