浏览代码

wrap DiagnosticResult in Result in order to track the state of applied fixes

Sebastian Stenzel 3 年之前
父节点
当前提交
f3953c2fb1

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

@@ -23,7 +23,7 @@ import java.util.stream.Stream;
 @HealthCheckScoped
 @HealthCheckScoped
 public class CheckDetailController implements FxController {
 public class CheckDetailController implements FxController {
 
 
-	private final EasyObservableList<DiagnosticResult> results;
+	private final EasyObservableList<Result> results;
 	private final OptionalBinding<Worker.State> taskState;
 	private final OptionalBinding<Worker.State> taskState;
 	private final Binding<String> taskName;
 	private final Binding<String> taskName;
 	private final Binding<String> taskDuration;
 	private final Binding<String> taskDuration;
@@ -39,7 +39,7 @@ public class CheckDetailController implements FxController {
 	private final ResultListCellFactory resultListCellFactory;
 	private final ResultListCellFactory resultListCellFactory;
 	private final ResourceBundle resourceBundle;
 	private final ResourceBundle resourceBundle;
 
 
-	public ListView<DiagnosticResult> resultsListView;
+	public ListView<Result> resultsListView;
 	private Subscription resultSubscription;
 	private Subscription resultSubscription;
 
 
 	@Inject
 	@Inject
@@ -71,8 +71,8 @@ public class CheckDetailController implements FxController {
 		}
 		}
 	}
 	}
 
 
-	private Function<Stream<? extends DiagnosticResult>, Long> countSeverity(DiagnosticResult.Severity severity) {
-		return stream -> stream.filter(item -> severity.equals(item.getSeverity())).count();
+	private Function<Stream<? extends Result>, Long> countSeverity(DiagnosticResult.Severity severity) {
+		return stream -> stream.filter(item -> severity.equals(item.diagnosis().getSeverity())).count();
 	}
 	}
 
 
 	@FXML
 	@FXML

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

