From c843b4e6230ff140851352b106c8f6188e294439 Mon Sep 17 00:00:00 2001 From: Release Bot Date: Wed, 12 Aug 2026 03:22:27 +0200 Subject: [PATCH] fix: keep large queue cancellation responsive --- README.md | 2 +- lib/upload-manager.js | 1 + package-lock.json | 4 ++-- package.json | 2 +- renderer/app.js | 2 ++ tests/ui-smoke.js | 3 +++ tests/upload-manager.test.js | 33 +++++++++++++++++++++++++++++++++ 7 files changed, 43 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 248f092..0861c63 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Multi-Hoster-Upload is a Windows desktop application for sending file batches to Download the current Setup or Portable build from the [latest GitHub release](https://github.com/Sucukdeluxe/Multi-Hoster-Upload/releases/latest). -The latest public release is version 2.1.12. Use the release page for the executables and the full English changelog. +The latest public release is version 2.1.15. Use the release page for the executables and the full English changelog. ## Features diff --git a/lib/upload-manager.js b/lib/upload-manager.js index da30586..76165b5 100644 --- a/lib/upload-manager.js +++ b/lib/upload-manager.js @@ -471,6 +471,7 @@ class UploadManager extends EventEmitter { }; const emitFinalStatus = (status, payload = {}) => { + if (status === 'aborted' && this.abortController.signal.aborted) return; this._emitProgress(uploadId, fileName, task.hoster, { accountId: task.accountId, jobId, status, diff --git a/package-lock.json b/package-lock.json index 60600c0..d53bb21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "multi-hoster-uploader", - "version": "2.1.14", + "version": "2.1.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "multi-hoster-uploader", - "version": "2.1.14", + "version": "2.1.15", "dependencies": { "chokidar": "^3.6.0", "undici": "^7.29.0", diff --git a/package.json b/package.json index 5ba098a..b7da5bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "multi-hoster-uploader", - "version": "2.1.14", + "version": "2.1.15", "description": "Upload files to doodstream, voe, vidmoly, byse simultaneously", "main": "main.js", "scripts": { diff --git a/renderer/app.js b/renderer/app.js index c6afb00..f35f82b 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -2305,6 +2305,8 @@ function showRecentContextMenu(row, x, y) { applyRecentSelectionClasses(); } const menu = document.getElementById('recentContextMenu'); + const copyItem = menu.querySelector('[data-action="recent-copy-links"]'); + if (copyItem) copyItem.textContent = selectedRecentIds.size > 1 ? `Links kopieren (${selectedRecentIds.size})` : 'Link kopieren'; menu.style.display = 'block'; menu.style.left = Math.min(x, window.innerWidth - menu.offsetWidth - 5) + 'px'; menu.style.top = Math.min(y, window.innerHeight - menu.offsetHeight - 5) + 'px'; diff --git a/tests/ui-smoke.js b/tests/ui-smoke.js index 9c83c9c..22971b7 100644 --- a/tests/ui-smoke.js +++ b/tests/ui-smoke.js @@ -1486,6 +1486,9 @@ setTimeout(async () => { const historyWorkspaceLayout = await wc.executeJavaScript('(() => { const view = document.getElementById("history-view"); const sidebar = view?.querySelector(":scope > .view-sidebar"); const main = view?.querySelector(":scope > .view-main"); if (!sidebar || !main) return false; const sidebarRect = sidebar.getBoundingClientRect(); const mainRect = main.getBoundingClientRect(); return sidebarRect.width > 0 && mainRect.width > 0 && sidebarRect.right <= mainRect.left; })()'); check('History view separates sidebar and main workspace', historyWorkspaceLayout === true); + const singleRecentLinkContextLabel = await wc.executeJavaScript('(() => { selectedRecentIds.clear(); const row = document.createElement("tr"); row.dataset.order = "1001"; showRecentContextMenu(row, 8, 8); const label = document.querySelector("#recentContextMenu [data-action=recent-copy-links]")?.textContent?.trim(); hideContextMenu(); return label; })()'); + check('Recent upload context menu uses singular copy text for one link', singleRecentLinkContextLabel === 'Link kopieren'); + const historySidebarInformation = await wc.executeJavaScript('(() => { const sidebar = document.querySelector("#history-view > .view-sidebar")?.getBoundingClientRect(); const section = document.querySelector("#history-view .view-sidebar-section")?.getBoundingClientRect(); const retention = document.getElementById("historySidebarRetention")?.textContent?.trim(); return Boolean(sidebar && section && section.top >= sidebar.top + sidebar.height * 0.55 && retention === "Alles behalten"); })()'); check('History sidebar shows the active retention in its lower area', historySidebarInformation === true); diff --git a/tests/upload-manager.test.js b/tests/upload-manager.test.js index 9950b18..d6477ed 100644 --- a/tests/upload-manager.test.js +++ b/tests/upload-manager.test.js @@ -225,6 +225,39 @@ describe('UploadManager', () => { assert.ok(batchDone, 'batch-done should be emitted even after cancel'); }); + it('does not emit one aborted progress event per job when cancelling a whole batch', async () => { + mockUploadFile.mock.mockImplementation(async (hoster, filePath, apiKey, onProgress, signal) => { + await new Promise((resolve, reject) => { + const timer = setTimeout(resolve, 10000); + signal.addEventListener('abort', () => { + clearTimeout(timer); + reject(new Error('Aborted')); + }, { once: true }); + }); + return { download_url: `https://${hoster}/d/ok123`, embed_url: null, file_code: 'ok123' }; + }); + + const mgr = new UploadManager({ + 'doodstream.com': { retries: 0, parallelCount: 1, maxSpeedKbs: 0, restartBelowKbs: 0, timeIntervalSec: 0, maxSizeMb: 0 } + }); + const statuses = []; + mgr.on('progress', (data) => statuses.push(data.status)); + + const tasks = Array.from({ length: 60 }, (_, index) => ({ + jobId: `cancel-${index}`, + file: `/test/cancel-${index}.mp4`, + hoster: 'doodstream.com', + apiKey: 'key1' + })); + const batchPromise = mgr.startBatch(tasks); + + await new Promise((resolve) => setTimeout(resolve, 50)); + mgr.cancel(); + await batchPromise; + + assert.equal(statuses.filter((status) => status === 'aborted').length, 0); + }); + it('maxSizeMb filter skips oversized files', async () => { fakeFileSize = 5 * 1024 * 1024; // 5 MB