cancel button

This commit is contained in:
Broque Thomas 2025-08-28 13:04:58 -07:00
parent fd9fcf6c90
commit 58e160aba5
2 changed files with 64 additions and 67 deletions

View file

@ -3506,6 +3506,9 @@ def _download_track_worker(task_id, batch_id=None):
# Cancellation Checkpoint 1: Before doing anything
with tasks_lock:
if task_id not in download_tasks:
print(f"❌ [Modal Worker] Task {task_id} was deleted before starting")
return
if download_tasks[task_id]['status'] == 'cancelled':
print(f"❌ [Modal Worker] Task {task_id} cancelled before starting")
# Free worker slot when cancelled
@ -3586,6 +3589,9 @@ def _download_track_worker(task_id, batch_id=None):
for query_index, query in enumerate(search_queries):
# Cancellation check before each query
with tasks_lock:
if task_id not in download_tasks:
print(f"❌ [Modal Worker] Task {task_id} was deleted during query {query_index + 1}")
return
if download_tasks[task_id]['status'] == 'cancelled':
print(f"❌ [Modal Worker] Task {task_id} cancelled during query {query_index + 1}")
# Free worker slot when cancelled
@ -3662,6 +3668,9 @@ def _attempt_download_with_candidates(task_id, candidates, track, batch_id=None)
for candidate_index, candidate in enumerate(candidates):
# Check cancellation before each attempt
with tasks_lock:
if task_id not in download_tasks:
print(f"❌ [Modal Worker] Task {task_id} was deleted during candidate {candidate_index + 1}")
return False
if download_tasks[task_id]['status'] == 'cancelled':
print(f"❌ [Modal Worker] Task {task_id} cancelled during candidate {candidate_index + 1}")
# Free worker slot when cancelled

View file

@ -2048,6 +2048,9 @@ function startModalDownloadPolling(playlistId) {
default: statusText = `${task.status}`; break;
}
if(statusEl) statusEl.textContent = statusText;
if (actionsEl && !['completed', 'failed', 'cancelled'].includes(task.status) && actionsEl.innerHTML === '-') {
actionsEl.innerHTML = `<button class="cancel-track-btn" title="Cancel this download" onclick="cancelTrackDownload('${playlistId}', ${task.track_index})">×</button>`;
}
if (actionsEl && ['completed', 'failed', 'cancelled'].includes(task.status)) {
actionsEl.innerHTML = '-';
}
@ -2060,12 +2063,23 @@ function startModalDownloadPolling(playlistId) {
document.getElementById(`stat-downloaded-${playlistId}`).textContent = completedCount;
if (data.phase === 'complete' || data.phase === 'error' || (missingCount > 0 && totalFinished >= missingCount)) {
process.status = 'complete';
showToast(`Process complete for ${process.playlist.name}!`, 'success');
// --- REPLACE THE INSIDE OF THIS IF BLOCK with the following ---
if (data.phase === 'cancelled') {
process.status = 'cancelled';
showToast(`Process cancelled for ${process.playlist.name}.`, 'info');
} else if (data.phase === 'error') {
process.status = 'complete'; // Treat as complete to allow cleanup
showToast(`Process for ${process.playlist.name} failed!`, 'error');
} else {
process.status = 'complete';
showToast(`Process complete for ${process.playlist.name}!`, 'success');
}
document.getElementById(`cancel-all-btn-${playlistId}`).style.display = 'none';
clearInterval(process.poller);
process.poller = null;
updatePlaylistCardUI(playlistId);
// --- END OF REPLACEMENT BLOCK ---
}
}
} catch (error) {
@ -2133,72 +2147,46 @@ async function cancelAllOperations(playlistId) {
const process = activeDownloadProcesses[playlistId];
if (!process) return;
console.log(`🚫 Cancelling all operations for playlist ${playlistId}`);
try {
// First, try to cancel the entire batch if we have a batch ID
if (process.batchId) {
try {
const batchResponse = await fetch(`/api/playlists/${process.batchId}/cancel_batch`, {
method: 'POST'
});
const batchData = await batchResponse.json();
if (batchData.success) {
console.log(`✅ Cancelled batch ${process.batchId} with ${batchData.cancelled_tasks} tasks`);
}
} catch (error) {
console.warn('Failed to cancel batch, falling back to individual cancellation:', error);
}
}
// Also cancel individual download tasks for immediate UI feedback
const modal = document.getElementById(`download-missing-modal-${playlistId}`);
if (modal) {
const taskRows = modal.querySelectorAll('tr[data-task-id]');
const cancellationPromises = [];
taskRows.forEach(row => {
const taskId = row.dataset.taskId;
const trackIndex = row.dataset.trackIndex;
if (taskId && row.dataset.locallyCancelled !== 'true') {
// Mark as locally cancelled for immediate UI feedback
row.dataset.locallyCancelled = 'true';
const statusElement = document.getElementById(`download-${playlistId}-${trackIndex}`);
const actionsElement = document.getElementById(`actions-${playlistId}-${trackIndex}`);
if (statusElement) statusElement.textContent = '🚫 Cancelled';
if (actionsElement) actionsElement.innerHTML = '-';
// Add to cancellation promises
cancellationPromises.push(
fetch('/api/downloads/cancel_task', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task_id: taskId })
}).catch(error => {
console.warn(`Failed to cancel task ${taskId}:`, error);
})
);
}
});
// Wait for all cancellations to complete
if (cancellationPromises.length > 0) {
await Promise.allSettled(cancellationPromises);
console.log(`✅ Cancelled ${cancellationPromises.length} individual download tasks`);
}
}
process.status = 'cancelled';
cleanupDownloadProcess(playlistId);
showToast('All operations cancelled', 'info');
} catch (error) {
console.error('Error during cancellation:', error);
process.status = 'cancelled';
cleanupDownloadProcess(playlistId);
showToast('Operations cancelled (with errors)', 'warning');
// Prevent multiple cancel all operations
if (process.cancellingAll) {
console.log(`⚠️ Cancel All already in progress for ${playlistId}`);
return;
}
process.cancellingAll = true;
console.log(`🚫 Cancel All clicked for playlist ${playlistId} - closing modal and cleaning up server`);
showToast('Cancelling all operations and closing modal...', 'info');
// Mark process as complete immediately so polling stops
process.status = 'complete';
// Stop any active polling
if (process.poller) {
clearInterval(process.poller);
process.poller = null;
}
// Tell server to stop starting new downloads and clean up the batch
if (process.batchId) {
try {
// Cancel the batch (stops new downloads from starting)
const cancelResponse = await fetch(`/api/playlists/${process.batchId}/cancel_batch`, {
method: 'POST'
});
if (cancelResponse.ok) {
const cancelData = await cancelResponse.json();
console.log(`✅ Server stopped new downloads for batch ${process.batchId}`);
}
} catch (error) {
console.warn('Error during server batch cancel:', error);
}
}
// Close the modal immediately - this will handle cleanup
closeDownloadMissingModal(playlistId);
showToast('Modal closed. Active downloads will finish in background.', 'success');
}
function resetToInitialState() {