perf(upload): run multi-file selections through the 10-worker pool

`uploadFiles` sent files one at a time (await per file), so dropping N
files meant N sequential round-trips. Reuse the bounded-concurrency pool
already proven in `uploadFolderEntries` (CONCURRENCY = 10): independent
files now upload up to 10 at a time — ~10x faster for many small files.

Per-file progress (XHR → bell), the legacy progress bar, timeout
notifications and the quota short-circuit are preserved. On a quota error
the pool stops pulling new files while in-flight uploads finish (matching
`uploadFolderEntries`) instead of the old hard `break`.

https://claude.ai/code/session_01Dp3oWon5GBMVn4j3QXZdgx
This commit is contained in:
Claude
2026-06-10 08:44:39 +00:00
parent 214d42d0f6
commit 3c64354a0d
+34 -5
View File
@@ -349,7 +349,9 @@ const fileOps = {
}
// Filter out unreadable entries (typically dropped folders/placeholders)
/** @type {File[]} */
const readableFiles = [];
/** @type {string[]} */
const skippedEntries = [];
for (const f of originalFiles) {
// eslint-disable-next-line no-await-in-loop
@@ -386,13 +388,21 @@ const fileOps = {
let uploadedCount = 0;
let successCount = 0;
let quotaStop = false;
for (let i = 0; i < totalFiles; i++) {
const file = readableFiles[i];
const targetFolderId = app.currentPath || app.userHomeFolderId;
/**
* Upload a single readable file by index. Shared counters are
* mutated here; safe because JS runs the workers cooperatively
* (no true parallelism between awaits).
* @param {number} idx
*/
const uploadOneFile = async (idx) => {
if (quotaStop) return;
const file = readableFiles[idx];
const formData = new FormData();
const targetFolderId = app.currentPath || app.userHomeFolderId;
if (targetFolderId) formData.append('folder_id', targetFolderId);
formData.append('file', file);
@@ -436,6 +446,8 @@ const fileOps = {
});
}
if (result.isQuotaError) {
// Stop pulling new files; in-flight uploads still finish.
quotaStop = true;
const msg = result.errorMsg || i18n.t('storage_quota_exceeded');
if (notifications) {
notifications.addNotification({
@@ -445,10 +457,27 @@ const fileOps = {
text: msg
});
}
break;
}
}
};
// Pool-based concurrency: keep up to CONCURRENCY uploads in flight
// instead of one at a time (mirrors uploadFolderEntries). Files are
// independent, so this is ~CONCURRENCY× faster for many small files.
const CONCURRENCY = 10;
let nextIdx = 0;
const runNext = async () => {
while (nextIdx < totalFiles && !quotaStop) {
const idx = nextIdx++;
await uploadOneFile(idx);
}
};
const workers = [];
for (let w = 0; w < Math.min(CONCURRENCY, totalFiles); w++) {
workers.push(runNext());
}
await Promise.all(workers);
// All done
this._finishUploadToast(successCount, totalFiles);