diff --git a/lib/upload-manager.js b/lib/upload-manager.js index 8cfeaf4..0068dc7 100644 --- a/lib/upload-manager.js +++ b/lib/upload-manager.js @@ -698,21 +698,23 @@ class UploadManager extends EventEmitter { } addJobs(tasks) { - if (!this.running || !tasks || tasks.length === 0) return; + if (!this.running || !tasks || tasks.length === 0) return 0; const { signal } = this.abortController; const results = this._batchResults || new Map(); + let added = 0; for (const task of tasks) { + // Skip if this job is already being processed (prevent duplicates) + if (task.jobId && this.jobAbortControllers.has(task.jobId)) continue; const fileName = path.basename(task.file); if (!results.has(task.file)) { let size = 0; try { size = fs.statSync(task.file).size; } catch {} results.set(task.file, { name: fileName, size, results: [] }); } - } - // Start each new job and track promises so batch-done waits for them - for (const task of tasks) { this._additionalPromises.push(this._runJob(task, results, signal)); + added++; } + return added; } cancelJobs(jobIds) { diff --git a/main.js b/main.js index 0f61f26..f758bc9 100644 --- a/main.js +++ b/main.js @@ -786,9 +786,9 @@ ipcMain.handle('add-jobs-to-batch', (_event, payload) => { const jobs = payload && Array.isArray(payload.jobs) ? payload.jobs : []; const tasks = buildUploadTasksFromJobs(config, jobs); if (tasks.length === 0) return { added: 0 }; - uploadManager.addJobs(tasks); - debugLog(`add-jobs-to-batch: added ${tasks.length} tasks to running batch`); - return { added: tasks.length }; + const added = uploadManager.addJobs(tasks); + debugLog(`add-jobs-to-batch: ${added} of ${tasks.length} tasks added (${tasks.length - added} already in batch)`); + return { added }; }); ipcMain.handle('finish-after-active', () => { diff --git a/renderer/app.js b/renderer/app.js index 108ab49..ca91688 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -1354,25 +1354,28 @@ function _markSkippedJobs(result) { async function startSelectedUpload() { if (uploading) { - // Batch already running — only add error/aborted/skipped jobs (not already-queued ones) - const retryable = queueJobs.filter(j => selectedJobIds.has(j.id) && ['error', 'aborted', 'skipped'].includes(j.status)); - if (retryable.length > 0) { - retryable.forEach(j => { + // Batch already running — add selected jobs (queued/error/aborted/skipped) to running batch + // Upload-manager has duplicate protection (skips jobs already tracked) + const addable = queueJobs.filter(j => selectedJobIds.has(j.id) && ['queued', 'error', 'aborted', 'skipped'].includes(j.status)); + if (addable.length > 0) { + addable.forEach(j => { j.status = 'queued'; j.error = null; j.result = null; j.bytesUploaded = 0; j.speedKbs = 0; j.progress = 0; j.uploadId = null; }); renderQueueTable(); const result = await window.api.addJobsToBatch({ - jobs: retryable.map(j => ({ id: j.id, file: j.file, fileName: j.fileName, hoster: j.hoster })) + jobs: addable.map(j => ({ id: j.id, file: j.file, fileName: j.fileName, hoster: j.hoster })) }); _markSkippedJobs(result); persistQueueStateSoon(); - showCopyToast(`${retryable.length} Jobs zum laufenden Upload hinzugefügt`); - } else { - // All selected jobs are already queued/uploading — just inform user - const waiting = queueJobs.filter(j => selectedJobIds.has(j.id) && j.status === 'queued'); - if (waiting.length > 0) { - showCopyToast(`${waiting.length} Jobs warten bereits auf ihren Upload-Slot`); + const added = result && result.added || 0; + const alreadyInBatch = addable.length - added; + if (added > 0 && alreadyInBatch > 0) { + showCopyToast(`${added} Jobs hinzugefügt, ${alreadyInBatch} waren schon im Batch`); + } else if (added > 0) { + showCopyToast(`${added} Jobs zum laufenden Upload hinzugefügt`); + } else { + showCopyToast(`${addable.length} Jobs sind bereits im laufenden Batch`); } } return;