From 176cadc2dd52f75c7fc434bfb97d897dc5a57814 Mon Sep 17 00:00:00 2001 From: Administrator Date: Sat, 21 Mar 2026 08:46:19 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(queue):=20deleted=20jobs=20r?= =?UTF-8?q?eappear=20after=20restart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three root causes fixed: - handleProgress() re-created deleted jobs from stale progress callbacks - Queue save was debounced (10s during uploads), deletion lost on app close - Delete was blocked during active uploads (removed !uploading guard) Now: deletions save immediately, deleted IDs are tracked to prevent re-creation, and active uploads are cancelled when their jobs are deleted. Co-Authored-By: Claude Opus 4.6 (1M context) --- renderer/app.js | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/renderer/app.js b/renderer/app.js index 8534435..d08f847 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -35,6 +35,7 @@ let _sessionUploadedBytes = 0; // Bytes fully uploaded this session (done jobs) let _sessionTrackedJobs = new Set(); // Job IDs already counted for totalBytes let _sessionDoneJobs = new Set(); // Job IDs already counted for uploadedBytes let _completedUploadKeys = new Set(); // 'filepath|hoster' keys for done uploads (survives removeFromQueueOnDone) +let _deletedJobIds = new Set(); // IDs of jobs explicitly deleted by user (prevents re-creation from stale progress callbacks) let queueSortState = { key: 'filename', direction: 'asc' }; // History state @@ -696,6 +697,9 @@ function indexJob(job) { function removeJobFromIndex(job) { _jobIndexById.delete(job.id); if (job.uploadId) _jobIndexByUploadId.delete(job.uploadId); + // Track deletion so handleProgress() won't re-create this job from stale callbacks + _deletedJobIds.add(job.id); + if (job.uploadId) _deletedJobIds.add(job.uploadId); } // --- Queue Table Rendering (debounced with virtual scrolling) --- @@ -1150,7 +1154,14 @@ document.addEventListener('keydown', (e) => { e.preventDefault(); if (selectedRecentIds.size > 0) { deleteSelectedRecentFiles(); - } else if (selectedJobIds.size > 0 && !uploading) { + } else if (selectedJobIds.size > 0) { + const deletedIds = [...selectedJobIds]; + // Cancel active uploads for deleted jobs + const activeIds = deletedIds.filter(id => { + const j = _jobIndexById.get(id); + return j && (j.status === 'uploading' || j.status === 'queued' || j.status === 'retrying' || j.status === 'getting-server'); + }); + if (activeIds.length > 0) window.api.cancelSelectedJobs(activeIds); queueJobs = queueJobs.filter(j => { if (selectedJobIds.has(j.id)) { removeJobFromIndex(j); return false; } return true; @@ -1160,7 +1171,7 @@ document.addEventListener('keydown', (e) => { renderQueueTable(); if (queueJobs.length === 0) { selectedFiles = []; updateUploadView(); } updateStatusBar(); - persistQueueStateSoon(); + persistQueueStateSoon(true); } } } @@ -1184,6 +1195,12 @@ async function handleContextAction(action) { } else if (action === 'retry-selected') { retrySelectedJobs(); } else if (action === 'delete-selected') { + // Cancel active uploads for deleted jobs + const activeIds = [...selectedJobIds].filter(id => { + const j = _jobIndexById.get(id); + return j && (j.status === 'uploading' || j.status === 'queued' || j.status === 'retrying' || j.status === 'getting-server'); + }); + if (activeIds.length > 0) window.api.cancelSelectedJobs(activeIds); queueJobs = queueJobs.filter(j => { if (selectedJobIds.has(j.id)) { removeJobFromIndex(j); @@ -1196,10 +1213,15 @@ async function handleContextAction(action) { renderQueueTable(); if (queueJobs.length === 0) { selectedFiles = []; updateUploadView(); } updateStatusBar(); - persistQueueStateSoon(); + persistQueueStateSoon(true); } else if (action === 'copy-all-links') { copyAllLinks(); } else if (action === 'delete-all') { + // Cancel all active uploads + const activeIds = queueJobs + .filter(j => j.status === 'uploading' || j.status === 'queued' || j.status === 'retrying' || j.status === 'getting-server') + .map(j => j.id); + if (activeIds.length > 0) window.api.cancelSelectedJobs(activeIds); queueJobs.forEach(j => removeJobFromIndex(j)); queueJobs = []; selectedJobIds.clear(); @@ -1208,7 +1230,7 @@ async function handleContextAction(action) { renderQueueTable(); updateUploadView(); updateStatusBar(); - persistQueueStateSoon(); + persistQueueStateSoon(true); } else if (action === 'always-on-top') { alwaysOnTopState = !alwaysOnTopState; await window.api.setAlwaysOnTop(alwaysOnTopState); @@ -1406,6 +1428,10 @@ function handleProgress(data) { } } if (!job) { + // Don't re-create jobs that were explicitly deleted by the user + if ((data.jobId && _deletedJobIds.has(data.jobId)) || (data.uploadId && _deletedJobIds.has(data.uploadId))) { + return; + } job = { id: data.jobId || data.uploadId || `job-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, uploadId: data.uploadId,