@@ -35,7 +35,7 @@ class HealthCheckTask extends Task<Void> {
 	private final Masterkey masterkey;
 	private final Masterkey masterkey;
 	private final SecureRandom csprng;
 	private final SecureRandom csprng;
 	private final HealthCheck check;
 	private final HealthCheck check;
-	private final ObservableList<DiagnosticResult> results;
+	private final ObservableList<Result> results;
 	private final LongProperty durationInMillis;
 	private final LongProperty durationInMillis;
 	private final BooleanProperty chosenForExecution;
 	private final BooleanProperty chosenForExecution;
 
 
@@ -45,7 +45,7 @@ class HealthCheckTask extends Task<Void> {
 		this.masterkey = Objects.requireNonNull(masterkey);
 		this.masterkey = Objects.requireNonNull(masterkey);
 		this.csprng = Objects.requireNonNull(csprng);
 		this.csprng = Objects.requireNonNull(csprng);
 		this.check = Objects.requireNonNull(check);
 		this.check = Objects.requireNonNull(check);
-		this.results = FXCollections.observableArrayList();
+		this.results = FXCollections.observableArrayList(Result::observables);
 		try {
 		try {
 			updateTitle(resourceBundle.getString("health." + check.identifier()));
 			updateTitle(resourceBundle.getString("health." + check.identifier()));
 		} catch (MissingResourceException e) {
 		} catch (MissingResourceException e) {
@@ -61,11 +61,11 @@ class HealthCheckTask extends Task<Void> {
 		Instant start = Instant.now();
 		Instant start = Instant.now();
 		try (var masterkeyClone = masterkey.clone(); //
 		try (var masterkeyClone = masterkey.clone(); //
 			 var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
 			 var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
-			check.check(vaultPath, vaultConfig, masterkeyClone, cryptor, result -> {
+			check.check(vaultPath, vaultConfig, masterkeyClone, cryptor, diagnosis -> {
 				if (isCancelled()) {
 				if (isCancelled()) {
 					throw new CancellationException();
 					throw new CancellationException();
 				}
 				}
-				Platform.runLater(() -> results.add(result));
+				Platform.runLater(() -> results.add(Result.create(diagnosis)));
 			});
 			});
 		}
 		}
 		Platform.runLater(() -> durationInMillis.set(Duration.between(start, Instant.now()).toMillis()));
 		Platform.runLater(() -> durationInMillis.set(Duration.between(start, Instant.now()).toMillis()));
@@ -88,7 +88,7 @@ class HealthCheckTask extends Task<Void> {
 		return new Observable[]{results, chosenForExecution};
 		return new Observable[]{results, chosenForExecution};
 	}
 	}
 
 
-	public ObservableList<DiagnosticResult> results() {
+	public ObservableList<Result> results() {
 		return results;
 		return results;
 	}
 	}
 
 

+ 1 - 1
src/main/java/org/cryptomator/ui/health/ReportWriter.java

@@ -72,7 +72,7 @@ public class ReportWriter {
 					case SUCCEEDED -> {
 					case SUCCEEDED -> {
 						writer.write("STATUS: SUCCESS\nRESULTS:\n");
 						writer.write("STATUS: SUCCESS\nRESULTS:\n");
 						for (var result : task.results()) {
 						for (var result : task.results()) {
-							writer.write(REPORT_CHECK_RESULT.formatted(result.getSeverity(), result.toString()));
+							writer.write(REPORT_CHECK_RESULT.formatted(result.diagnosis().getSeverity(), result.getDescription()));
 						}
 						}
 					}
 					}
 					case CANCELLED -> writer.write("STATUS: CANCELED\n");
 					case CANCELLED -> writer.write("STATUS: CANCELED\n");

+ 43 - 0
src/main/java/org/cryptomator/ui/health/Result.java

@@ -0,0 +1,43 @@
+package org.cryptomator.ui.health;
+
+import org.cryptomator.cryptofs.health.api.DiagnosticResult;
+
+import javafx.beans.Observable;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+
+record Result(DiagnosticResult diagnosis, ObjectProperty<FixState> fixState) {
+
+	enum FixState {
+		NOT_FIXABLE,
+		FIXABLE,
+		FIXING,
+		FIXED,
+		FIX_FAILED
+	}
+
+	public static Result create(DiagnosticResult diagnosis) {
+		FixState initialState = switch (diagnosis.getSeverity()) {
+			case WARN -> FixState.FIXABLE;
+			default -> FixState.NOT_FIXABLE;
+		};
+		return new Result(diagnosis, new SimpleObjectProperty<>(initialState));
+	}
+
+	public Observable[] observables() {
+		return new Observable[]{fixState};
+	}
+
+	public String getDescription() {
+		return diagnosis.toString();
+	}
+
+	public FixState getState() {
+		return fixState.get();
+	}
+
+	public void setState(FixState state) {
+		this.fixState.set(state);
+	}
+
+}

+ 80 - 40
src/main/java/org/cryptomator/ui/health/ResultListCellController.java

@@ -1,84 +1,82 @@
 package org.cryptomator.ui.health;
 package org.cryptomator.ui.health;
 
 
 import com.tobiasdiez.easybind.EasyBind;
 import com.tobiasdiez.easybind.EasyBind;
-import org.cryptomator.cryptofs.health.api.DiagnosticResult;
+import com.tobiasdiez.easybind.optional.OptionalBinding;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.controls.FontAwesome5Icon;
 import org.cryptomator.ui.controls.FontAwesome5Icon;
 import org.cryptomator.ui.controls.FontAwesome5IconView;
 import org.cryptomator.ui.controls.FontAwesome5IconView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 
 import javax.inject.Inject;
 import javax.inject.Inject;
+import javafx.application.Platform;
 import javafx.beans.binding.Binding;
 import javafx.beans.binding.Binding;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.binding.ObjectBinding;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
-import javafx.beans.value.ObservableValue;
+import javafx.beans.value.ObservableObjectValue;
 import javafx.fxml.FXML;
 import javafx.fxml.FXML;
 import javafx.scene.control.Button;
 import javafx.scene.control.Button;
 
 
 // unscoped because each cell needs its own controller
 // unscoped because each cell needs its own controller
 public class ResultListCellController implements FxController {
 public class ResultListCellController implements FxController {
 
 
-	private final ResultFixApplier fixApplier;
-	private final ObjectProperty<DiagnosticResult> result;
+	private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
+
+	private final ObjectProperty<Result> result;
 	private final Binding<String> description;
 	private final Binding<String> description;
+	private final ResultFixApplier fixApplier;
+	private final OptionalBinding<Result.FixState> fixState;
+	private final ObjectBinding<FontAwesome5Icon> glyph;
+	private final BooleanBinding fixable;
+	private final BooleanBinding fixing;
+	private final BooleanBinding fixed;
 
 
 	public FontAwesome5IconView iconView;
 	public FontAwesome5IconView iconView;
-	public Button actionButton;
+	public Button fixButton;
 
 
 	@Inject
 	@Inject
 	public ResultListCellController(ResultFixApplier fixApplier) {
 	public ResultListCellController(ResultFixApplier fixApplier) {
 		this.result = new SimpleObjectProperty<>(null);
 		this.result = new SimpleObjectProperty<>(null);
-		this.description = EasyBind.wrapNullable(result).map(DiagnosticResult::toString).orElse("");
+		this.description = EasyBind.wrapNullable(result).map(Result::getDescription).orElse("");
 		this.fixApplier = fixApplier;
 		this.fixApplier = fixApplier;
-		result.addListener(this::updateCellContent);
-	}
-
-	private void updateCellContent(ObservableValue<? extends DiagnosticResult> observable, DiagnosticResult oldVal, DiagnosticResult newVal) {
-		iconView.getStyleClass().clear();
-		actionButton.setVisible(false);
-		//TODO: see comment in case WARN
-		actionButton.setManaged(false);
-		switch (newVal.getSeverity()) {
-			case INFO -> {
-				iconView.setGlyph(FontAwesome5Icon.INFO_CIRCLE);
-				iconView.getStyleClass().add("glyph-icon-muted");
-			}
-			case GOOD -> {
-				iconView.setGlyph(FontAwesome5Icon.CHECK);
-				iconView.getStyleClass().add("glyph-icon-primary");
-			}
-			case WARN -> {
-				iconView.setGlyph(FontAwesome5Icon.EXCLAMATION_TRIANGLE);
-				iconView.getStyleClass().add("glyph-icon-orange");
-				//TODO: Neither is any fix implemented, nor it is ensured, that only fix is executed at a time with good ui indication
-				//	before both are not fix, do not show the button
-				//actionButton.setVisible(true);
-			}
-			case CRITICAL -> {
-				iconView.setGlyph(FontAwesome5Icon.TIMES);
-				iconView.getStyleClass().add("glyph-icon-red");
-			}
-		}
+		this.fixState = EasyBind.wrapNullable(result).mapObservable(Result::fixState);
+		this.glyph = Bindings.createObjectBinding(this::getGlyph, result);
+		this.fixable = Bindings.createBooleanBinding(this::isFixable, fixState);
+		this.fixing = Bindings.createBooleanBinding(this::isFixing, fixState);
+		this.fixed = Bindings.createBooleanBinding(this::isFixed, fixState);
 	}
 	}
 
 
 	@FXML
 	@FXML
-	public void runResultAction() {
+	public void fix() {
 		final var realResult = result.get();
 		final var realResult = result.get();
 		if (realResult != null) {
 		if (realResult != null) {
 			fixApplier.fix(realResult);
 			fixApplier.fix(realResult);
 		}
 		}
 	}
 	}
-	/* Getter & Setter */
 
 
+	@FXML
+	public void initialize() {
+		// see getGlyph() for relevant glyphs:
+		EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-muted", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.INFO_CIRCLE));
+		EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-primary", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.CHECK));
+		EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-orange", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.EXCLAMATION_TRIANGLE));
+		EasyBind.includeWhen(iconView.getStyleClass(), "glyph-icon-red", iconView.glyphProperty().isEqualTo(FontAwesome5Icon.TIMES));
+	}
+
+	/* Getter & Setter */
 
 
-	public DiagnosticResult getResult() {
+	public Result getResult() {
 		return result.get();
 		return result.get();
 	}
 	}
 
 
-	public void setResult(DiagnosticResult result) {
+	public void setResult(Result result) {
 		this.result.set(result);
 		this.result.set(result);
 	}
 	}
 
 
-	public ObjectProperty<DiagnosticResult> resultProperty() {
+	public ObjectProperty<Result> resultProperty() {
 		return result;
 		return result;
 	}
 	}
 
 
@@ -86,7 +84,49 @@ public class ResultListCellController implements FxController {
 		return description.getValue();
 		return description.getValue();
 	}
 	}
 
 
+	public ObjectBinding<FontAwesome5Icon> glyphProperty() {
+		return glyph;
+	}
+
+	public FontAwesome5Icon getGlyph() {
+		var r = result.get();
+		if (r == null) {
+			return null;
+		}
+		return switch (r.diagnosis().getSeverity()) {
+			case INFO -> FontAwesome5Icon.INFO_CIRCLE;
+			case GOOD -> FontAwesome5Icon.CHECK;
+			case WARN -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
+			case CRITICAL -> FontAwesome5Icon.TIMES;
+		};
+	}
+
 	public Binding<String> descriptionProperty() {
 	public Binding<String> descriptionProperty() {
 		return description;
 		return description;
 	}
 	}
+
+	public BooleanBinding fixableProperty() {
+		return fixable;
+	}
+
+	public boolean isFixable() {
+		return fixState.get().map(Result.FixState.FIXABLE::equals).orElse(false);
+	}
+
+	public BooleanBinding fixingProperty() {
+		return fixing;
+	}
+
+	public boolean isFixing() {
+		return fixState.get().map(Result.FixState.FIXING::equals).orElse(false);
+	}
+
+	public BooleanBinding fixedProperty() {
+		return fixed;
+	}
+
+	public boolean isFixed() {
+		return fixState.get().map(Result.FixState.FIXED::equals).orElse(false);
+	}
+
 }
 }

+ 4 - 5
src/main/java/org/cryptomator/ui/health/ResultListCellFactory.java

@@ -1,7 +1,6 @@
 package org.cryptomator.ui.health;
 package org.cryptomator.ui.health;
 
 
 
 
-import org.cryptomator.cryptofs.health.api.DiagnosticResult;
 import org.cryptomator.ui.common.FxmlLoaderFactory;
 import org.cryptomator.ui.common.FxmlLoaderFactory;
 
 
 import javax.inject.Inject;
 import javax.inject.Inject;
@@ -15,7 +14,7 @@ import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.io.UncheckedIOException;
 
 
 @HealthCheckScoped
 @HealthCheckScoped
-public class ResultListCellFactory implements Callback<ListView<DiagnosticResult>, ListCell<DiagnosticResult>> {
+public class ResultListCellFactory implements Callback<ListView<Result>, ListCell<Result>> {
 
 
 	private final FxmlLoaderFactory fxmlLoaders;
 	private final FxmlLoaderFactory fxmlLoaders;
 
 
@@ -25,7 +24,7 @@ public class ResultListCellFactory implements Callback<ListView<DiagnosticResult
 	}
 	}
 
 
 	@Override
 	@Override
-	public ListCell<DiagnosticResult> call(ListView<DiagnosticResult> param) {
+	public ListCell<Result> call(ListView<Result> param) {
 		try {
 		try {
 			FXMLLoader fxmlLoader = fxmlLoaders.load("/fxml/health_result_listcell.fxml");
 			FXMLLoader fxmlLoader = fxmlLoaders.load("/fxml/health_result_listcell.fxml");
 			return new ResultListCellFactory.Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
 			return new ResultListCellFactory.Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
@@ -34,7 +33,7 @@ public class ResultListCellFactory implements Callback<ListView<DiagnosticResult
 		}
 		}
 	}
 	}
 
 
-	private static class Cell extends ListCell<DiagnosticResult> {
+	private static class Cell extends ListCell<Result> {
 
 
 		private final Parent node;
 		private final Parent node;
 		private final ResultListCellController controller;
 		private final ResultListCellController controller;
@@ -45,7 +44,7 @@ public class ResultListCellFactory implements Callback<ListView<DiagnosticResult
 		}
 		}
 
 
 		@Override
 		@Override
-		protected void updateItem(DiagnosticResult item, boolean empty) {
+		protected void updateItem(Result item, boolean empty) {
 			super.updateItem(item, empty);
 			super.updateItem(item, empty);
 			if (item == null || empty) {
 			if (item == null || empty) {
 				setText(null);
 				setText(null);

+ 12 - 2
src/main/resources/fxml/health_result_listcell.fxml

@@ -6,6 +6,10 @@
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.layout.HBox?>
 <?import javafx.scene.layout.HBox?>
 <?import javafx.scene.layout.Region?>
 <?import javafx.scene.layout.Region?>
+<?import javafx.scene.control.ProgressIndicator?>
+<?import javafx.scene.text.Text?>
+<?import javafx.scene.layout.Pane?>
+<?import javafx.scene.layout.StackPane?>
 <HBox xmlns:fx="http://javafx.com/fxml"
 <HBox xmlns:fx="http://javafx.com/fxml"
 	  xmlns="http://javafx.com/javafx"
 	  xmlns="http://javafx.com/javafx"
 	  fx:controller="org.cryptomator.ui.health.ResultListCellController"
 	  fx:controller="org.cryptomator.ui.health.ResultListCellController"
@@ -18,12 +22,18 @@
 		<Insets topRightBottomLeft="6"/>
 		<Insets topRightBottomLeft="6"/>
 	</padding>
 	</padding>
 	<children>
 	<children>
-		<FontAwesome5IconView fx:id="iconView" HBox.hgrow="NEVER" glyphSize="16"/>
+		<FontAwesome5IconView fx:id="iconView" HBox.hgrow="NEVER" glyphSize="16" glyph="${controller.glyph}"/>
 		<Label text="${controller.description}"/>
 		<Label text="${controller.description}"/>
 		<Region HBox.hgrow="ALWAYS"/>
 		<Region HBox.hgrow="ALWAYS"/>
 		<!-- TODO: setting the minWidth of the button is just a workaround.
 		<!-- TODO: setting the minWidth of the button is just a workaround.
 		           What we actually want to do is to prevent shrinking the button more than the text
 		           What we actually want to do is to prevent shrinking the button more than the text
 		           -> own subclass of HBox is needed -->
 		           -> own subclass of HBox is needed -->
-		<Button fx:id="actionButton" text="%health.check.fixBtn" onAction="#runResultAction" alignment="CENTER" visible="false" minWidth="-Infinity"/>
+		<StackPane HBox.hgrow="NEVER">
+			<children>
+				<Button fx:id="fixButton" text="%health.check.fixBtn" visible="${controller.fixable}" managed="${controller.fixable}" onAction="#fix" alignment="CENTER" minWidth="-Infinity"/>
+				<ProgressIndicator progress="-1" prefWidth="12" prefHeight="12" visible="${controller.fixing}" managed="${controller.fixing}"/>
+				<FontAwesome5IconView glyph="CHECK" glyphSize="16" visible="${controller.fixed}" managed="${controller.fixed}"/>
+			</children>
+		</StackPane>
 	</children>
 	</children>
 </HBox>
 </HBox>