mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-23 22:47:19 +00:00
Isolate upload dialog to prevent double uploads
Replace reuse of `#upload-file-dialog` with a dedicated hidden file input owned by `init_upload_using_dialog`. This avoids collisions with the dashboard Files tab’s `onchange` property handler, which could trigger duplicate concurrent uploads and "Entry already exists" races. The new flow stores the current target path once per dialog open, clears input value before click (so re-selecting the same file still fires `change`), and snapshots `FileList` to an array before clearing to keep async uploads reliable.
This commit is contained in:
+55
-22
@@ -2168,6 +2168,53 @@ window.updateSubdomainsForItems = async function (fsentries, container) {
|
||||
}
|
||||
};
|
||||
|
||||
// This flow owns its own file input rather than borrowing the shell's
|
||||
// #upload-file-dialog. That element is shared, and on the dashboard the Files
|
||||
// tab assigns an onchange PROPERTY to it — which jQuery's unbind() cannot
|
||||
// remove — so a single selection used to fire both handlers and start two
|
||||
// concurrent uploads of the same files. When the two destinations coincided
|
||||
// (both default to Desktop) the signed batch writes raced and the loser failed
|
||||
// with "Entry already exists"; when they differed, the files also landed in
|
||||
// the Files tab's directory. A private input keeps the two flows independent.
|
||||
let el_upload_dialog_input = null;
|
||||
|
||||
// Destination for the pending selection. Module-level rather than captured per
|
||||
// call because the input's change handler is bound once, and only one native
|
||||
// file dialog can be open at a time — matching the old behaviour, where
|
||||
// re-binding meant the most recent caller's target won.
|
||||
let upload_dialog_target_path = null;
|
||||
|
||||
const get_upload_dialog_input = () => {
|
||||
if ( el_upload_dialog_input?.isConnected ) {
|
||||
return el_upload_dialog_input;
|
||||
}
|
||||
|
||||
el_upload_dialog_input = document.createElement('input');
|
||||
el_upload_dialog_input.type = 'file';
|
||||
el_upload_dialog_input.name = 'file';
|
||||
el_upload_dialog_input.multiple = true;
|
||||
el_upload_dialog_input.style.display = 'none';
|
||||
document.body.appendChild(el_upload_dialog_input);
|
||||
|
||||
el_upload_dialog_input.addEventListener('change', function () {
|
||||
// Snapshot into an array before clearing: `value = ''` empties the live
|
||||
// FileList in place, and upload_items consumes it asynchronously.
|
||||
const files = Array.from(el_upload_dialog_input.files ?? []);
|
||||
el_upload_dialog_input.value = '';
|
||||
if ( files.length === 0 ) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.upload_items(files, upload_dialog_target_path);
|
||||
}
|
||||
catch ( err ) {
|
||||
UIAlert(err.message ?? err);
|
||||
}
|
||||
});
|
||||
|
||||
return el_upload_dialog_input;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} el_target_container
|
||||
@@ -2175,29 +2222,15 @@ window.updateSubdomainsForItems = async function (fsentries, container) {
|
||||
*/
|
||||
|
||||
window.init_upload_using_dialog = function (el_target_container, target_path = null) {
|
||||
$('#upload-file-dialog').unbind('onchange');
|
||||
$('#upload-file-dialog').unbind('change');
|
||||
$('#upload-file-dialog').unbind('onChange');
|
||||
upload_dialog_target_path = target_path === null
|
||||
? $(el_target_container).attr('data-path')
|
||||
: path.resolve(target_path);
|
||||
|
||||
target_path = target_path === null ? $(el_target_container).attr('data-path') : path.resolve(target_path);
|
||||
$('#upload-file-dialog').trigger('click');
|
||||
$('#upload-file-dialog').on('change', async function (e) {
|
||||
if ( $('#upload-file-dialog').val() !== '' ) {
|
||||
const files = $('#upload-file-dialog')[0].files;
|
||||
if ( files.length > 0 ) {
|
||||
try {
|
||||
window.upload_items(files, target_path);
|
||||
}
|
||||
catch ( err ) {
|
||||
UIAlert(err.message ?? err);
|
||||
}
|
||||
$('#upload-file-dialog').val('');
|
||||
}
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
});
|
||||
const el_input = get_upload_dialog_input();
|
||||
// Clearing before opening lets the same file be picked twice in a row; an
|
||||
// unchanged value fires no change event.
|
||||
el_input.value = '';
|
||||
el_input.click();
|
||||
};
|
||||
|
||||
window.upload_items = async function (items, dest_path) {
|
||||
|
||||
Reference in New Issue
Block a user