Browse Source

Replace stub check detail description with info about check duration and count of warnings and critical errors

Armin Schrenk 3 years ago
parent
commit
186c129a30

+ 39 - 0
main/ui/src/main/java/org/cryptomator/ui/controls/FormattedLabel2.java

@@ -0,0 +1,39 @@
+package org.cryptomator.ui.controls;
+
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.StringBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+
+public class FormattedLabel2 extends FormattedLabel {
+
+	private final ObjectProperty<Object> arg2 = new SimpleObjectProperty<>();
+
+	public FormattedLabel2() {
+		textProperty().unbind();
+		textProperty().bind(createStringBinding2());
+	}
+
+	private StringBinding createStringBinding2() {
+		return Bindings.createStringBinding(this::updateText, formatProperty(), arg1Property(), arg2);
+	}
+
+	private String updateText() {
+		return String.format(getFormat(), getArg1(), arg2.get());
+	}
+
+	/* Getter & Setter */
+
+	public ObjectProperty<Object> arg2Property() {
+		return arg2;
+	}
+
+	public Object getArg2() {
+		return arg2.get();
+	}
+
+	public void setArg2(Object arg2) {
+		this.arg2.set(arg2);
+	}
+
+}

+ 86 - 2
main/ui/src/main/java/org/cryptomator/ui/health/CheckDetailController.java

@@ -8,37 +8,85 @@ import org.cryptomator.ui.common.FxController;
 import javax.inject.Inject;
 import javafx.beans.binding.Binding;
 import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.LongProperty;
 import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleLongProperty;
+import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
 import javafx.collections.ObservableList;
 import javafx.concurrent.Worker;
 import javafx.fxml.FXML;
 import javafx.scene.control.ListView;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
 
 @HealthCheckScoped
 public class CheckDetailController implements FxController {
 
+	private final Map<ObservableList<DiagnosticResult>, WarnAndErrorEntry> cachedWarnAndErrorCounts;
 	private final Binding<ObservableList<DiagnosticResult>> results;
 	private final OptionalBinding<Worker.State> taskState;
 	private final Binding<String> taskName;
+	private final Binding<Number> taskDuration;
 	private final ResultListCellFactory resultListCellFactory;
 	private final BooleanBinding producingResults;
+	private final LongProperty numOfWarnings;
+	private final LongProperty numOfErrors;
 
 	public ListView<DiagnosticResult> resultsListView;
 
 	@Inject
 	public CheckDetailController(ObjectProperty<HealthCheckTask> selectedTask, ResultListCellFactory resultListCellFactory) {
+		selectedTask.addListener(this::rebindWarnAndErrorCount);
 		this.results = EasyBind.wrapNullable(selectedTask).map(HealthCheckTask::results).orElse(FXCollections.emptyObservableList());
 		this.taskState = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::stateProperty);
 		this.taskName = EasyBind.wrapNullable(selectedTask).map(HealthCheckTask::getTitle).orElse("");
+		this.taskDuration = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::durationInMillisProperty).orElse(-1L);
 		this.resultListCellFactory = resultListCellFactory;
 		this.producingResults = taskState.filter(this::producesResults).isPresent();
+		this.numOfWarnings = new SimpleLongProperty(0);
+		this.numOfErrors = new SimpleLongProperty(0);
+		this.cachedWarnAndErrorCounts = new IdentityHashMap<>(); //important to use an identity hashmap, because collections violate the immnutable hashkey contract
+	}
+
+	private synchronized void rebindWarnAndErrorCount(ObservableValue<? extends HealthCheckTask> observable, HealthCheckTask oldVal, HealthCheckTask newVal) {
+		//create and cache properites for the newList, if not already present
+		final var listToUpdate = newVal.results();
+		cachedWarnAndErrorCounts.computeIfAbsent(listToUpdate, key -> {
+			var warnProperty = new SimpleLongProperty(countSeverityInList(listToUpdate, DiagnosticResult.Severity.WARN));
+			var errProperty = new SimpleLongProperty(countSeverityInList(listToUpdate, DiagnosticResult.Severity.CRITICAL));
+			return new WarnAndErrorEntry(warnProperty, errProperty);
+		});
+		listToUpdate.addListener(this::updateListSpecificWarnAndErrorCount);
+
+		//updateBindings
+		numOfErrors.bind(cachedWarnAndErrorCounts.get(listToUpdate).errorCount);
+		numOfWarnings.bind(cachedWarnAndErrorCounts.get(listToUpdate).warningCount);
+	}
+
+	private synchronized void updateListSpecificWarnAndErrorCount(ListChangeListener.Change<? extends DiagnosticResult> c) {
+		long tmpErr = cachedWarnAndErrorCounts.get(c.getList()).errorCount.get();
+		long tmpWarn = cachedWarnAndErrorCounts.get(c.getList()).warningCount.get();
+		while (c.next()) {
+			if (c.wasAdded()) {
+				tmpWarn += countSeverityInList(c.getAddedSubList(), DiagnosticResult.Severity.WARN);
+				tmpErr += countSeverityInList(c.getAddedSubList(), DiagnosticResult.Severity.CRITICAL);
+			}
+		}
+		cachedWarnAndErrorCounts.get(c.getList()).errorCount.set(tmpErr);
+		cachedWarnAndErrorCounts.get(c.getList()).warningCount.set(tmpWarn);
+	}
+
+	private long countSeverityInList(List<? extends DiagnosticResult> list, DiagnosticResult.Severity severityToCount) {
+		return list.stream().map(DiagnosticResult::getServerity).filter(severityToCount::equals).count();
 	}
 
 	private boolean producesResults(Worker.State state) {
 		return switch (state) {
-			case SCHEDULED, RUNNING, SUCCEEDED -> true;
-			case READY, CANCELLED, FAILED -> false;
+			case SCHEDULED, RUNNING -> true;
+			case READY, SUCCEEDED, CANCELLED, FAILED -> false;
 		};
 	}
 
