diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadArtifactUIEvent.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadArtifactUIEvent.java index 3d75b67a7..e1fe4e07f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadArtifactUIEvent.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadArtifactUIEvent.java @@ -15,6 +15,5 @@ package org.eclipse.hawkbit.ui.artifacts.event; * */ public enum UploadArtifactUIEvent { - - SHOW_DROP_HINTS, HIDE_DROP_HINTS, SOFTWARE_DRAG_START, SOFTWARE_TYPE_DRAG_START, UPDATE_UPLOAD_COUNT, ENABLE_PROCESS_BUTTON, HIDE_FILTER_BY_TYPE, SHOW_FILTER_BY_TYPE, DISCARD_DELETE_SOFTWARE, DISCARD_ALL_DELETE_SOFTWARE, DELETED_ALL_SOFWARE, DISABLE_PROCESS_BUTTON, DISCARD_DELETE_SOFTWARE_TYPE, DISCARD_ALL_DELETE_SOFTWARE_TYPE, DELETED_ALL_SOFWARE_TYPE + SHOW_DROP_HINTS, HIDE_DROP_HINTS, SOFTWARE_DRAG_START, SOFTWARE_TYPE_DRAG_START, UPDATE_UPLOAD_COUNT, HIDE_FILTER_BY_TYPE, SHOW_FILTER_BY_TYPE, DISCARD_DELETE_SOFTWARE, DISCARD_ALL_DELETE_SOFTWARE, DELETED_ALL_SOFWARE, DISCARD_DELETE_SOFTWARE_TYPE, DISCARD_ALL_DELETE_SOFTWARE_TYPE, DELETED_ALL_SOFWARE_TYPE, MINIMIZED_STATUS_POPUP, MAXIMIZED_STATUS_POPUP, UPLOAD_IN_PROGESS, ARTIFACT_RESULT_POPUP_CLOSED } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadFileStatus.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadFileStatus.java new file mode 100644 index 000000000..81b096c3e --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadFileStatus.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.artifacts.event; + +import java.io.Serializable; + +import org.eclipse.hawkbit.repository.model.SoftwareModule; + +/** + * + * Holds file and upload status details.Meta data sent with upload events. + * + */ +public class UploadFileStatus implements Serializable { + + private static final long serialVersionUID = -3599629192216760811L; + + private String fileName; + + private long contentLength; + + private long bytesRead; + + private String failureReason; + + private SoftwareModule softwareModule; + + public UploadFileStatus(String fileName) { + this.fileName = fileName; + } + + public UploadFileStatus(String fileName, long bytesRead, long contentLength,SoftwareModule softwareModule) { + this.fileName = fileName; + this.contentLength = contentLength; + this.bytesRead = bytesRead; + this.softwareModule = softwareModule; + } + + public UploadFileStatus(String fileName, String failureReason,SoftwareModule selectedSw) { + this.failureReason = failureReason; + this.fileName = fileName; + this.softwareModule = selectedSw; + } + + public String getFileName() { + return fileName; + } + + public long getContentLength() { + return contentLength; + } + + public long getBytesRead() { + return bytesRead; + } + + public String getFailureReason() { + return failureReason; + } + + public SoftwareModule getSoftwareModule() { + return softwareModule; + } +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadStatusEvent.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadStatusEvent.java new file mode 100644 index 000000000..8a4da9f6d --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/event/UploadStatusEvent.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.artifacts.event; +/** + * + * Holds the upload file status. + * + */ +public class UploadStatusEvent { + + public enum UploadStatusEventType { + UPLOAD_FAILED, UPLOAD_IN_PROGRESS, UPLOAD_STARTED, UPLOAD_FINISHED, UPLOAD_SUCCESSFUL, UPLOAD_STREAMING_FAILED, UPLOAD_STREAMING_FINISHED, ABORT_UPLOAD + } + + private UploadStatusEventType uploadProgressEventType; + + private UploadFileStatus uploadStatus; + + public UploadStatusEvent(UploadStatusEventType eventType, UploadFileStatus entity) { + this.uploadProgressEventType = eventType; + this.uploadStatus = entity; + } + + public UploadFileStatus getUploadStatus() { + return uploadStatus; + } + + public void setUploadStatus(UploadFileStatus uploadStatus) { + this.uploadStatus = uploadStatus; + } + + public UploadStatusEventType getUploadProgressEventType() { + return uploadProgressEventType; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/ArtifactUploadState.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/ArtifactUploadState.java index f82ce9ccf..5399e59c8 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/ArtifactUploadState.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/state/ArtifactUploadState.java @@ -9,14 +9,18 @@ package org.eclipse.hawkbit.ui.artifacts.state; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.ui.artifacts.upload.UploadStatusObject; import org.eclipse.hawkbit.ui.common.ManagmentEntityState; import org.springframework.beans.factory.annotation.Autowired; @@ -58,8 +62,61 @@ public class ArtifactUploadState implements ManagmentEntityState, Serializ private final Set selectedDeleteSWModuleTypes = new HashSet<>(); private boolean noDataAvilableSoftwareModule = Boolean.FALSE; + + private boolean isStatusPopupMinimized = Boolean.FALSE; + + private boolean isUploadCompleted = Boolean.FALSE; + + private List uploadedFileStatusList = new ArrayList<>(); + + private final AtomicInteger numberOfFileUploadsExpected = new AtomicInteger(); - /** + private final AtomicInteger numberOfFilesActuallyUpload = new AtomicInteger(); + + private final AtomicInteger numberOfFileUploadsFailed = new AtomicInteger(); + + public AtomicInteger getNumberOfFileUploadsFailed() { + return numberOfFileUploadsFailed; + } + + public AtomicInteger getNumberOfFilesActuallyUpload() { + return numberOfFilesActuallyUpload; + } + + public AtomicInteger getNumberOfFileUploadsExpected() { + return numberOfFileUploadsExpected; + } + + + public List getUploadedFileStatusList() { + return uploadedFileStatusList; + } + + public void setUploadedFileStatusList(List uploadedFileStatusList) { + this.uploadedFileStatusList = uploadedFileStatusList; + } + + public boolean isUploadCompleted() { + return isUploadCompleted; + } + + public void setUploadCompleted(boolean isUploadCompleted) { + this.isUploadCompleted = isUploadCompleted; + } + + + public void setStatusPopupMinimized(boolean isStatusPopupMinimized) { + this.isStatusPopupMinimized = isStatusPopupMinimized; + } + + public boolean isStatusPopupMinimized() { + return isStatusPopupMinimized; + } + + + + + /** * Set software. * * @return diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadConfirmationwindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadConfirmationwindow.java index 971e1e446..8455dea69 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadConfirmationwindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadConfirmationwindow.java @@ -544,7 +544,7 @@ public class UploadConfirmationwindow implements Button.ClickListener { if (event.getComponent().getId().equals(SPUIComponentIdProvider.UPLOAD_ARTIFACT_DETAILS_CLOSE)) { uploadConfrimationWindow.close(); } else if (event.getComponent().getId().equals(SPUIComponentIdProvider.UPLOAD_DISCARD_DETAILS_BUTTON)) { - uploadLayout.clearFileList(); + uploadLayout.clearUploadedFileDetails(); uploadConfrimationWindow.close(); } else if (event.getComponent().getId().equals(SPUIComponentIdProvider.UPLOAD_BUTTON)) { processArtifactUpload(); @@ -568,10 +568,10 @@ public class UploadConfirmationwindow implements Button.ClickListener { uploadDetailsTable.removeItem(((Button) event.getComponent()).getData()); uploadLayout.getFileSelected().remove(customFile); - uploadLayout.updateActionCount(); + uploadLayout.updateUploadCounts(); if (uploadDetailsTable.getItemIds().isEmpty()) { - uploadLayout.clearFileList(); uploadConfrimationWindow.close(); + uploadLayout.clearUploadedFileDetails(); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadHandler.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadHandler.java index d5c6759b5..78a6fd9a9 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadHandler.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadHandler.java @@ -11,13 +11,21 @@ package org.eclipse.hawkbit.ui.artifacts.upload; import java.io.IOException; import java.io.OutputStream; +import javax.annotation.PreDestroy; + import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException; import org.eclipse.hawkbit.repository.model.SoftwareModule; -import org.eclipse.hawkbit.ui.artifacts.state.CustomFile; +import org.eclipse.hawkbit.ui.artifacts.event.UploadFileStatus; +import org.eclipse.hawkbit.ui.artifacts.event.UploadStatusEvent; +import org.eclipse.hawkbit.ui.artifacts.event.UploadStatusEvent.UploadStatusEventType; +import org.eclipse.hawkbit.ui.artifacts.state.ArtifactUploadState; import org.eclipse.hawkbit.ui.utils.I18N; import org.eclipse.hawkbit.ui.utils.SpringContextHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.vaadin.spring.events.EventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; import com.vaadin.server.StreamVariable; import com.vaadin.ui.Upload; @@ -49,28 +57,53 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene private final long fileSize; private final UploadLayout view; - private final UploadStatusInfoWindow infoWindow; private final long maxSize; private final Upload upload; private volatile String fileName = null; private volatile String mimeType = null; - private volatile boolean interrupted = false; + private volatile boolean streamingInterrupted = false; + private volatile boolean uploadInterrupted = false; + private volatile boolean aborted = false; + private String failureReason; private final I18N i18n; + private transient EventBus.SessionEventBus eventBus; + private final SoftwareModule selectedSw; + private SoftwareModule selectedSwForUpload; + private ArtifactUploadState artifactUploadState; - UploadHandler(final String fileName, final long fileSize, final UploadLayout view, - final UploadStatusInfoWindow infoWindow, final long maxSize, final Upload upload, final String mimeType) { + UploadHandler(final String fileName, final long fileSize, final UploadLayout view, final long maxSize, + final Upload upload, final String mimeType, SoftwareModule selectedSw) { super(); + this.aborted = false; this.fileName = fileName; this.fileSize = fileSize; this.view = view; - this.infoWindow = infoWindow; this.maxSize = maxSize; this.upload = upload; this.mimeType = mimeType; + this.selectedSw = selectedSw; this.i18n = SpringContextHelper.getBean(I18N.class); + this.eventBus = SpringContextHelper.getBean(EventBus.SessionEventBus.class); + this.artifactUploadState = SpringContextHelper.getBean(ArtifactUploadState.class); + eventBus.subscribe(this); + } + @PreDestroy + void destroy() { + /* + * It's good manners to do this, even though vaadin-spring will + * automatically unsubscribe when this UI is garbage collected. + */ + eventBus.unsubscribe(this); + } + + @EventBusListenerMethod(scope = EventScope.SESSION) + void onEvent(final UploadStatusEventType event) { + if (event == UploadStatusEventType.ABORT_UPLOAD) { + aborted = true; + } } /** @@ -81,11 +114,13 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene @Override public final OutputStream getOutputStream() { try { - return view.saveUploadedFileDetails(fileName, fileSize, mimeType); + streamingInterrupted = false; + failureReason = null; + return view.saveUploadedFileDetails(fileName, fileSize, mimeType, selectedSw); } catch (final ArtifactUploadFailedException e) { LOG.error("Atifact upload failed {} ", e); failureReason = e.getMessage(); - interrupted = true; + streamingInterrupted = true; return new NullOutputStream(); } } @@ -98,23 +133,22 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene */ @Override public OutputStream receiveUpload(final String fileName, final String mimeType) { + aborted = false; + failureReason = null; this.fileName = fileName; this.mimeType = mimeType; // reset has directory flag before upload view.setHasDirectory(false); try { - if (view.checkIfSoftwareModuleIsSelected()) { - if (view.checkForDuplicate(fileName)) { - view.showDuplicateMessage(); - } else { - view.increaseNumberOfFileUploadsExpected(); - return view.saveUploadedFileDetails(fileName, 0, mimeType); - } + if (view.checkIfSoftwareModuleIsSelected() && !view.checkForDuplicate(fileName, selectedSwForUpload)) { + view.increaseNumberOfFileUploadsExpected(); + return view.saveUploadedFileDetails(fileName, 0, mimeType, selectedSwForUpload); } } catch (final ArtifactUploadFailedException e) { LOG.error("Atifact upload failed {} ", e); failureReason = e.getMessage(); upload.interruptUpload(); + uploadInterrupted = true; } // if final validation fails ,final no upload ,return NullOutputStream return new NullOutputStream(); @@ -129,13 +163,8 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene @Override public void uploadSucceeded(final SucceededEvent event) { LOG.debug("Streaming finished for file :{}", event.getFilename()); - view.updateFileSize(event.getFilename(), event.getLength()); - - // recorded that we now one more uploaded - view.increaseNumberOfFilesActuallyUpload(); - - // inform upload status window - infoWindow.uploadSucceeded(event.getFilename()); + eventBus.publish(this, new UploadStatusEvent(UploadStatusEventType.UPLOAD_SUCCESSFUL, new UploadFileStatus( + event.getFilename(), 0, event.getLength(), selectedSwForUpload))); } /** @@ -148,20 +177,8 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene @Override public void streamingFinished(final StreamingEndEvent event) { LOG.debug("Streaming finished for file :{}", event.getFileName()); - // record that we now one more uploaded - view.increaseNumberOfFilesActuallyUpload(); - - // inform upload status window - infoWindow.uploadSucceeded(event.getFileName()); - - // check if we are finished - if (view.enableProcessBtn()) { - infoWindow.uploadSessionFinished(); - } - view.updateActionCount(); - - // display the duplicate message after streaming all files - view.displayDuplicateValidationMessage(); + eventBus.publish(this, new UploadStatusEvent(UploadStatusEventType.UPLOAD_STREAMING_FINISHED, + new UploadFileStatus(event.getFileName(), 0, event.getContentLength(), selectedSw))); } /** @@ -173,12 +190,8 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene @Override public void uploadFinished(final FinishedEvent event) { LOG.debug("Upload finished for file :{}", event.getFilename()); - // check if we are finished - if (view.enableProcessBtn()) { - infoWindow.uploadSessionFinished(); - } - view.updateActionCount(); - + eventBus.publish(this, + new UploadStatusEvent(UploadStatusEventType.UPLOAD_FINISHED, new UploadFileStatus(event.getFilename()))); } /** @@ -189,7 +202,8 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene @Override public void streamingStarted(final StreamingStartEvent event) { LOG.debug("Streaming started for file :{}", fileName); - infoWindow.uploadStarted(fileName); + eventBus.publish(this, new UploadStatusEvent(UploadStatusEventType.UPLOAD_STARTED, new UploadFileStatus( + fileName, 0, 0, selectedSw))); } /** @@ -199,14 +213,24 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene */ @Override public void uploadStarted(final StartedEvent event) { - // single file session - if (view.isSoftwareModuleSelected() && !view.checkIfFileIsDuplicate(event.getFilename())) { - infoWindow.uploadSessionStarted(); - LOG.debug("Upload started for file :{}", event.getFilename()); - infoWindow.uploadStarted(event.getFilename()); - } else { + uploadInterrupted = false; + selectedSwForUpload = artifactUploadState.getSelectedBaseSoftwareModule().isPresent() ? artifactUploadState + .getSelectedBaseSoftwareModule().get() : null; + + if (view.isSoftwareModuleSelected()) { + // single file session + if (!view.checkIfFileIsDuplicate(event.getFilename(), selectedSwForUpload)) { + LOG.debug("Upload started for file :{}", event.getFilename()); + eventBus.publish(this, new UploadStatusEvent(UploadStatusEventType.UPLOAD_STARTED, + new UploadFileStatus(event.getFilename(), 0, 0, selectedSwForUpload))); + } + } + else { failureReason = i18n.get("message.upload.failed"); upload.interruptUpload(); + // actual interrupt will happen a bit late so setting the below + // flag + uploadInterrupted = true; } } @@ -227,21 +251,25 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene */ @Override public void updateProgress(final long readBytes, final long contentLength) { - if (readBytes > maxSize || contentLength > maxSize) { - LOG.error("User tried to upload more than was allowed ({}).", maxSize); - view.decreaseNumberOfFileUploadsExpected(); - final SoftwareModule sw = view.getSoftwareModuleSelected(); - view.getFileSelected().remove(new CustomFile(fileName, sw.getName(), sw.getVersion())); - view.updateActionCount(); - failureReason = i18n.get("message.uploadedfile.size.exceeded", maxSize); - infoWindow.uploadFailed(fileName, failureReason); - upload.interruptUpload(); - interrupted = true; - return; + // Update progress is called event after upload interrupted in + // uploadStarted method + if (!uploadInterrupted) { + if (aborted) { + LOG.info("User aborted file upload for file : {}", fileName); + failureReason = i18n.get("message.uploadedfile.aborted"); + interruptFileUpload(); + return; + } + if (readBytes > maxSize || contentLength > maxSize) { + LOG.error("User tried to upload more than was allowed ({}).", maxSize); + failureReason = i18n.get("message.uploadedfile.size.exceeded", maxSize); + interruptFileUpload(); + return; + } + eventBus.publish(this, new UploadStatusEvent(UploadStatusEventType.UPLOAD_IN_PROGRESS, + new UploadFileStatus(fileName, readBytes, contentLength, selectedSwForUpload))); + LOG.info("Update progress - {} : {}", fileName, (double) readBytes / (double) contentLength); } - - infoWindow.updateProgress(fileName, readBytes, contentLength); - LOG.info("Update progress - {} : {}", fileName, (double) readBytes / (double) contentLength); } /** @@ -251,19 +279,20 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene */ @Override public void onProgress(final StreamingProgressEvent event) { - if (event.getBytesReceived() > maxSize || event.getContentLength() > maxSize) { - LOG.error("User tried to upload more than was allowed ({}).", maxSize); - view.decreaseNumberOfFileUploadsExpected(); - final SoftwareModule sw = view.getSoftwareModuleSelected(); - view.getFileSelected().remove(new CustomFile(fileName, sw.getName(), sw.getVersion())); - view.updateActionCount(); - failureReason = i18n.get("message.uploadedfile.size.exceeded", maxSize); - infoWindow.uploadFailed(event.getFileName(), failureReason); - interrupted = true; + if (aborted) { + LOG.info("User aborted the upload for file : {}", event.getFileName()); + failureReason = i18n.get("message.uploadedfile.aborted"); + interruptFileStreaming(); return; } - - infoWindow.updateProgress(event.getFileName(), event.getBytesReceived(), event.getContentLength()); + if (event.getBytesReceived() > maxSize || event.getContentLength() > maxSize) { + LOG.error("User tried to upload more than was allowed ({}).", maxSize); + failureReason = i18n.get("message.uploadedfile.size.exceeded", maxSize); + interruptFileStreaming(); + return; + } + eventBus.publish(this, new UploadStatusEvent(UploadStatusEventType.UPLOAD_IN_PROGRESS, new UploadFileStatus( + fileName, event.getBytesReceived(), event.getContentLength(), selectedSw))); // Logging to solve sonar issue LOG.trace("Streaming in progress for file :{}", event.getFileName()); } @@ -276,19 +305,16 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene */ @Override public void streamingFailed(final StreamingErrorEvent event) { - LOG.info("Streaming failed for file :{}", event.getFileName()); - view.decreaseNumberOfFileUploadsExpected(); - final SoftwareModule sw = view.getSoftwareModuleSelected(); - view.getFileSelected().remove(new CustomFile(fileName, sw.getName(), sw.getVersion())); - view.updateActionCount(); - infoWindow.uploadFailed(event.getFileName(), failureReason); - // check if we are finished - if (view.enableProcessBtn()) { - infoWindow.uploadSessionFinished(); + if (failureReason == null) { + failureReason = event.getException().getMessage(); } - view.displayDuplicateValidationMessage(); + eventBus.publish(this, new UploadStatusEvent(UploadStatusEventType.UPLOAD_STREAMING_FAILED, + new UploadFileStatus(fileName, failureReason, selectedSw))); - LOG.info("Streaming failed due to :{}", event.getException()); + if (!aborted) { + LOG.info("Streaming failed for file :{}", event.getFileName()); + LOG.info("Streaming failed due to :{}", event.getException()); + } } /** @@ -298,20 +324,21 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene */ @Override public void uploadFailed(final FailedEvent event) { - LOG.info("Upload failed for file :{}", event.getFilename()); - view.decreaseNumberOfFileUploadsExpected(); /** - * If upload interrupted because of duplicate file,do not remove the - * file already in upload list - **/ - if (!view.getDuplicateFileNamesList().isEmpty()) { - final SoftwareModule sw = view.getSoftwareModuleSelected(); - view.getFileSelected().remove(new CustomFile(fileName, sw.getName(), sw.getVersion())); + * If upload failed due to no selected software UPLOAD_FAILED event need + * not be published. + */ + if (selectedSwForUpload != null) { + if (failureReason == null) { + failureReason = event.getReason().getMessage(); + } + eventBus.publish(this, new UploadStatusEvent(UploadStatusEventType.UPLOAD_FAILED, new UploadFileStatus( + fileName, failureReason, selectedSwForUpload))); + if (!aborted) { + LOG.info("Upload failed for file :{}", event.getFilename()); + LOG.info("Upload failed for file :{}", event.getReason()); + } } - view.updateActionCount(); - infoWindow.uploadFailed(event.getFilename(), failureReason); - LOG.info("Upload failed for file :{}", event.getReason()); - } /** @@ -319,7 +346,7 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene */ @Override public boolean isInterrupted() { - return interrupted; + return streamingInterrupted; } private static class NullOutputStream extends OutputStream { @@ -365,4 +392,13 @@ public class UploadHandler implements StreamVariable, Receiver, SucceededListene return true; } + private void interruptFileStreaming() { + streamingInterrupted = true; + } + + private void interruptFileUpload() { + upload.interruptUpload(); + uploadInterrupted = true; + } + } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadLayout.java index 48da08118..af02e917b 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadLayout.java @@ -16,7 +16,6 @@ import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -26,6 +25,8 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule; import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent; import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent.SoftwareModuleEventType; import org.eclipse.hawkbit.ui.artifacts.event.UploadArtifactUIEvent; +import org.eclipse.hawkbit.ui.artifacts.event.UploadStatusEvent; +import org.eclipse.hawkbit.ui.artifacts.event.UploadStatusEvent.UploadStatusEventType; import org.eclipse.hawkbit.ui.artifacts.state.ArtifactUploadState; import org.eclipse.hawkbit.ui.artifacts.state.CustomFile; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; @@ -97,10 +98,6 @@ public class UploadLayout extends VerticalLayout { @Autowired private transient SPInfo spInfo; - private final AtomicInteger numberOfFileUploadsExpected = new AtomicInteger(); - - private final AtomicInteger numberOfFilesActuallyUpload = new AtomicInteger(); - private final List duplicateFileNamesList = new ArrayList<>(); private Button processBtn; @@ -119,6 +116,8 @@ public class UploadLayout extends VerticalLayout { private Boolean hasDirectory = Boolean.FALSE; + private Button uploadStatusButton; + /** * Initialize the upload layout. */ @@ -126,13 +125,52 @@ public class UploadLayout extends VerticalLayout { void init() { createComponents(); buildLayout(); - updateActionCount(); + restoreState(); eventBus.subscribe(this); ui = UI.getCurrent(); } - private void createComponents() { + @EventBusListenerMethod(scope = EventScope.SESSION) + void onEvent(final UploadArtifactUIEvent event) { + if (event == UploadArtifactUIEvent.DELETED_ALL_SOFWARE) { + ui.access(() -> updateActionCount()); + } else if (event == UploadArtifactUIEvent.MINIMIZED_STATUS_POPUP) { + ui.access(() -> showUploadStatusButton()); + } else if (event == UploadArtifactUIEvent.MAXIMIZED_STATUS_POPUP) { + ui.access(() -> maximizeStatusPopup()); + } else if (event == UploadArtifactUIEvent.ARTIFACT_RESULT_POPUP_CLOSED) { + ui.access(() -> closeUploadStatusPopup()); + } + } + @EventBusListenerMethod(scope = EventScope.SESSION) + void onEvent(final UploadStatusEvent event) { + if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_STARTED) { + ui.access(() -> onStartOfUpload()); + } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_FAILED) { + ui.access(() -> onUploadFailure(event)); + } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_FINISHED) { + ui.access(() -> onUploadCompletion()); + } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_SUCCESSFUL) { + ui.access(() -> onUploadSuccess(event)); + } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_STREAMING_FAILED) { + ui.access(() -> onUploadStreamingFailure(event)); + } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_STREAMING_FINISHED) { + ui.access(() -> onUploadStreamingSuccess()); + } + } + + @PreDestroy + void destroy() { + /* + * It's good manners to do this, even though vaadin-spring will + * automatically unsubscribe when this UI is garbage collected. + */ + eventBus.unsubscribe(this); + } + + private void createComponents() { + createUploadStatusButton(); createProcessButton(); createDiscardBtn(); } @@ -140,8 +178,8 @@ public class UploadLayout extends VerticalLayout { private void buildLayout() { final Upload upload = new Upload(); - final UploadHandler uploadHandler = new UploadHandler(null, 0, this, uploadInfoWindow, - spInfo.getMaxArtifactFileSize(), upload, null); + final UploadHandler uploadHandler = new UploadHandler(null, 0, this, spInfo.getMaxArtifactFileSize(), upload, + null, null); upload.setButtonCaption(i18n.get("upload.file")); upload.setImmediate(true); upload.setReceiver(uploadHandler); @@ -154,12 +192,15 @@ public class UploadLayout extends VerticalLayout { fileUploadLayout = new HorizontalLayout(); fileUploadLayout.setSpacing(true); + fileUploadLayout.addStyleName(SPUIStyleDefinitions.FOOTER_LAYOUT); fileUploadLayout.addComponent(upload); fileUploadLayout.setComponentAlignment(upload, Alignment.MIDDLE_LEFT); fileUploadLayout.addComponent(processBtn); fileUploadLayout.setComponentAlignment(processBtn, Alignment.MIDDLE_RIGHT); fileUploadLayout.addComponent(discardBtn); fileUploadLayout.setComponentAlignment(discardBtn, Alignment.MIDDLE_RIGHT); + fileUploadLayout.addComponent(uploadStatusButton); + fileUploadLayout.setComponentAlignment(uploadStatusButton, Alignment.MIDDLE_RIGHT); setMargin(false); /* create drag-drop wrapper for drop area */ @@ -167,7 +208,25 @@ public class UploadLayout extends VerticalLayout { dropAreaWrapper.setDropHandler(new DropAreahandler()); setSizeFull(); setSpacing(true); + } + private void restoreState() { + updateActionCount(); + + if (!artifactUploadState.getFileSelected().isEmpty() && artifactUploadState.isUploadCompleted()) { + processBtn.setEnabled(true); + } + if (artifactUploadState.isStatusPopupMinimized()) { + showUploadStatusButton(); + if (artifactUploadState.isUploadCompleted()) { + setUploadStatusButtonIconToFinished(); + } + } + if (artifactUploadState.isUploadCompleted()) { + artifactUploadState.getNumberOfFilesActuallyUpload().set(0); + artifactUploadState.getNumberOfFileUploadsExpected().set(0); + artifactUploadState.getNumberOfFileUploadsFailed().set(0); + } } public DragAndDropWrapper getDropAreaWrapper() { @@ -187,15 +246,16 @@ public class UploadLayout extends VerticalLayout { public void drop(final DragAndDropEvent event) { if (validate(event)) { final Html5File[] files = ((WrapperTransferable) event.getTransferable()).getFiles(); + // selected software module at the time of file drop is + // considered for upload + SoftwareModule selectedSw = artifactUploadState.getSelectedBaseSoftwareModule().get(); // reset the flag hasDirectory = Boolean.FALSE; for (final Html5File file : files) { - processFile(file); + processFile(file, selectedSw); } - if (numberOfFileUploadsExpected.get() > 0) { + if (artifactUploadState.getNumberOfFileUploadsExpected().get() > 0) { processBtn.setEnabled(false); - // reset before we start - uploadInfoWindow.uploadSessionStarted(); } else { // If the upload is not started, it signifies all // dropped files as either duplicate or directory.So @@ -205,20 +265,20 @@ public class UploadLayout extends VerticalLayout { } } - private void processFile(final Html5File file) { + private void processFile(final Html5File file, SoftwareModule selectedSw) { if (!isDirectory(file)) { - if (!checkForDuplicate(file.getFileName())) { - numberOfFileUploadsExpected.incrementAndGet(); - file.setStreamVariable(createStreamVariable(file)); + if (!checkForDuplicate(file.getFileName(), selectedSw)) { + artifactUploadState.getNumberOfFileUploadsExpected().incrementAndGet(); + file.setStreamVariable(createStreamVariable(file, selectedSw)); } } else { hasDirectory = Boolean.TRUE; } } - private StreamVariable createStreamVariable(final Html5File file) { - return new UploadHandler(file.getFileName(), file.getFileSize(), UploadLayout.this, uploadInfoWindow, - spInfo.getMaxArtifactFileSize(), null, file.getType()); + private StreamVariable createStreamVariable(final Html5File file, SoftwareModule selectedSw) { + return new UploadHandler(file.getFileName(), file.getFileSize(), UploadLayout.this, + spInfo.getMaxArtifactFileSize(), null, file.getType(), selectedSw); } private boolean isDirectory(final Html5File file) { @@ -270,9 +330,7 @@ public class UploadLayout extends VerticalLayout { processBtn.addStyleName(SPUIStyleDefinitions.ACTION_BUTTON); processBtn.addClickListener(this::displayConfirmWindow); processBtn.setHtmlContentAllowed(true); - if (artifactUploadState.getFileSelected().isEmpty()) { - processBtn.setEnabled(false); - } + processBtn.setEnabled(false); } private void createDiscardBtn() { @@ -284,23 +342,14 @@ public class UploadLayout extends VerticalLayout { discardBtn.addClickListener(this::discardUploadData); } - boolean checkForDuplicate(final String filename) { - final Boolean isDuplicate = checkIfFileIsDuplicate(filename); + boolean checkForDuplicate(final String filename, final SoftwareModule selectedSw) { + final Boolean isDuplicate = checkIfFileIsDuplicate(filename, selectedSw); if (isDuplicate) { getDuplicateFileNamesList().add(filename); } return isDuplicate; } - @EventBusListenerMethod(scope = EventScope.SESSION) - void toggleProcessButton(final UploadArtifactUIEvent event) { - if (event == UploadArtifactUIEvent.ENABLE_PROCESS_BUTTON) { - processBtn.setEnabled(true); - } else if (event == UploadArtifactUIEvent.DISABLE_PROCESS_BUTTON) { - processBtn.setEnabled(false); - } - } - /** * Save uploaded file details. * @@ -312,29 +361,29 @@ public class UploadLayout extends VerticalLayout { * file size * @param mimeType * the mimeType of the file + * @param selectedSw * @throws IOException * in case of upload errors */ - OutputStream saveUploadedFileDetails(final String name, final long size, final String mimeType) { + OutputStream saveUploadedFileDetails(final String name, final long size, final String mimeType, + SoftwareModule selectedSw) { File tempFile = null; try { tempFile = File.createTempFile("spUiArtifactUpload", null); final OutputStream out = new FileOutputStream(tempFile); - final SoftwareModule selectedSoftwareModule = artifactUploadState.getSelectedBaseSoftwareModule().get(); + final String currentBaseSoftwareModuleKey = HawkbitCommonUtil.getFormattedNameVersion(selectedSw.getName(), + selectedSw.getVersion()); - final String currentBaseSoftwareModuleKey = HawkbitCommonUtil - .getFormattedNameVersion(selectedSoftwareModule.getName(), selectedSoftwareModule.getVersion()); - - final CustomFile customFile = new CustomFile(name, size, tempFile.getAbsolutePath(), - selectedSoftwareModule.getName(), selectedSoftwareModule.getVersion(), mimeType); + final CustomFile customFile = new CustomFile(name, size, tempFile.getAbsolutePath(), selectedSw.getName(), + selectedSw.getVersion(), mimeType); artifactUploadState.getFileSelected().add(customFile); processBtn.setEnabled(false); if (!artifactUploadState.getBaseSwModuleList().keySet().contains(currentBaseSoftwareModuleKey)) { - artifactUploadState.getBaseSwModuleList().put(currentBaseSoftwareModuleKey, selectedSoftwareModule); + artifactUploadState.getBaseSwModuleList().put(currentBaseSoftwareModuleKey, selectedSw); } return out; } catch (final FileNotFoundException e) { @@ -344,6 +393,7 @@ public class UploadLayout extends VerticalLayout { LOG.error("Upload failed {}", e); throw new ArtifactUploadFailedException(i18n.get("message.upload.failed")); } + } Boolean validate(final DragAndDropEvent event) { @@ -378,13 +428,6 @@ public class UploadLayout extends VerticalLayout { return true; } - SoftwareModule getSoftwareModuleSelected() { - if (artifactUploadState.getSelectedBaseSoftwareModule().isPresent()) { - return artifactUploadState.getSelectedBaseSoftwareModule().get(); - } - return null; - } - Boolean isSoftwareModuleSelected() { if (!artifactUploadState.getSelectedBaseSwModuleId().isPresent()) { return false; @@ -400,11 +443,10 @@ public class UploadLayout extends VerticalLayout { * file name * @return Boolean */ - public Boolean checkIfFileIsDuplicate(final String name) { + public Boolean checkIfFileIsDuplicate(final String name, final SoftwareModule selectedSoftwareModule) { Boolean isDuplicate = false; - final SoftwareModule selectedSoftwareModule = artifactUploadState.getSelectedBaseSoftwareModule().get(); - final String currentBaseSoftwareModuleKey = HawkbitCommonUtil - .getFormattedNameVersion(selectedSoftwareModule.getName(), selectedSoftwareModule.getVersion()); + final String currentBaseSoftwareModuleKey = HawkbitCommonUtil.getFormattedNameVersion( + selectedSoftwareModule.getName(), selectedSoftwareModule.getVersion()); for (final CustomFile customFile : artifactUploadState.getFileSelected()) { final String fileSoftwareModuleKey = HawkbitCommonUtil.getFormattedNameVersion( @@ -418,7 +460,7 @@ public class UploadLayout extends VerticalLayout { } void decreaseNumberOfFileUploadsExpected() { - numberOfFileUploadsExpected.decrementAndGet(); + artifactUploadState.getNumberOfFileUploadsExpected().decrementAndGet(); } List getDuplicateFileNamesList() { @@ -439,8 +481,10 @@ public class UploadLayout extends VerticalLayout { void displayDuplicateValidationMessage() { // check if streaming of all dropped files are completed - if (numberOfFilesActuallyUpload.intValue() == numberOfFileUploadsExpected.intValue()) { + if (artifactUploadState.getNumberOfFilesActuallyUpload().intValue() == artifactUploadState + .getNumberOfFileUploadsExpected().intValue()) { displayCompositeMessage(); + duplicateFileNamesList.clear(); } } @@ -454,7 +498,6 @@ public class UploadLayout extends VerticalLayout { } else if (duplicateFileNamesList.size() > 1) { message.append(i18n.get("message.no.duplicateFiles")); } - duplicateFileNamesList.clear(); } return message.toString(); } @@ -464,13 +507,12 @@ public class UploadLayout extends VerticalLayout { } void increaseNumberOfFileUploadsExpected() { - numberOfFileUploadsExpected.incrementAndGet(); + artifactUploadState.getNumberOfFileUploadsExpected().incrementAndGet(); } - void updateFileSize(final String name, final long size) { - final SoftwareModule selectedSoftwareModule = artifactUploadState.getSelectedBaseSoftwareModule().get(); - final String currentBaseSoftwareModuleKey = HawkbitCommonUtil - .getFormattedNameVersion(selectedSoftwareModule.getName(), selectedSoftwareModule.getVersion()); + void updateFileSize(final String name, final long size, SoftwareModule selectedSoftwareModule) { + final String currentBaseSoftwareModuleKey = HawkbitCommonUtil.getFormattedNameVersion( + selectedSoftwareModule.getName(), selectedSoftwareModule.getVersion()); for (final CustomFile customFile : artifactUploadState.getFileSelected()) { final String fileSoftwareModuleKey = HawkbitCommonUtil.getFormattedNameVersion( @@ -483,17 +525,22 @@ public class UploadLayout extends VerticalLayout { } void increaseNumberOfFilesActuallyUpload() { - numberOfFilesActuallyUpload.incrementAndGet(); + artifactUploadState.getNumberOfFilesActuallyUpload().incrementAndGet(); + } + + void increaseNumberOfFileUploadsFailed() { + artifactUploadState.getNumberOfFileUploadsFailed().incrementAndGet(); } /** * Enable process button once upload is completed. */ boolean enableProcessBtn() { - if (numberOfFilesActuallyUpload.intValue() >= numberOfFileUploadsExpected.intValue()) { + if (artifactUploadState.getNumberOfFilesActuallyUpload().intValue() >= artifactUploadState + .getNumberOfFileUploadsExpected().intValue() && !getFileSelected().isEmpty()) { processBtn.setEnabled(true); - numberOfFileUploadsExpected.set(0); - numberOfFilesActuallyUpload.set(0); + artifactUploadState.getNumberOfFilesActuallyUpload().set(0); + artifactUploadState.getNumberOfFileUploadsExpected().set(0); return true; } return false; @@ -507,13 +554,23 @@ public class UploadLayout extends VerticalLayout { if (event.getButton().equals(discardBtn)) { if (artifactUploadState.getFileSelected().isEmpty()) { uiNotification.displayValidationError(i18n.get("message.error.noFileSelected")); - } else { - clearFileList(); + clearUploadedFileDetails(); } } } + protected void clearUploadedFileDetails() { + clearFileList(); + closeUploadStatusPopup(); + } + + private void closeUploadStatusPopup() { + uploadInfoWindow.clearWindow(); + hideUploadStatusButton(); + artifactUploadState.setStatusPopupMinimized(false); + } + /** * Clear details. */ @@ -529,19 +586,20 @@ public class UploadLayout extends VerticalLayout { processBtn.setCaption(SPUILabelDefinitions.PROCESS); /* disable when there is no files to upload. */ processBtn.setEnabled(false); - numberOfFileUploadsExpected.set(0); - numberOfFilesActuallyUpload.set(0); + artifactUploadState.getNumberOfFilesActuallyUpload().set(0); + artifactUploadState.getNumberOfFileUploadsExpected().set(0); + artifactUploadState.getNumberOfFileUploadsFailed().set(0); duplicateFileNamesList.clear(); } private void setConfirmationPopupHeightWidth(final float newWidth, final float newHeight) { if (currentUploadConfirmationwindow != null) { - currentUploadConfirmationwindow.getUploadArtifactDetails().setWidth(HawkbitCommonUtil - .getArtifactUploadPopupWidth(newWidth, SPUIDefinitions.MIN_UPLOAD_CONFIRMATION_POPUP_WIDTH), - Unit.PIXELS); - currentUploadConfirmationwindow.getUploadDetailsTable().setHeight(HawkbitCommonUtil - .getArtifactUploadPopupHeight(newHeight, SPUIDefinitions.MIN_UPLOAD_CONFIRMATION_POPUP_HEIGHT), - Unit.PIXELS); + currentUploadConfirmationwindow.getUploadArtifactDetails().setWidth( + HawkbitCommonUtil.getArtifactUploadPopupWidth(newWidth, + SPUIDefinitions.MIN_UPLOAD_CONFIRMATION_POPUP_WIDTH), Unit.PIXELS); + currentUploadConfirmationwindow.getUploadDetailsTable().setHeight( + HawkbitCommonUtil.getArtifactUploadPopupHeight(newHeight, + SPUIDefinitions.MIN_UPLOAD_CONFIRMATION_POPUP_HEIGHT), Unit.PIXELS); } } @@ -558,10 +616,12 @@ public class UploadLayout extends VerticalLayout { && currentUploadConfirmationwindow.getCurrentUploadResultWindow() != null) { final UploadResultWindow uploadResultWindow = currentUploadConfirmationwindow .getCurrentUploadResultWindow(); - uploadResultWindow.getUploadResultsWindow().setWidth(HawkbitCommonUtil.getArtifactUploadPopupWidth(newWidth, - SPUIDefinitions.MIN_UPLOAD_CONFIRMATION_POPUP_WIDTH), Unit.PIXELS); - uploadResultWindow.getUploadResultTable().setHeight(HawkbitCommonUtil.getArtifactUploadPopupHeight( - newHeight, SPUIDefinitions.MIN_UPLOAD_CONFIRMATION_POPUP_HEIGHT), Unit.PIXELS); + uploadResultWindow.getUploadResultsWindow().setWidth( + HawkbitCommonUtil.getArtifactUploadPopupWidth(newWidth, + SPUIDefinitions.MIN_UPLOAD_CONFIRMATION_POPUP_WIDTH), Unit.PIXELS); + uploadResultWindow.getUploadResultTable().setHeight( + HawkbitCommonUtil.getArtifactUploadPopupHeight(newHeight, + SPUIDefinitions.MIN_UPLOAD_CONFIRMATION_POPUP_HEIGHT), Unit.PIXELS); } } @@ -572,8 +632,8 @@ public class UploadLayout extends VerticalLayout { } else { currentUploadConfirmationwindow = new UploadConfirmationwindow(this, artifactUploadState); UI.getCurrent().addWindow(currentUploadConfirmationwindow.getUploadConfrimationWindow()); - setConfirmationPopupHeightWidth(Page.getCurrent().getBrowserWindowWidth(), - Page.getCurrent().getBrowserWindowHeight()); + setConfirmationPopupHeightWidth(Page.getCurrent().getBrowserWindowWidth(), Page.getCurrent() + .getBrowserWindowHeight()); } } } @@ -599,25 +659,101 @@ public class UploadLayout extends VerticalLayout { /** * @return */ - VerticalLayout getDropAreaLayout() { return dropAreaLayout; } - @EventBusListenerMethod(scope = EventScope.SESSION) - void onEvent(final UploadArtifactUIEvent event) { - if (event == UploadArtifactUIEvent.DELETED_ALL_SOFWARE) { - ui.access(() -> updateActionCount()); + private void onStartOfUpload() { + setUploadStatusButtonIconToInProgress(); + if (artifactUploadState.isStatusPopupMinimized()) { + updateStatusButtonCount(); } } - @PreDestroy - void destroy() { - /* - * It's good manners to do this, even though vaadin-spring will - * automatically unsubscribe when this UI is garbage collected. - */ - eventBus.unsubscribe(this); + private void onUploadStreamingSuccess() { + increaseNumberOfFilesActuallyUpload(); + updateUploadCounts(); + enableProcessBtn(); + if (isUploadComplete()) { + uploadInfoWindow.uploadSessionFinished(); + setUploadStatusButtonIconToFinished(); + } + // display the duplicate message after streaming all files + displayDuplicateValidationMessage(); + } + + private void onUploadStreamingFailure(UploadStatusEvent event) { + /** + * If upload interrupted because of duplicate file,do not remove the + * file already in upload list + **/ + if (getDuplicateFileNamesList().isEmpty() + || !getDuplicateFileNamesList().contains(event.getUploadStatus().getFileName())) { + final SoftwareModule sw = event.getUploadStatus().getSoftwareModule(); + if (sw != null) { + getFileSelected().remove( + new CustomFile(event.getUploadStatus().getFileName(), sw.getName(), sw.getVersion())); + } + // failed reason to be updated only if there is error other than + // duplicate file error + uploadInfoWindow.uploadFailed(event.getUploadStatus().getFileName(), event.getUploadStatus() + .getFailureReason(), event.getUploadStatus().getSoftwareModule()); + increaseNumberOfFileUploadsFailed(); + } + decreaseNumberOfFileUploadsExpected(); + updateUploadCounts(); + enableProcessBtn(); + // check if we are finished + if (isUploadComplete()) { + uploadInfoWindow.uploadSessionFinished(); + setUploadStatusButtonIconToFinished(); + } + displayDuplicateValidationMessage(); + } + + private void onUploadSuccess(UploadStatusEvent event) { + updateFileSize(event.getUploadStatus().getFileName(), event.getUploadStatus().getContentLength(), event + .getUploadStatus().getSoftwareModule()); + // recorded that we now one more uploaded + increaseNumberOfFilesActuallyUpload(); + } + + private void onUploadCompletion() { + // check if we are finished + if (isUploadComplete()) { + uploadInfoWindow.uploadSessionFinished(); + setUploadStatusButtonIconToFinished(); + displayDuplicateValidationMessage(); + } + updateUploadCounts(); + enableProcessBtn(); + } + + private boolean isUploadComplete() { + int uploadedCount = artifactUploadState.getNumberOfFilesActuallyUpload().intValue(); + int expectedUploadsCount = artifactUploadState.getNumberOfFileUploadsExpected().intValue(); + return uploadedCount == expectedUploadsCount; + } + + private void onUploadFailure(final UploadStatusEvent event) { + /** + * If upload interrupted because of duplicate file,do not remove the + * file already in upload list + **/ + if (getDuplicateFileNamesList().isEmpty() + || !getDuplicateFileNamesList().contains(event.getUploadStatus().getFileName())) { + final SoftwareModule sw = event.getUploadStatus().getSoftwareModule(); + if (sw != null) { + getFileSelected().remove( + new CustomFile(event.getUploadStatus().getFileName(), sw.getName(), sw.getVersion())); + } + // failed reason to be updated only if there is error other than + // duplicate file error + uploadInfoWindow.uploadFailed(event.getUploadStatus().getFileName(), event.getUploadStatus() + .getFailureReason(), event.getUploadStatus().getSoftwareModule()); + increaseNumberOfFileUploadsFailed(); + decreaseNumberOfFileUploadsExpected(); + } } /** @@ -637,8 +773,8 @@ public class UploadLayout extends VerticalLayout { * @param selectedBaseSoftwareModule */ public void refreshArtifactDetailsLayout(final SoftwareModule selectedBaseSoftwareModule) { - eventBus.publish(this, - new SoftwareModuleEvent(SoftwareModuleEventType.ARTIFACTS_CHANGED, selectedBaseSoftwareModule)); + eventBus.publish(this, new SoftwareModuleEvent(SoftwareModuleEventType.ARTIFACTS_CHANGED, + selectedBaseSoftwareModule)); } /** @@ -655,4 +791,79 @@ public class UploadLayout extends VerticalLayout { public void setHasDirectory(final Boolean hasDirectory) { this.hasDirectory = hasDirectory; } + + private void createUploadStatusButton() { + uploadStatusButton = SPUIComponentProvider.getButton(SPUIComponentIdProvider.UPLOAD_STATUS_BUTTON, "", "", "", + false, null, SPUIButtonStyleSmall.class); + uploadStatusButton.setStyleName(SPUIStyleDefinitions.ACTION_BUTTON); + uploadStatusButton.addStyleName(SPUIStyleDefinitions.UPLOAD_PROGRESS_INDICATOR_STYLE); + uploadStatusButton.setWidth("100px"); + uploadStatusButton.setHtmlContentAllowed(true); + uploadStatusButton.addClickListener(event -> onClickOfUploadStatusButton()); + uploadStatusButton.setVisible(false); + } + + void updateStatusButtonCount() { + int uploadsPending = artifactUploadState.getNumberOfFileUploadsExpected().get() + - artifactUploadState.getNumberOfFilesActuallyUpload().get(); + int uploadsFailed = artifactUploadState.getNumberOfFileUploadsFailed().get(); + StringBuilder builder = new StringBuilder(""); + if (uploadsFailed != 0) { + if (uploadsPending != 0) { + builder.append("
" + uploadsFailed + "
"); + } else { + builder.append("
" + uploadsFailed + "
"); + } + } + if (uploadsPending != 0) { + builder.append("
" + uploadsPending + "
"); + } + uploadStatusButton.setCaption(builder.toString()); + } + + private void onClickOfUploadStatusButton() { + artifactUploadState.setStatusPopupMinimized(false); + eventBus.publish(this, UploadArtifactUIEvent.MAXIMIZED_STATUS_POPUP); + } + + private void showUploadStatusButton() { + if (uploadStatusButton == null) { + return; + } + uploadStatusButton.setVisible(true); + updateStatusButtonCount(); + } + + protected void hideUploadStatusButton() { + if (uploadStatusButton == null) { + return; + } + uploadStatusButton.setVisible(false); + } + + private void maximizeStatusPopup() { + hideUploadStatusButton(); + uploadInfoWindow.maximizeStatusPopup(); + } + + private void setUploadStatusButtonIconToFinished() { + if (uploadStatusButton == null) { + return; + } + uploadStatusButton.removeStyleName(SPUIStyleDefinitions.UPLOAD_PROGRESS_INDICATOR_STYLE); + uploadStatusButton.setIcon(FontAwesome.UPLOAD); + } + + private void setUploadStatusButtonIconToInProgress() { + if (uploadStatusButton == null) { + return; + } + uploadStatusButton.addStyleName(SPUIStyleDefinitions.UPLOAD_PROGRESS_INDICATOR_STYLE); + uploadStatusButton.setIcon(null); + } + + protected void updateUploadCounts() { + updateActionCount(); + updateStatusButtonCount(); + } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadResultWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadResultWindow.java index c29548b82..f495c59aa 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadResultWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadResultWindow.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.ui.artifacts.upload; import java.util.ArrayList; import java.util.List; +import org.eclipse.hawkbit.ui.artifacts.event.UploadArtifactUIEvent; import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleTiny; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; @@ -19,6 +20,8 @@ import org.eclipse.hawkbit.ui.utils.SPUIComponentIdProvider; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; +import org.eclipse.hawkbit.ui.utils.SpringContextHelper; +import org.vaadin.spring.events.EventBus; import com.vaadin.data.Item; import com.vaadin.data.util.IndexedContainer; @@ -63,6 +66,9 @@ public class UploadResultWindow implements Button.ClickListener { private static final String UPLOAD_RESULT = "uploadResult"; private static final String REASON = "reason"; + + private transient EventBus.SessionEventBus eventBus; + /** * Initialize upload status popup. @@ -75,6 +81,7 @@ public class UploadResultWindow implements Button.ClickListener { public UploadResultWindow(final List uploadResultList, final I18N i18n) { this.uploadResultList = uploadResultList; this.i18n = i18n; + eventBus = SpringContextHelper.getBean( EventBus.SessionEventBus.class); createComponents(); createLayout(); } @@ -183,6 +190,8 @@ public class UploadResultWindow implements Button.ClickListener { if (event.getComponent().getId().equals(SPUIComponentIdProvider.UPLOAD_ARTIFACT_RESULT_CLOSE) || event.getComponent().getId().equals(SPUIComponentIdProvider.UPLOAD_ARTIFACT_RESULT_POPUP_CLOSE)) { uploadResultsWindow.close(); + //close upload status popup if open + eventBus.publish(this, UploadArtifactUIEvent.ARTIFACT_RESULT_POPUP_CLOSED); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadStatusInfoWindow.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadStatusInfoWindow.java index 8b36ef39e..a58c6ea14 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadStatusInfoWindow.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadStatusInfoWindow.java @@ -8,20 +8,49 @@ */ package org.eclipse.hawkbit.ui.artifacts.upload; -import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.ui.artifacts.event.UploadArtifactUIEvent; +import org.eclipse.hawkbit.ui.artifacts.event.UploadStatusEvent; +import org.eclipse.hawkbit.ui.artifacts.event.UploadStatusEvent.UploadStatusEventType; +import org.eclipse.hawkbit.ui.artifacts.state.ArtifactUploadState; +import org.eclipse.hawkbit.ui.common.ConfirmationDialog; +import org.eclipse.hawkbit.ui.components.SPUIComponentProvider; +import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleSmallNoBorder; +import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; +import org.eclipse.hawkbit.ui.utils.I18N; +import org.eclipse.hawkbit.ui.utils.SPUIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions; +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; +import org.springframework.beans.factory.annotation.Autowired; +import org.vaadin.spring.events.EventBus; +import org.vaadin.spring.events.EventScope; +import org.vaadin.spring.events.annotation.EventBusListenerMethod; + +import com.vaadin.data.Container.Indexed; import com.vaadin.data.Item; import com.vaadin.data.util.IndexedContainer; import com.vaadin.server.FontAwesome; import com.vaadin.shared.ui.window.WindowMode; import com.vaadin.spring.annotation.SpringComponent; import com.vaadin.spring.annotation.ViewScope; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Grid; import com.vaadin.ui.Grid.SelectionMode; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; import com.vaadin.ui.renderers.HtmlRenderer; import com.vaadin.ui.renderers.ProgressBarRenderer; +import com.vaadin.ui.themes.ValoTheme; import elemental.json.JsonValue; @@ -34,7 +63,16 @@ import elemental.json.JsonValue; @ViewScope @SpringComponent -public class UploadStatusInfoWindow extends Window implements Window.CloseListener, Window.WindowModeChangeListener { +public class UploadStatusInfoWindow extends Window { + + @Autowired + private transient EventBus.SessionEventBus eventBus; + + @Autowired + private ArtifactUploadState artifactUploadState; + + @Autowired + private I18N i18n; private static final String PROGRESS = "Progress"; @@ -46,52 +84,173 @@ public class UploadStatusInfoWindow extends Window implements Window.CloseListen private static final long serialVersionUID = 1L; - private final Grid grid; + private Grid grid; - private final IndexedContainer uploads; + private IndexedContainer uploads; private volatile boolean errorOccured = false; + private volatile boolean uploadAborted = false; + + private Button minimizeButton; + + private VerticalLayout mainLayout; + + private Label windowCaption; + + private Button closeButton; + + private Button resizeButton; + + private UI ui; + + private ConfirmationDialog confirmDialog; + /** * Default Constructor. */ - UploadStatusInfoWindow() { - super("Upload Status"); + @PostConstruct + void init() { - addStyleName(SPUIStyleDefinitions.UPLOAD_INFO); - center(); - setImmediate(true); - setResizable(true); - setDraggable(true); - setClosable(true); - uploads = new IndexedContainer(); - uploads.addContainerProperty(STATUS, String.class, "Active"); - uploads.addContainerProperty(FILE_NAME, String.class, null); - uploads.addContainerProperty(PROGRESS, Double.class, 0D); - uploads.addContainerProperty(REASON, String.class, ""); + setPopupProperties(); + createStatusPopupHeaderComponents(); - grid = new Grid(uploads); - grid.addStyleName(SPUIStyleDefinitions.UPLOAD_STATUS_GRID); - grid.setSelectionMode(SelectionMode.NONE); - grid.getColumn(STATUS).setRenderer(new StatusRenderer()); - grid.getColumn(PROGRESS).setRenderer(new ProgressBarRenderer()); - setColumnWidth(); - grid.setFrozenColumnCount(4); - grid.setColumnOrder(STATUS, PROGRESS, FILE_NAME, REASON); - grid.setHeaderVisible(true); - grid.setImmediate(true); + mainLayout = new VerticalLayout(); + mainLayout.setSpacing(Boolean.TRUE); + mainLayout.setSizeUndefined(); setPopupSizeInMinMode(); - setContent(grid); - addCloseListener(this); - addWindowModeChangeListener(this); + uploads = getGridContainer(); + grid = createGrid(); + setGridColumnProperties(); + + mainLayout.addComponents(getCaptionLayout(), grid); + mainLayout.setExpandRatio(grid, 1.0F); + setContent(mainLayout); + eventBus.subscribe(this); + ui = UI.getCurrent(); + + createConfirmDialog(); + } + + @EventBusListenerMethod(scope = EventScope.SESSION) + void onEvent(final UploadStatusEvent event) { + if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_IN_PROGRESS) { + UI.getCurrent().access( + () -> updateProgress(event.getUploadStatus().getFileName(), event.getUploadStatus().getBytesRead(), + event.getUploadStatus().getContentLength(), event.getUploadStatus().getSoftwareModule())); + } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_STARTED) { + UI.getCurrent().access(() -> onStartOfUpload(event)); + } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_STREAMING_FAILED) { + ui.access(() -> uploadFailed(event.getUploadStatus().getFileName(), event.getUploadStatus() + .getFailureReason(), event.getUploadStatus().getSoftwareModule())); + } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_SUCCESSFUL) { + UI.getCurrent().access( + () -> uploadSucceeded(event.getUploadStatus().getFileName(), event.getUploadStatus() + .getSoftwareModule())); + } else if (event.getUploadProgressEventType() == UploadStatusEventType.UPLOAD_STREAMING_FINISHED) { + ui.access(() -> uploadSucceeded(event.getUploadStatus().getFileName(), event.getUploadStatus() + .getSoftwareModule())); + } + } + + private void onStartOfUpload(UploadStatusEvent event) { + uploadSessionStarted(); + uploadStarted(event.getUploadStatus().getFileName(), event.getUploadStatus().getSoftwareModule()); + } + + @PreDestroy + void destroy() { + /* + * It's good manners to do this, even though vaadin-spring will + * automatically unsubscribe when this UI is garbage collected. + */ + eventBus.unsubscribe(this); + } + + private void restoreState() { + Indexed container = grid.getContainerDataSource(); + if (container.getItemIds().isEmpty()) { + container.removeAllItems(); + for (UploadStatusObject statusObject : artifactUploadState.getUploadedFileStatusList()) { + Item item = container.addItem(getItemid(statusObject.getFilename(), + statusObject.getSelectedSoftwareModule())); + item.getItemProperty(REASON).setValue(statusObject.getReason() != null ? statusObject.getReason() : ""); + item.getItemProperty(STATUS).setValue(statusObject.getStatus()); + item.getItemProperty(PROGRESS).setValue(statusObject.getProgress()); + item.getItemProperty(FILE_NAME).setValue(statusObject.getFilename()); + SoftwareModule sw = statusObject.getSelectedSoftwareModule(); + item.getItemProperty(SPUILabelDefinitions.NAME_VERSION).setValue( + HawkbitCommonUtil.getFormattedNameVersion(sw.getName(), sw.getVersion())); + } + if (artifactUploadState.isUploadCompleted()) { + minimizeButton.setEnabled(false); + } + } + } + + private void setPopupProperties() { + setId(SPUIComponentIdProvider.UPLOAD_STATUS_POPUP_ID); + addStyleName(SPUIStyleDefinitions.UPLOAD_INFO); + setImmediate(true); + setResizable(false); + setDraggable(true); + setClosable(false); + setModal(true); + } + + private void setGridColumnProperties() { + grid.getColumn(STATUS).setRenderer(new StatusRenderer()); + grid.getColumn(PROGRESS).setRenderer(new ProgressBarRenderer()); + grid.setColumnOrder(STATUS, PROGRESS, FILE_NAME, SPUILabelDefinitions.NAME_VERSION, REASON); + setColumnWidth(); + grid.getColumn(SPUILabelDefinitions.NAME_VERSION).setHeaderCaption(i18n.get("upload.swModuleTable.header")); + grid.setFrozenColumnCount(5); + } + + private Grid createGrid() { + Grid statusGrid = new Grid(uploads); + statusGrid.addStyleName(SPUIStyleDefinitions.UPLOAD_STATUS_GRID); + statusGrid.setSelectionMode(SelectionMode.NONE); + statusGrid.setHeaderVisible(true); + statusGrid.setImmediate(true); + statusGrid.setSizeFull(); + return statusGrid; + } + + private IndexedContainer getGridContainer() { + IndexedContainer uploadContainer = new IndexedContainer(); + uploadContainer.addContainerProperty(STATUS, String.class, "Active"); + uploadContainer.addContainerProperty(FILE_NAME, String.class, null); + uploadContainer.addContainerProperty(PROGRESS, Double.class, 0D); + uploadContainer.addContainerProperty(REASON, String.class, ""); + uploadContainer.addContainerProperty(SPUILabelDefinitions.NAME_VERSION, String.class, ""); + return uploadContainer; + } + + private HorizontalLayout getCaptionLayout() { + final HorizontalLayout captionLayout = new HorizontalLayout(); + captionLayout.setSizeFull(); + captionLayout.setHeight("36px"); + captionLayout.addComponents(windowCaption, minimizeButton, resizeButton, closeButton); + captionLayout.setExpandRatio(windowCaption, 1.0F); + captionLayout.addStyleName("v-window-header"); + return captionLayout; + } + + private void createStatusPopupHeaderComponents() { + minimizeButton = getMinimizeButton(); + windowCaption = new Label("Upload status"); + closeButton = getCloseButton(); + resizeButton = getResizeButton(); } private void setColumnWidth() { - grid.getColumn(STATUS).setWidth(70); + grid.getColumn(STATUS).setWidth(60); grid.getColumn(PROGRESS).setWidth(150); - grid.getColumn(FILE_NAME).setWidth(280); - grid.getColumn(REASON).setWidth(300); + grid.getColumn(FILE_NAME).setWidth(200); + grid.getColumn(REASON).setWidth(290); + grid.getColumn(SPUILabelDefinitions.NAME_VERSION).setWidth(200); } private void resetColumnWidth() { @@ -99,15 +258,16 @@ public class UploadStatusInfoWindow extends Window implements Window.CloseListen grid.getColumn(PROGRESS).setWidthUndefined(); grid.getColumn(FILE_NAME).setWidthUndefined(); grid.getColumn(REASON).setWidthUndefined(); - + grid.getColumn(SPUILabelDefinitions.NAME_VERSION).setWidthUndefined(); } private static class StatusRenderer extends HtmlRenderer { + private static final long serialVersionUID = -5365795450234970943L; + @Override public JsonValue encode(final String value) { - - String result = ""; + String result; switch (value) { case "Finished": result = "
" + FontAwesome.CHECK_CIRCLE.getHtml() + "
"; @@ -127,30 +287,66 @@ public class UploadStatusInfoWindow extends Window implements Window.CloseListen * Automatically close if not error has occured. */ void uploadSessionFinished() { - if (!errorOccured) { - close(); + uploadAborted = false; + if (!errorOccured && !artifactUploadState.isStatusPopupMinimized()) { + clearWindow(); } - + artifactUploadState.setUploadCompleted(true); + minimizeButton.setEnabled(false); + closeButton.setEnabled(true); + confirmDialog.getWindow().close(); + UI.getCurrent().removeWindow(confirmDialog.getWindow()); } void uploadSessionStarted() { - close(); + if (artifactUploadState.getNumberOfFilesActuallyUpload().intValue() == 0 + && artifactUploadState.getNumberOfFileUploadsFailed().intValue() == 0 + && !artifactUploadState.isStatusPopupMinimized()) { + openWindow(); + } + if (!uploadAborted) { + minimizeButton.setEnabled(true); + closeButton.setEnabled(true); + artifactUploadState.setUploadCompleted(false); + } + } + + void openWindow() { UI.getCurrent().addWindow(this); center(); } - void uploadStarted(final String filename) { - final Item item = uploads.addItem(filename); - item.getItemProperty(FILE_NAME).setValue(filename); - grid.scrollToEnd(); - + void maximizeStatusPopup() { + openWindow(); + restoreState(); } - void updateProgress(final String filename, final long readBytes, final long contentLength) { - final Item item = uploads.getItem(filename); + void uploadStarted(final String filename, final SoftwareModule softwareModule) { + final Item item = uploads.addItem(getItemid(filename, softwareModule)); if (item != null) { - item.getItemProperty(PROGRESS).setValue((double) readBytes / (double) contentLength); + item.getItemProperty(FILE_NAME).setValue(filename); + item.getItemProperty(SPUILabelDefinitions.NAME_VERSION).setValue( + HawkbitCommonUtil.getFormattedNameVersion(softwareModule.getName(), softwareModule.getVersion())); + } + grid.scrollToEnd(); + UploadStatusObject uploadStatus = new UploadStatusObject(filename, softwareModule); + uploadStatus.setStatus("Active"); + artifactUploadState.getUploadedFileStatusList().add(uploadStatus); + } + void updateProgress(final String filename, final long readBytes, final long contentLength, + final SoftwareModule softwareModule) { + final Item item = uploads.getItem(getItemid(filename, softwareModule)); + double progress = (double) readBytes / (double) contentLength; + if (item != null) { + item.getItemProperty(PROGRESS).setValue(progress); + } + List uploadStatusObjectList = (List) artifactUploadState + .getUploadedFileStatusList().stream().filter(e -> e.getFilename().equals(filename)) + .collect(Collectors.toList()); + if (!uploadStatusObjectList.isEmpty()) { + UploadStatusObject uploadStatusObject = uploadStatusObjectList.get(0); + uploadStatusObject.setProgress(progress); } } @@ -159,67 +355,142 @@ public class UploadStatusInfoWindow extends Window implements Window.CloseListen * * @param filename * of the uploaded file. + * @param softwareModule + * selected software module */ - public void uploadSucceeded(final String filename) { - final Item item = uploads.getItem(filename); + public void uploadSucceeded(final String filename, SoftwareModule softwareModule) { + final Item item = uploads.getItem(getItemid(filename, softwareModule)); + String status = "Finished"; if (item != null) { - item.getItemProperty(STATUS).setValue("Finished"); - + item.getItemProperty(STATUS).setValue(status); + } + List uploadStatusObjectList = (List) artifactUploadState + .getUploadedFileStatusList().stream().filter(e -> e.getFilename().equals(filename)) + .collect(Collectors.toList()); + if (!uploadStatusObjectList.isEmpty()) { + UploadStatusObject uploadStatusObject = uploadStatusObjectList.get(0); + uploadStatusObject.setStatus(status); + uploadStatusObject.setProgress(1d); } } - void uploadFailed(final String filename, final String errorReason) { - final Item item = uploads.getItem(filename); + void uploadFailed(final String filename, final String errorReason, SoftwareModule softwareModule) { + errorOccured = true; + String status = "Failed"; + final Item item = uploads.getItem(getItemid(filename, softwareModule)); if (item != null) { - if (!errorOccured) { - errorOccured = true; - } item.getItemProperty(REASON).setValue(errorReason); - item.getItemProperty(STATUS).setValue("Failed"); - + item.getItemProperty(STATUS).setValue(status); + } + List uploadStatusObjectList = (List) artifactUploadState + .getUploadedFileStatusList().stream().filter(e -> e.getFilename().equals(filename)) + .collect(Collectors.toList()); + if (!uploadStatusObjectList.isEmpty()) { + UploadStatusObject uploadStatusObject = uploadStatusObjectList.get(0); + uploadStatusObject.setStatus(status); + uploadStatusObject.setReason(errorReason); } } - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.Window.CloseListener#windowClose(com.vaadin.ui.Window. - * CloseEvent) - */ - @Override - public void windowClose(final CloseEvent e) { - clearWindow(); - } - - private void clearWindow() { + protected void clearWindow() { errorOccured = false; uploads.removeAllItems(); setWindowMode(WindowMode.NORMAL); + setColumnWidth(); + setPopupSizeInMinMode(); + resizeButton.setIcon(FontAwesome.EXPAND); + this.close(); + artifactUploadState.getUploadedFileStatusList().clear(); + artifactUploadState.getNumberOfFileUploadsFailed().set(0); } - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.Window.WindowModeChangeListener#windowModeChanged(com. - * vaadin.ui.Window. WindowModeChangeEvent) - */ - @Override - public void windowModeChanged(final WindowModeChangeEvent event) { - if (event.getWindow().getWindowMode() == WindowMode.MAXIMIZED) { + private void setPopupSizeInMinMode() { + mainLayout.setWidth(900, Unit.PIXELS); + mainLayout.setHeight(510, Unit.PIXELS); + } + + private Button getMinimizeButton() { + final Button minimizeBtn = SPUIComponentProvider.getButton( + SPUIComponentIdProvider.UPLOAD_STATUS_POPUP_MINIMIZE_BUTTON_ID, "", "", "", true, FontAwesome.MINUS, + SPUIButtonStyleSmallNoBorder.class); + minimizeBtn.addStyleName(ValoTheme.BUTTON_BORDERLESS); + minimizeBtn.addClickListener(event -> minimizeWindow()); + minimizeBtn.setEnabled(true); + return minimizeBtn; + } + + private Button getResizeButton() { + final Button resizeBtn = SPUIComponentProvider.getButton( + SPUIComponentIdProvider.UPLOAD_STATUS_POPUP_RESIZE_BUTTON_ID, "", "", "", true, FontAwesome.EXPAND, + SPUIButtonStyleSmallNoBorder.class); + resizeBtn.addStyleName(ValoTheme.BUTTON_BORDERLESS); + resizeBtn.addClickListener(event -> resizeWindow(event)); + return resizeBtn; + } + + private void resizeWindow(ClickEvent event) { + if (event.getButton().getIcon() == FontAwesome.EXPAND) { + event.getButton().setIcon(FontAwesome.COMPRESS); + setWindowMode(WindowMode.MAXIMIZED); resetColumnWidth(); grid.getColumn(STATUS).setExpandRatio(0); grid.getColumn(PROGRESS).setExpandRatio(1); grid.getColumn(FILE_NAME).setExpandRatio(2); grid.getColumn(REASON).setExpandRatio(3); - grid.setSizeFull(); + grid.getColumn(SPUILabelDefinitions.NAME_VERSION).setExpandRatio(4); + mainLayout.setSizeFull(); } else { + event.getButton().setIcon(FontAwesome.EXPAND); + setWindowMode(WindowMode.NORMAL); setColumnWidth(); setPopupSizeInMinMode(); } } - private void setPopupSizeInMinMode() { - grid.setWidth(800, Unit.PIXELS); - grid.setHeight(510, Unit.PIXELS); + private void minimizeWindow() { + this.close(); + artifactUploadState.setStatusPopupMinimized(true); + eventBus.publish(this, UploadArtifactUIEvent.MINIMIZED_STATUS_POPUP); + } + + private Button getCloseButton() { + final Button closeBtn = SPUIComponentProvider.getButton( + SPUIComponentIdProvider.UPLOAD_STATUS_POPUP_CLOSE_BUTTON_ID, "", "", "", true, FontAwesome.TIMES, + SPUIButtonStyleSmallNoBorder.class); + closeBtn.addStyleName(ValoTheme.BUTTON_BORDERLESS); + closeBtn.addClickListener(event -> onClose()); + return closeBtn; + } + + private void onClose() { + if (!artifactUploadState.isUploadCompleted()) { + confirmAbortAction(); + } else { + clearWindow(); + } + } + + private void confirmAbortAction() { + UI.getCurrent().addWindow(confirmDialog.getWindow()); + confirmDialog.getWindow().bringToFront(); + } + + private void createConfirmDialog() { + confirmDialog = new ConfirmationDialog(i18n.get("caption.confirm.abort.action"), + i18n.get("message.abort.upload"), i18n.get("button.ok"), i18n.get("button.cancel"), ok -> { + if (ok) { + eventBus.publish(this, UploadStatusEventType.ABORT_UPLOAD); + uploadAborted = true; + errorOccured = true; + minimizeButton.setEnabled(false); + closeButton.setEnabled(false); + } + }); + } + + private String getItemid(final String filename, final SoftwareModule softwareModule) { + return new StringBuilder(filename).append( + HawkbitCommonUtil.getFormattedNameVersion(softwareModule.getName(), softwareModule.getVersion())) + .toString(); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadStatusObject.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadStatusObject.java new file mode 100644 index 000000000..4ea82d7dd --- /dev/null +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/UploadStatusObject.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.ui.artifacts.upload; + +import org.eclipse.hawkbit.repository.model.SoftwareModule; + +/** + * + * Holds uploaded file status.Used to display the details in upload status + * popup. + * + */ +public class UploadStatusObject { + private String status; + private Double progress; + private String filename; + private String reason; + private SoftwareModule selectedSoftwareModule; + + public UploadStatusObject(final String status, final Double progress, final String fileName, final String reason, + final SoftwareModule selectedSoftwareModule) { + this(fileName,selectedSoftwareModule); + this.status = status; + this.progress = progress; + this.reason = reason; + } + + public UploadStatusObject(String fileName, SoftwareModule selectedSoftwareModule) { + this.filename = fileName; + this.selectedSoftwareModule = selectedSoftwareModule; + } + + public SoftwareModule getSelectedSoftwareModule() { + return selectedSoftwareModule; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public Double getProgress() { + return progress; + } + + public void setProgress(Double progress) { + this.progress = progress; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + @Override + public boolean equals(Object obj) { + if (this == null || obj == null) { + return false; + } + if (obj instanceof UploadStatusObject && this.getFilename() == ((UploadStatusObject) obj).getFilename()) { + return true; + } + return false; + } + +} diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/footer/AbstractDeleteActionsLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/footer/AbstractDeleteActionsLayout.java index 079913d90..b324f0325 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/footer/AbstractDeleteActionsLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/footer/AbstractDeleteActionsLayout.java @@ -130,7 +130,7 @@ public abstract class AbstractDeleteActionsLayout extends VerticalLayout impleme addComponent(hLayout); setComponentAlignment(hLayout, Alignment.BOTTOM_CENTER); } - setStyleName("footer-layout"); + setStyleName(SPUIStyleDefinitions.FOOTER_LAYOUT); setWidth("100%"); } @@ -168,7 +168,7 @@ public abstract class AbstractDeleteActionsLayout extends VerticalLayout impleme final Button button = SPUIComponentProvider.getButton(SPUIComponentIdProvider.BULK_UPLOAD_STATUS_BUTTON, "", "", "", false, null, SPUIButtonStyleSmall.class); button.setStyleName(SPUIStyleDefinitions.ACTION_BUTTON); - button.addStyleName(SPUIStyleDefinitions.BULK_UPLOAD_PROGRESS_INDICATOR_STYLE); + button.addStyleName(SPUIStyleDefinitions.UPLOAD_PROGRESS_INDICATOR_STYLE); button.setWidth("100px"); button.setHtmlContentAllowed(true); button.addClickListener(event -> onClickBulkUploadNotificationButton()); @@ -199,7 +199,7 @@ public abstract class AbstractDeleteActionsLayout extends VerticalLayout impleme if (bulkUploadStatusButton == null) { return; } - bulkUploadStatusButton.removeStyleName(SPUIStyleDefinitions.BULK_UPLOAD_PROGRESS_INDICATOR_STYLE); + bulkUploadStatusButton.removeStyleName(SPUIStyleDefinitions.UPLOAD_PROGRESS_INDICATOR_STYLE); bulkUploadStatusButton.setIcon(FontAwesome.UPLOAD); } @@ -207,7 +207,7 @@ public abstract class AbstractDeleteActionsLayout extends VerticalLayout impleme if (bulkUploadStatusButton == null) { return; } - bulkUploadStatusButton.addStyleName(SPUIStyleDefinitions.BULK_UPLOAD_PROGRESS_INDICATOR_STYLE); + bulkUploadStatusButton.addStyleName(SPUIStyleDefinitions.UPLOAD_PROGRESS_INDICATOR_STYLE); bulkUploadStatusButton.setIcon(null); } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridLayout.java index 997dbdd96..b07b927f0 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/grid/AbstractGridLayout.java @@ -9,6 +9,7 @@ package org.eclipse.hawkbit.ui.common.grid; import org.eclipse.hawkbit.ui.utils.SPUIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import com.vaadin.ui.Alignment; import com.vaadin.ui.HorizontalLayout; @@ -72,7 +73,7 @@ public abstract class AbstractGridLayout extends VerticalLayout { final Label countMessageLabel = getCountMessageLabel(); countMessageLabel.setId(SPUIComponentIdProvider.ROLLOUT_GROUP_TARGET_LABEL); rolloutGroupTargetsCountLayout.addComponent(getCountMessageLabel()); - rolloutGroupTargetsCountLayout.setStyleName("footer-layout"); + rolloutGroupTargetsCountLayout.setStyleName(SPUIStyleDefinitions.FOOTER_LAYOUT); rolloutGroupTargetsCountLayout.setWidth("100%"); return rolloutGroupTargetsCountLayout; diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterManagementView.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterManagementView.java index ef759aa5f..40e6306b0 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterManagementView.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/FilterManagementView.java @@ -14,6 +14,7 @@ import org.eclipse.hawkbit.ui.HawkbitUI; import org.eclipse.hawkbit.ui.filtermanagement.event.CustomFilterUIEvent; import org.eclipse.hawkbit.ui.filtermanagement.footer.TargetFilterCountMessageLabel; import org.eclipse.hawkbit.ui.filtermanagement.state.FilterManagementUIState; +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; import org.springframework.beans.factory.annotation.Autowired; import org.vaadin.spring.events.EventBus; import org.vaadin.spring.events.EventScope; @@ -152,7 +153,7 @@ public class FilterManagementView extends VerticalLayout implements View { private HorizontalLayout addTargetFilterMessageLabel() { final HorizontalLayout messageLabelLayout = new HorizontalLayout(); messageLabelLayout.addComponent(targetFilterCountMessageLabel); - messageLabelLayout.addStyleName("footer-layout"); + messageLabelLayout.addStyleName(SPUIStyleDefinitions.FOOTER_LAYOUT); return messageLabelLayout; } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIComponentIdProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIComponentIdProvider.java index 17c215b0a..d5fcaa265 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIComponentIdProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIComponentIdProvider.java @@ -898,6 +898,30 @@ public final class SPUIComponentIdProvider { */ public static final String VALIDATION_STATUS_ICON_ID = "validation.status.icon"; + /** + * Artifact upload status popup - minimize button id. + */ + public static final String UPLOAD_STATUS_POPUP_MINIMIZE_BUTTON_ID = "artifact.upload.minimize.button.id"; + + /** + * Artifact upload status popup - close button id. + */ + public static final String UPLOAD_STATUS_POPUP_CLOSE_BUTTON_ID = "artifact.upload.close.button.id"; + + /** + * Artifact upload status popup - resize button id. + */ + public static final String UPLOAD_STATUS_POPUP_RESIZE_BUTTON_ID = "artifact.upload.resize.button.id"; + + /** + * Artifact upload view - upload status button id. + */ + public static final String UPLOAD_STATUS_BUTTON = "artficat.upload.status.button.id"; + + /** + * Artifact uplaod view - uplod status popup id. + */ + public static final String UPLOAD_STATUS_POPUP_ID = "artifact.upload.status.popup.id"; /** * /* Private Constructor. */ diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIStyleDefinitions.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIStyleDefinitions.java index c50135251..ac713a07c 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIStyleDefinitions.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/SPUIStyleDefinitions.java @@ -240,9 +240,9 @@ public final class SPUIStyleDefinitions { public static final String DISABLE_ACTION_TYPE_LAYOUT = "disable-action-type-layout"; /** - * Bulk upload progress indicator style. + * Upload progress indicator style. */ - public static final String BULK_UPLOAD_PROGRESS_INDICATOR_STYLE = "app-loading"; + public static final String UPLOAD_PROGRESS_INDICATOR_STYLE = "app-loading"; /** * Target filter search progress indicator style. @@ -293,6 +293,11 @@ public final class SPUIStyleDefinitions { * Status pending icon. */ public static final String STATUS_ICON_PENDING = "statusIconPending"; + + /** + * Footer layout style. + */ + public static final String FOOTER_LAYOUT = "footer-layout"; /** * Constructor. diff --git a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/others.scss b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/others.scss index d327baa94..69dacfa57 100644 --- a/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/others.scss +++ b/hawkbit-ui/src/main/resources/VAADIN/themes/hawkbit/customstyles/others.scss @@ -10,7 +10,7 @@ @mixin others { //Style to display the pending action count - .unread { + .unread, .error-count { @include valo-badge-style; position: absolute; pointer-events: none; @@ -21,6 +21,15 @@ border-radius: $v-border-radius; color: $widget-bg; } + + .error-count { + top: round($v-unit-size / -5); + right: round($v-unit-size / 1.9); + } + + .error-count-color{ + @include valo-gradient($color: $red-color); + } //Deployment view - Style of count message .v-caption-count-msg-box { diff --git a/hawkbit-ui/src/main/resources/messages.properties b/hawkbit-ui/src/main/resources/messages.properties index 4fdbe7d02..1fee8dcdb 100644 --- a/hawkbit-ui/src/main/resources/messages.properties +++ b/hawkbit-ui/src/main/resources/messages.properties @@ -86,6 +86,7 @@ caption.cancel.action.confirmbox = Confirm action cancel caption.forcequit.action.confirmbox = Confirm force quit action caption.forced.datefield = Force update at time caption.force.action.confirmbox = Confirm Force Active Action +caption.confirm.abort.action = Confirm abort action caption.filter.delete.confirmbox = Confirm Filter Delete Action @@ -321,8 +322,11 @@ message.duplicate.filename = Duplicate file name message.swModule.deleted = {0} Software module(s) deleted message.upload.failed = Streaming Failed message.uploadedfile.size.exceeded = File size exceeded .Allowed size {0} bytes +message.uploadedfile.aborted = File upload aborted message.file.not.found = File not found message.artifact.deleted =Artifact with file {0} deleted successfully +message.abort.upload = Are you sure that you want to abort the upload? + upload.swModuleTable.header = Software module diff --git a/hawkbit-ui/src/main/resources/messages_de.properties b/hawkbit-ui/src/main/resources/messages_de.properties index 3a75ba61f..1c437071a 100644 --- a/hawkbit-ui/src/main/resources/messages_de.properties +++ b/hawkbit-ui/src/main/resources/messages_de.properties @@ -85,8 +85,9 @@ caption.forced.datefield = Force update at time caption.force.action.confirmbox = Confirm Force Active Action caption.filter.simple = Simple Filter caption.filter.custom = Custom Filter - caption.filter.delete.confirmbox = Confirm Filter Delete Action +caption.confirm.abort.action = Confirm abort action + # Labels prefix with - label label.dist.details.type = Type : @@ -319,8 +320,11 @@ message.swModule.deleted = {0} Software module(s) deleted message.error.missing.tagname = Please select tag name message.upload.failed = Streaming Failed message.uploadedfile.size.exceeded = File size exceeded .Allowed size {0} bytes +message.uploadedfile.aborted = File upload aborted message.file.not.found = File not found message.artifact.deleted =Artifact with file {0} deleted successfully +message.abort.upload = Are you sure that you want to abort the upload? + upload.swModuleTable.header = Software module diff --git a/hawkbit-ui/src/main/resources/messages_en.properties b/hawkbit-ui/src/main/resources/messages_en.properties index d87ac238b..7716da4f2 100644 --- a/hawkbit-ui/src/main/resources/messages_en.properties +++ b/hawkbit-ui/src/main/resources/messages_en.properties @@ -85,8 +85,8 @@ caption.soft.delete.confirmbox = Confirm Software Module Delete Action caption.cancel.action.confirmbox = Confirm action cancellation caption.forced.datefield = Force update at time caption.force.action.confirmbox = Confirm Force Active Action - caption.filter.delete.confirmbox = Confirm Filter Delete Action +caption.confirm.abort.action = Confirm abort action # Labels prefix with - label label.dist.details.type = Type : @@ -313,7 +313,10 @@ message.duplicate.filename = Duplicate file name message.swModule.deleted = {0} Software module(s) deleted message.upload.failed = Streaming Failed message.uploadedfile.size.exceeded = File size exceeded .Allowed size {0} bytes +message.uploadedfile.aborted = File upload aborted message.file.not.found = File not found +message.abort.upload = Are you sure that you want to abort the upload? + upload.swModuleTable.header = Software module upload.selectedfile.name = file selected for upload diff --git a/pom.xml b/pom.xml index 0e4fec71d..49ef805b3 100644 --- a/pom.xml +++ b/pom.xml @@ -168,6 +168,8 @@ eclipse_codeformatter.xml **/addons.scss **/VAADIN/widgetsets/** + .sonar + **/.sonar/** JAVADOC_STYLE