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:
@@ -349,7 +349,9 @@ const fileOps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter out unreadable entries (typically dropped folders/placeholders)
|
// Filter out unreadable entries (typically dropped folders/placeholders)
|
||||||
|
/** @type {File[]} */
|
||||||
const readableFiles = [];
|
const readableFiles = [];
|
||||||
|
/** @type {string[]} */
|
||||||
const skippedEntries = [];
|
const skippedEntries = [];
|
||||||
for (const f of originalFiles) {
|
for (const f of originalFiles) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
@@ -386,13 +388,21 @@ const fileOps = {
|
|||||||
|
|
||||||
let uploadedCount = 0;
|
let uploadedCount = 0;
|
||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
|
let quotaStop = false;
|
||||||
|
|
||||||
for (let i = 0; i < totalFiles; i++) {
|
const targetFolderId = app.currentPath || app.userHomeFolderId;
|
||||||
const file = readableFiles[i];
|
|
||||||
|
/**
|
||||||
|
* 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 formData = new FormData();
|
||||||
|
|
||||||
const targetFolderId = app.currentPath || app.userHomeFolderId;
|
|
||||||
if (targetFolderId) formData.append('folder_id', targetFolderId);
|
if (targetFolderId) formData.append('folder_id', targetFolderId);
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
@@ -436,6 +446,8 @@ const fileOps = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (result.isQuotaError) {
|
if (result.isQuotaError) {
|
||||||
|
// Stop pulling new files; in-flight uploads still finish.
|
||||||
|
quotaStop = true;
|
||||||
const msg = result.errorMsg || i18n.t('storage_quota_exceeded');
|
const msg = result.errorMsg || i18n.t('storage_quota_exceeded');
|
||||||
if (notifications) {
|
if (notifications) {
|
||||||
notifications.addNotification({
|
notifications.addNotification({
|
||||||
@@ -445,10 +457,27 @@ const fileOps = {
|
|||||||
text: msg
|
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
|
// All done
|
||||||
this._finishUploadToast(successCount, totalFiles);
|
this._finishUploadToast(successCount, totalFiles);
|
||||||
|
|||||||
Reference in New Issue
Block a user