✨ feat: retry/start selected jobs while upload batch is running
Previously, 'Erneut versuchen' and 'Ausgewählte starten' did nothing when a batch was already running (uploading=true). Failed jobs were set to 'Wartet' but never actually uploaded because they couldn't be added to the running batch. New: upload-manager.addJobs() allows adding tasks to a running batch. When a batch is active and user retries/starts jobs, they're injected into the running batch via IPC 'add-jobs-to-batch'. The upload manager starts processing them immediately using the existing semaphores. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a1a3e87de8
commit
e1b03605fa
@ -135,6 +135,7 @@ class UploadManager extends EventEmitter {
|
|||||||
const { signal } = this.abortController;
|
const { signal } = this.abortController;
|
||||||
const batchId = `batch-${Date.now()}`;
|
const batchId = `batch-${Date.now()}`;
|
||||||
const results = new Map(); // filePath -> { name, size, results: [] }
|
const results = new Map(); // filePath -> { name, size, results: [] }
|
||||||
|
this._batchResults = results;
|
||||||
|
|
||||||
for (const task of tasks) {
|
for (const task of tasks) {
|
||||||
const fileName = path.basename(task.file);
|
const fileName = path.basename(task.file);
|
||||||
@ -690,6 +691,24 @@ class UploadManager extends EventEmitter {
|
|||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addJobs(tasks) {
|
||||||
|
if (!this.running || !tasks || tasks.length === 0) return;
|
||||||
|
const { signal } = this.abortController;
|
||||||
|
const results = this._batchResults || new Map();
|
||||||
|
for (const task of tasks) {
|
||||||
|
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 — they'll acquire semaphores and run
|
||||||
|
for (const task of tasks) {
|
||||||
|
this._runJob(task, results, signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cancelJobs(jobIds) {
|
cancelJobs(jobIds) {
|
||||||
for (const jobId of jobIds || []) {
|
for (const jobId of jobIds || []) {
|
||||||
if (!jobId) continue;
|
if (!jobId) continue;
|
||||||
|
|||||||
13
main.js
13
main.js
@ -778,6 +778,19 @@ ipcMain.handle('cancel-selected-jobs', (_event, jobIds) => {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('add-jobs-to-batch', (_event, payload) => {
|
||||||
|
if (!uploadManager || !uploadManager.running) {
|
||||||
|
return { error: 'Kein Upload aktiv' };
|
||||||
|
}
|
||||||
|
const config = configStore.load();
|
||||||
|
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 };
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('finish-after-active', () => {
|
ipcMain.handle('finish-after-active', () => {
|
||||||
if (uploadManager) {
|
if (uploadManager) {
|
||||||
uploadManager.finishAfterActive();
|
uploadManager.finishAfterActive();
|
||||||
|
|||||||
@ -34,6 +34,7 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
startUpload: (payload) => ipcRenderer.invoke('start-upload', payload),
|
startUpload: (payload) => ipcRenderer.invoke('start-upload', payload),
|
||||||
cancelUpload: () => ipcRenderer.invoke('cancel-upload'),
|
cancelUpload: () => ipcRenderer.invoke('cancel-upload'),
|
||||||
cancelSelectedJobs: (jobIds) => ipcRenderer.invoke('cancel-selected-jobs', jobIds),
|
cancelSelectedJobs: (jobIds) => ipcRenderer.invoke('cancel-selected-jobs', jobIds),
|
||||||
|
addJobsToBatch: (payload) => ipcRenderer.invoke('add-jobs-to-batch', payload),
|
||||||
finishAfterActive: () => ipcRenderer.invoke('finish-after-active'),
|
finishAfterActive: () => ipcRenderer.invoke('finish-after-active'),
|
||||||
runHealthCheck: (payload) => ipcRenderer.invoke('run-health-check', payload),
|
runHealthCheck: (payload) => ipcRenderer.invoke('run-health-check', payload),
|
||||||
|
|
||||||
|
|||||||
@ -1352,7 +1352,23 @@ function _markSkippedJobs(result) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function startSelectedUpload() {
|
async function startSelectedUpload() {
|
||||||
if (uploading) return;
|
if (uploading) {
|
||||||
|
// Batch already running — add selected jobs to the running batch
|
||||||
|
const retryable = queueJobs.filter(j => selectedJobIds.has(j.id) && ['queued', 'error', 'aborted', 'skipped'].includes(j.status));
|
||||||
|
if (retryable.length > 0) {
|
||||||
|
retryable.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 }))
|
||||||
|
});
|
||||||
|
_markSkippedJobs(result);
|
||||||
|
persistQueueStateSoon();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
uploading = true; // set immediately to prevent double-click race
|
uploading = true; // set immediately to prevent double-click race
|
||||||
updateQueueActionButtons();
|
updateQueueActionButtons();
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user