Interrupt upload until the user makes a decision?

I have a problem with the uploader. When I upload multiple files it can happen that on or multiple files already exists. If this happens I want that the uploader interrupts and a dialof is opened where the user can select if he want to overwrite the file (in my case a new version is created), the file should be stored using another name or the upload of this current file should be aborted. After the user made a decision by pressing the button, the upload should continue. I already tried with CompletableFuture but I am not able to get this work as expected. Can someone give me a hint?

This is my current code:

public CmisMultiFileUploader(DmsAbstractController dmsController, DmsScope scope, Organization organization, AutoUpdateComponent component) {
        this.uploadCounter = 0;
        this.tempFiles = new ConcurrentHashMap<>();
        this.setWidth(100, Unit.PERCENTAGE);

        MultiFileReceiver receiver = (fileName, mimeType) -> {
            try {
                parentFolder = dmsController.getCurrentFolder();
                if (parentFolder == null) {
                    displayDialog("No parent folder selected, cannot upload file(s)!");
                    throw new IllegalStateException("No parent folder selected");
                }

                File tempFile = File.createTempFile("upload-", fileName);
                tempFiles.put(fileName, tempFile);
                OutputStream outputStream = new FileOutputStream(tempFile);

                upload.addSucceededListener(event -> {
                    CompletableFuture<Void> decisionFuture = new CompletableFuture<>();
                    try {
                        try (InputStream inputStream = new FileInputStream(tempFile)) {
                            dmsController.storeDmsFile(DmsUploadOperation.UPLOAD, scope, parentFolder.getFolder(), fileName, mimeType, inputStream);
                            uploadCounter++;
                            decisionFuture.complete(null); // Upload successful, no conflicts
                        } catch (DocumentAlreadyExistsInFolder | CmisContentAlreadyExistsException e) {
                            // Handle file conflict with a blocking dialog
                            handleFileConflict(dmsController, scope, parentFolder.getFolder(), fileName, mimeType, tempFile, decisionFuture);
                        } catch (IOException | FolderNotInDmsScopeException | DmsScopeNullValueException e) {
                            decisionFuture.completeExceptionally(e);
                        }
                    } finally {
                        // Wait for decisionFuture before cleaning up
                        decisionFuture.whenComplete((result, exception) -> {
                            // Cleanup after the decision is made
                            if (exception != null) {
                                log.error("Error during file upload decision-making", exception);
                            }
                            tempFiles.remove(fileName);
                            if (tempFile.exists() && !tempFile.delete()) {
                                log.warn("Failed to delete temporary file: " + tempFile.getAbsolutePath());
                            }
                            clear();
                        });
                    }
                });

                return outputStream;
            } catch (Exception e) {
                log.error("Error initializing upload receiver for file: " + fileName, e);
                return null;
            }
        };

        upload = new Upload(receiver);
        upload.setMaxFileSize((int) dmsController.getMaxUploadSizeInByte());
        upload.setI18n(I18n.getUploadI18n(organization));
        upload.setDropAllowed(true);
        this.add(upload);

        upload.addFailedListener(event -> {
            displayDialog(event.getReason().getMessage());
        });

        upload.addStartedListener(event -> {
            log.debug("File upload started.");
        });

        upload.addProgressListener(event -> {
            //System.out.println("Progress");
        });

        upload.addFinishedListener(event -> {
            log.debug("File upload finished.");

            if(component != null)
                component.updateComponent();
        });

        upload.addFileRejectedListener(event -> {
            displayDialog(event.getErrorMessage());
        });

        upload.addAllFinishedListener(event -> {
            log.debug("All file uploads finished.");
            DefaultNotification.show(AppIcon.ok(), uploadCounter +" Datei(en) erfolgreich hochgeladen.");

            // Clear the upload form
            clear();

            //
            // Update the Autocomponent
            //
            if(component != null)
                component.updateComponent();
        });
    }

    private void handleFileConflict(DmsAbstractController dmsController, DmsScope scope, Folder parentFolder, String fileName, String mimeType, File tempFile, CompletableFuture<Void> decisionFuture) {
        // Open the conflict dialog
        UploadSelectionDialog uploadSelectionDialog = new UploadSelectionDialog(fileName);

        // New Version Option
        uploadSelectionDialog.addNewVersionListener(newVersion -> {
            try (InputStream inputStream = new FileInputStream(tempFile)) { // Use try-with-resources
                dmsController.storeDmsFile(DmsUploadOperation.NEW_VERSION, scope, parentFolder, fileName, mimeType, inputStream);
                uploadSelectionDialog.close();
                uploadCounter++;
                log.debug("Uploaded as a new version: " + fileName);
            } catch (Exception e) {
                log.error("Error uploading as new version: " + fileName, e);
                displayDialog("An error occurred while uploading " + fileName + " as a new version.");
            } finally {
                decisionFuture.complete(null); // Signal completion
            }
        });

        // Keep Both Option
        uploadSelectionDialog.addKeepBothListener(keepBoth -> {
            try (InputStream inputStream = new FileInputStream(tempFile)) {
                dmsController.storeDmsFile(DmsUploadOperation.KEEP_BOTH, scope, parentFolder, fileName, mimeType, inputStream);
                uploadSelectionDialog.close();
                uploadCounter++;
                log.debug("Uploaded with 'Keep Both' option: " + fileName);
            } catch (Exception e) {
                log.error("Error uploading with 'Keep Both' option: " + fileName, e);
                displayDialog("An error occurred while keeping both versions of " + fileName);
            } finally {
                decisionFuture.complete(null); // Signal completion
            }
        });

        // Cancel Option
        uploadSelectionDialog.addCancelListener(cancel -> {
            try {
                dmsController.storeDmsFile(DmsUploadOperation.ABORT, scope, parentFolder, fileName, mimeType, null);
                uploadSelectionDialog.close();
                log.debug("Upload canceled for: " + fileName);
            } catch (Exception e) {
                log.error("Error during upload cancellation: " + fileName, e);
                displayDialog("An error occurred while canceling the upload of " + fileName);
            } finally {
                decisionFuture.complete(null); // Signal completion
            }
        });

        // Open the dialog for user action
        uploadSelectionDialog.open();
    }

Thank you,
Florian

Not the answer you would hope for, but I doubt that it’s possible from Java.

I think the cleanest solution is to let the upload happen to temporary folder or memory buffer, and after the batch is completely uploaded, then move files to final destination. In this process you can easily handle the name conflicts, e.g. let user decide whether overwrite or let him rename the file.

Hi, thank you for your repsonse. You mean I should move the method where the files should be stored finally from the succeed listener to the finished listener?

I think it’s more like this:

  1. you use a memory buffer / temporary file
  2. allow the upload to finish even though it’s seemingly “aborted” or renamed
    3a) if the the user confirms they wanted to rename the file, save it to the final location with the requested name
    3b) if the user confirms they want to abort the upload, remove the upload from the temporary storage