From d3fda31243ca71787e550a4092959d3dc26caf05 Mon Sep 17 00:00:00 2001 From: Administrator Date: Mon, 8 Jun 2026 23:03:29 +0200 Subject: [PATCH] =?UTF-8?q?fix(ui):=20ETA=20includes=20waiting=20jobs=20?= =?UTF-8?q?=E2=80=94=20folder-added=20files=20now=20ship=20with=20bytesTot?= =?UTF-8?q?al?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.js | 29 ++++++++++++++++++++++++++++- preload.js | 2 ++ renderer/app.js | 47 ++++++++++++++++++++++++++++++++++++----------- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/main.js b/main.js index f7ecb38..afee109 100644 --- a/main.js +++ b/main.js @@ -1379,6 +1379,17 @@ ipcMain.handle('select-folder', async () => { }); if (result.canceled || !result.filePaths.length) return null; + const files = []; + for (const folder of result.filePaths) await walkFolderAsync(folder, files); + return files.length > 0 ? files.map(f => f.path) : null; +}); + +ipcMain.handle('select-folder-with-sizes', async () => { + const result = await dialog.showOpenDialog(mainWindow, { + properties: ['openDirectory', 'multiSelections'] + }); + if (result.canceled || !result.filePaths.length) return null; + const files = []; for (const folder of result.filePaths) await walkFolderAsync(folder, files); return files.length > 0 ? files : null; @@ -1396,7 +1407,11 @@ async function walkFolderAsync(rootDir, outFiles) { for (const entry of entries) { const full = path.join(dir, entry.name); if (entry.isDirectory()) stack.push(full); - else if (entry.isFile()) outFiles.push(full); + else if (entry.isFile()) { + let size = 0; + try { size = (await fsp.stat(full)).size; } catch {} + outFiles.push({ path: full, name: entry.name, size }); + } } if ((++scanned % 8) === 0) await new Promise(setImmediate); } @@ -1408,6 +1423,18 @@ ipcMain.handle('resolve-folder-files', async (_event, folderPath) => { return files; }); +ipcMain.handle('get-file-sizes', async (_event, paths) => { + if (!Array.isArray(paths)) return {}; + const fsp = fs.promises; + const out = {}; + let i = 0; + for (const p of paths) { + try { out[p] = (await fsp.stat(p)).size; } catch { out[p] = 0; } + if ((++i % 32) === 0) await new Promise(setImmediate); + } + return out; +}); + ipcMain.handle('start-upload', (_event, payload) => { const config = configStore.load(); const files = payload && Array.isArray(payload.files) ? payload.files : []; diff --git a/preload.js b/preload.js index abfd86d..db53210 100644 --- a/preload.js +++ b/preload.js @@ -30,7 +30,9 @@ contextBridge.exposeInMainWorld('api', { // File selection selectFiles: () => ipcRenderer.invoke('select-files'), selectFolder: () => ipcRenderer.invoke('select-folder'), + selectFolderWithSizes: () => ipcRenderer.invoke('select-folder-with-sizes'), resolveFolderFiles: (folderPath) => ipcRenderer.invoke('resolve-folder-files', folderPath), + getFileSizes: (paths) => ipcRenderer.invoke('get-file-sizes', paths), // Upload control startUpload: (payload) => ipcRenderer.invoke('start-upload', payload), diff --git a/renderer/app.js b/renderer/app.js index cae23af..562c345 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -695,11 +695,12 @@ async function addDroppedFiles(fileList) { const folderFiles = await window.api.resolveFolderFiles(filePath); if (folderFiles && folderFiles.length > 0) { for (const fp of folderFiles) { - if (!existingPaths.has(fp)) { - const name = fp.split('\\').pop().split('/').pop(); - newFiles.push({ path: fp, name, size: null }); - existingPaths.add(fp); - } + const p = typeof fp === 'string' ? fp : (fp && fp.path); + if (!p || existingPaths.has(p)) continue; + const name = typeof fp === 'string' ? p.split('\\').pop().split('/').pop() : (fp.name || p.split('\\').pop().split('/').pop()); + const size = typeof fp === 'string' ? null : (fp.size || 0); + newFiles.push({ path: p, name, size }); + existingPaths.add(p); } continue; } @@ -730,28 +731,52 @@ async function pickFiles() { } async function pickFolder() { + const richFiles = window.api.selectFolderWithSizes ? await window.api.selectFolderWithSizes() : null; + if (richFiles && Array.isArray(richFiles)) { addPathsToQueue(richFiles); return; } const paths = await window.api.selectFolder(); if (!paths) return; addPathsToQueue(paths); } function addPathsToQueue(paths) { - // Build path-Set once so dedup is O(1) per candidate instead of O(n+m). - // Matters when the user picks a folder with thousands of files. const existing = new Set(); for (const f of selectedFiles) existing.add(f.path); for (const f of _pendingFiles) existing.add(f.path); const newFiles = []; - for (const p of paths) { - if (existing.has(p)) continue; + const pendingSizeFetch = []; + for (const entry of paths) { + const p = typeof entry === 'string' ? entry : (entry && entry.path); + if (!p || existing.has(p)) continue; existing.add(p); - const name = p.split('\\').pop().split('/').pop(); - newFiles.push({ path: p, name, size: null }); + const name = typeof entry === 'string' ? p.split('\\').pop().split('/').pop() : (entry.name || p.split('\\').pop().split('/').pop()); + const size = typeof entry === 'string' ? null : (entry.size || 0); + newFiles.push({ path: p, name, size }); + if (size === null || size === undefined || size === 0) pendingSizeFetch.push(p); } if (newFiles.length > 0) { _pendingFiles.push(...newFiles); openHosterModal(); + if (pendingSizeFetch.length > 0 && window.api.getFileSizes) { + window.api.getFileSizes(pendingSizeFetch).then((sizeMap) => { + if (!sizeMap || typeof sizeMap !== 'object') return; + let changed = false; + for (const f of _pendingFiles) { + if (sizeMap[f.path] && (!f.size || f.size === 0)) { f.size = sizeMap[f.path]; changed = true; } + } + for (const f of selectedFiles) { + if (sizeMap[f.path] && (!f.size || f.size === 0)) { f.size = sizeMap[f.path]; changed = true; } + } + for (const j of queueJobs) { + if (sizeMap[j.file] && (!j.bytesTotal || j.bytesTotal === 0)) { j.bytesTotal = sizeMap[j.file]; changed = true; } + } + if (changed) { + _queueStatsCache = null; + if (typeof renderQueueTable === 'function') renderQueueTable(); + if (typeof updateStatusBar === 'function') updateStatusBar(); + } + }).catch(() => {}); + } } }