@@ -65,4 +113,40 @@ public class CheckDetailController implements FxController {
 	public BooleanBinding producingResultsProperty() {
 		return producingResults;
 	}
+
+	public Number getTaskDuration() {
+		return taskDuration.getValue();
+	}
+
+	public Binding<Number> taskDurationProperty() {
+		return taskDuration;
+	}
+
+	public long getNumOfWarnings() {
+		return numOfWarnings.get();
+	}
+
+	public LongProperty numOfWarningsProperty() {
+		return numOfWarnings;
+	}
+
+	public long getNumOfErrors() {
+		return numOfErrors.get();
+	}
+
+	public LongProperty numOfErrorsProperty() {
+		return numOfErrors;
+	}
+
+	private static class WarnAndErrorEntry {
+
+		WarnAndErrorEntry(LongProperty warningCount, LongProperty errorCount) {
+			this.warningCount = warningCount;
+			this.errorCount = errorCount;
+		}
+
+		final LongProperty warningCount;
+		final LongProperty errorCount;
+	}
+
 }

+ 16 - 0
main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java

@@ -8,11 +8,15 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javafx.application.Platform;
+import javafx.beans.property.LongProperty;
+import javafx.beans.property.SimpleLongProperty;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.concurrent.Task;
 import java.nio.file.Path;
 import java.security.SecureRandom;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.MissingResourceException;
 import java.util.Objects;
 import java.util.ResourceBundle;
@@ -28,6 +32,7 @@ class HealthCheckTask extends Task<Void> {
 	private final SecureRandom csprng;
 	private final HealthCheck check;
 	private final ObservableList<DiagnosticResult> results;
+	private final LongProperty durationInMillis;
 
 	public HealthCheckTask(Path vaultPath, VaultConfig vaultConfig, Masterkey masterkey, SecureRandom csprng, HealthCheck check, ResourceBundle resourceBundle) {
 		this.vaultPath = Objects.requireNonNull(vaultPath);
@@ -42,10 +47,12 @@ class HealthCheckTask extends Task<Void> {
 			LOG.warn("Missing proper name for health check {}, falling back to default.", check.identifier());
 			updateTitle(check.identifier());
 		}
+		this.durationInMillis = new SimpleLongProperty(-1);
 	}
 
 	@Override
 	protected Void call() {
+		Instant start = Instant.now();
 		try (var masterkeyClone = masterkey.clone(); //
 			 var cryptor = vaultConfig.getCipherCombo().getCryptorProvider(csprng).withKey(masterkeyClone)) {
 			check.check(vaultPath, vaultConfig, masterkeyClone, cryptor, result -> {
@@ -66,6 +73,7 @@ class HealthCheckTask extends Task<Void> {
 				Platform.runLater(() -> results.add(result));
 			});
 		}
+		Platform.runLater(() ->durationInMillis.set(Duration.between(start, Instant.now()).toMillis()));
 		return null;
 	}
 
@@ -89,4 +97,12 @@ class HealthCheckTask extends Task<Void> {
 		return check;
 	}
 
+	public LongProperty durationInMillisProperty() {
+		return durationInMillis;
+	}
+
+	public long getDurationInMillis() {
+		return durationInMillis.get();
+	}
+
 }

+ 7 - 4
main/ui/src/main/resources/fxml/health_check_details.fxml

@@ -1,15 +1,18 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
+<?import org.cryptomator.ui.controls.FormattedLabel?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.control.ListView?>
 <?import javafx.scene.layout.VBox?>
-<?import javafx.scene.text.Text?>
-<?import org.cryptomator.ui.controls.FormattedLabel?>
+<?import org.cryptomator.ui.controls.FormattedLabel2?>
 <VBox xmlns:fx="http://javafx.com/fxml"
 	  xmlns="http://javafx.com/javafx"
 	  fx:controller="org.cryptomator.ui.health.CheckDetailController"
 	  spacing="6">
 	<FormattedLabel fx:id="checkTitle" styleClass="label-large" format="%health.check.result.header" arg1="${controller.taskName}"/>
-	<Text fx:id="checkDescription" styleClass="label" text="FIXME: Here could be a small description."/>
-	<ListView fx:id="resultsListView"/>
+	<Label text="The check is currently running..." visible="${controller.producingResults}" managed="${controller.producingResults}"/>
+	<!-- TODO: what if the check was canceled? Should be indicated in ui -->
+	<FormattedLabel fx:id="checkDescription" styleClass="label" format="The check finished after %d milliseconds." arg1="${controller.taskDuration}" visible="${!controller.producingResults}" managed="${!controller.producingResults}"/>
+	<FormattedLabel2 styleClass="label" format="Found %d Warnings and %d unfixable Errors." arg1="${controller.numOfWarnings}" arg2="${controller.numOfErrors}" />
+	<ListView fx:id="resultsListView" VBox.vgrow="ALWAYS"/>
 </VBox>