From b8ef509059d16b5701c3f3d31003d07bc8e51f34 Mon Sep 17 00:00:00 2001 From: Sagar Date: Thu, 10 Sep 2026 10:53:32 +0200 Subject: [PATCH 1/4] Fixed handling of failed storage quota validation for direct uploads #12645 Delete files uploaded directly to S3 when post-upload validation fails and return the corresponding validation error to the user. --- .../harvard/iq/dataverse/EditDatafilesPage.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/edu/harvard/iq/dataverse/EditDatafilesPage.java b/src/main/java/edu/harvard/iq/dataverse/EditDatafilesPage.java index 56831834c31..1f7888327cd 100644 --- a/src/main/java/edu/harvard/iq/dataverse/EditDatafilesPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/EditDatafilesPage.java @@ -2190,6 +2190,22 @@ public void handleExternalUpload() { Optional.ofNullable(editDataFilesPageHelper.getHtmlErrorMessage(createDataFilesResult)).ifPresent(errorMessage -> errorMessages.add(errorMessage)); } catch (CommandException ex) { logger.log(Level.SEVERE, "Error during ingest of file {0}", new Object[]{fileName}); + + // Direct upload has already stored the file in S3. + // Remove it if Dataverse rejects the upload. + try { + sio.delete(); + } catch (IOException deleteEx) { + logger.log( + Level.WARNING, + "Failed to delete rejected direct upload {0}: {1}", + new Object[]{fileName, deleteEx.getMessage()} + ); + } + + // Show the actual command error to the user. + errorMessages.add(ex.getMessage()); + return; } if (datafiles == null) { From 1a088f21914ba2f8aec69bda9e10307d1dc3c1b8 Mon Sep 17 00:00:00 2001 From: Sagar Date: Mon, 14 Sep 2026 13:18:16 +0200 Subject: [PATCH 2/4] Fixed direct upload quota validation and refresh #12645 Validate files against upload-size and remaining-quota limits before direct upload. Refresh the available quota after uploads and unsaved-file deletions, and reinitialize direct upload after AJAX updates. --- .../iq/dataverse/EditDatafilesPage.java | 15 ++++++-- src/main/webapp/editFilesFragment.xhtml | 35 +++++++++++++++---- src/main/webapp/editdatafiles.xhtml | 1 + .../webapp/file-edit-popup-fragment.xhtml | 2 +- src/main/webapp/resources/js/fileupload.js | 21 ++++++++--- 5 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/EditDatafilesPage.java b/src/main/java/edu/harvard/iq/dataverse/EditDatafilesPage.java index 1f7888327cd..850ae32ec8e 100644 --- a/src/main/java/edu/harvard/iq/dataverse/EditDatafilesPage.java +++ b/src/main/java/edu/harvard/iq/dataverse/EditDatafilesPage.java @@ -356,11 +356,13 @@ public boolean isUnlimitedUploadFileSize() { } public Long getMaxTotalUploadSizeInBytes() { - return maxTotalUploadSizeInBytes; + return uploadSessionQuota == null + ? maxTotalUploadSizeInBytes + : uploadSessionQuota.getRemainingQuotaInBytes(); } public String getHumanMaxTotalUploadSizeInBytes() { - return FileSizeChecker.bytesToHumanReadable(maxTotalUploadSizeInBytes); + return FileSizeChecker.bytesToHumanReadable(getMaxTotalUploadSizeInBytes()); } public boolean isStorageQuotaEnforced() { @@ -981,6 +983,8 @@ private void deleteFiles(List filesForDelete) { // removing it from the fileMetadatas lists (above), we also remove it from // the newFiles list and the dataset's files, so it never gets saved. + releaseUploadQuota(markedForDelete.getDataFile()); + FileMetadataUtil.removeDataFileFromList(dataset.getFiles(), markedForDelete.getDataFile()); FileMetadataUtil.removeDataFileFromList(newFiles, markedForDelete.getDataFile()); FileUtil.deleteTempFile(markedForDelete.getDataFile(), dataset, ingestService); @@ -1011,6 +1015,13 @@ private void deleteFiles(List filesForDelete) { } } + private void releaseUploadQuota(DataFile dataFile) { + if (uploadSessionQuota != null && dataFile.getFilesize() > 0) { + uploadSessionQuota.setTotalUsageInBytes(Math.max(0L, + uploadSessionQuota.getTotalUsageInBytes() - dataFile.getFilesize())); + } + } + /** * Save for File Replace operations * diff --git a/src/main/webapp/editFilesFragment.xhtml b/src/main/webapp/editFilesFragment.xhtml index 8cc47f05f1c..f8425189a54 100644 --- a/src/main/webapp/editFilesFragment.xhtml +++ b/src/main/webapp/editFilesFragment.xhtml @@ -138,6 +138,29 @@ $(fileUpload).prepend('
#{bundle['file.selectToAdd.dragdropMsg']}
'); } } + function checkUploadLimits(files, maxFileSize, remainingQuota) { + var fileUploadWidget = PF('fileUploadWidget'); + var totalSize = 0; + for (var i = 0; i < files.length; i++) { + if (maxFileSize !== null && files[i].size > maxFileSize) { + fileUploadWidget.clear(); + fileUploadWidget.showMessage({summary: '#{bundle["file.edit.error.file_exceeds_limit"]}'}); + return false; + } + totalSize += files[i].size; + } + + if (remainingQuota !== null && totalSize > remainingQuota) { + var message = '#{bundle["file.addreplace.error.quota_exceeded"]}' + .replace('{0}', fileUploadWidget.formatSize(totalSize)) + .replace('{1}', fileUploadWidget.formatSize(remainingQuota)); + fileUploadWidget.clear(); + fileUploadWidget.showMessage({summary: message}); + return false; + } + + return true; + } function dropBoxUploadFinished(fileupload) { $('button[id$="dropBoxUploadFinished"]').trigger('click'); } @@ -147,7 +170,7 @@ $(document).ready(function () { uploadWidgetDropMsg(); - #{useDirectUpload ? 'setupDirectUpload(true);':''} + setupDirectUpload(#{useDirectUpload}, #{empty EditDatafilesPage.maxFileUploadSizeInBytes ? 'null' : EditDatafilesPage.maxFileUploadSizeInBytes}, #{empty EditDatafilesPage.maxTotalUploadSizeInBytes ? 'null' : EditDatafilesPage.maxTotalUploadSizeInBytes}); }); //]]> @@ -348,9 +371,9 @@