diff --git a/main/ui/src/main/java/org/cryptomator/ui/health/CheckController.java b/main/ui/src/main/java/org/cryptomator/ui/health/CheckController.java index 86400780a..0b4b4a4f0 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/health/CheckController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/health/CheckController.java @@ -1,23 +1,29 @@ package org.cryptomator.ui.health; -import org.cryptomator.common.vaults.Vault; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.EasyBinding; +import com.tobiasdiez.easybind.optional.OptionalBinding; +import dagger.Lazy; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.health.api.DiagnosticResult; import org.cryptomator.cryptofs.health.api.HealthCheck; -import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.ui.common.FxController; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.inject.Inject; +import javafx.beans.binding.Binding; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.binding.BooleanExpression; +import javafx.beans.binding.ObjectBinding; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.concurrent.Worker; import javafx.fxml.FXML; import javafx.scene.control.ListView; import javafx.stage.Stage; -import java.security.SecureRandom; +import java.util.Collection; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicReference; @@ -27,59 +33,78 @@ public class CheckController implements FxController { private final Stage window; private final VaultConfig vaultConfig; - private final HealthCheckTaskFactory healthCheckTaskFactory; private final ExecutorService executor; - private final ObjectProperty selectedCheck; - private final ObservableList checks; - private final ObservableList results; + private final ObjectProperty selectedTask; + private final ObservableList tasks; + private final ObjectBinding> selectedResults; + private final OptionalBinding selectedTaskState; + private final BooleanExpression ready; + private final BooleanExpression running; - public ListView checksListView; + public ListView checksListView; public ListView resultsListView; + @Inject - public CheckController(@HealthCheckWindow Stage window, AtomicReference vaultConfigRef, HealthCheckTaskFactory healthCheckTaskFactory, ExecutorService executor, ObjectProperty selectedCheck) { + public CheckController(@HealthCheckWindow Stage window, AtomicReference vaultConfigRef, Lazy> tasks, ExecutorService executor, ObjectProperty selectedTask) { this.window = window; this.vaultConfig = Objects.requireNonNull(vaultConfigRef.get()); - this.healthCheckTaskFactory = healthCheckTaskFactory; this.executor = executor; - this.selectedCheck = selectedCheck; - this.checks = FXCollections.observableArrayList(HealthCheck.allChecks()); - this.results = FXCollections.observableArrayList(); + this.selectedTask = selectedTask; + this.selectedResults = Bindings.createObjectBinding(this::getSelectedResults, selectedTask); + this.selectedTaskState = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::stateProperty); + this.ready = BooleanExpression.booleanExpression(selectedTaskState.map(Worker.State.READY::equals).orElse(false)); + this.running = BooleanExpression.booleanExpression(selectedTaskState.map(Worker.State.RUNNING::equals).orElse(false)); + this.tasks = FXCollections.observableArrayList(tasks.get()); } @FXML public void initialize() { - checksListView.setItems(checks); - checksListView.setCellFactory(this::createCheckListCell); - resultsListView.setItems(results); - resultsListView.setCellFactory(this::createResultListCell); - selectedCheck.bind(checksListView.getSelectionModel().selectedItemProperty()); - } - - private CheckListCell createCheckListCell(ListView list) { - return new CheckListCell(); - } - - private ResultListCell createResultListCell(ListView list) { - return new ResultListCell(); + checksListView.setItems(tasks); + checksListView.setCellFactory(ignored -> new CheckListCell()); + resultsListView.itemsProperty().bind(selectedResults); + resultsListView.setCellFactory(ignored -> new ResultListCell()); + selectedTask.bind(checksListView.getSelectionModel().selectedItemProperty()); } @FXML public void runCheck() { - executor.execute(healthCheckTaskFactory.newTask(selectedCheck.get(), results::add)); + assert selectedTask.get() != null; + executor.execute(selectedTask.get()); + } + @FXML + public void cancelCheck() { + assert selectedTask.get() != null; + assert selectedTask.get().isRunning(); + selectedTask.get().cancel(); } - /* Getter/Setter */ public VaultConfig getVaultConfig() { return vaultConfig; } - public HealthCheck getSelectedCheck() { - return selectedCheck.get(); + public boolean isRunning() { + return running.get(); } - public ReadOnlyObjectProperty selectedCheckProperty() { - return selectedCheck; + public BooleanExpression runningProperty() { + return running; + } + + public boolean isReady() { + return ready.get(); + } + + public BooleanExpression readyProperty() { + return ready; + } + + private ObservableList getSelectedResults() { + if (selectedTask.get() == null) { + return FXCollections.emptyObservableList(); + } else { + return selectedTask.get().results(); + } } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/health/CheckListCell.java b/main/ui/src/main/java/org/cryptomator/ui/health/CheckListCell.java index 09e73b4b8..a7da1c32e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/health/CheckListCell.java +++ b/main/ui/src/main/java/org/cryptomator/ui/health/CheckListCell.java @@ -1,16 +1,43 @@ package org.cryptomator.ui.health; -import org.cryptomator.cryptofs.health.api.HealthCheck; +import org.cryptomator.ui.controls.FontAwesome5Icon; +import org.cryptomator.ui.controls.FontAwesome5IconView; +import javafx.beans.value.ObservableValue; +import javafx.concurrent.Worker; +import javafx.scene.control.ContentDisplay; import javafx.scene.control.ListCell; -class CheckListCell extends ListCell { +class CheckListCell extends ListCell { + + private final FontAwesome5IconView stateIcon = new FontAwesome5IconView(); @Override - protected void updateItem(HealthCheck item, boolean empty) { + protected void updateItem(HealthCheckTask item, boolean empty) { super.updateItem(item, empty); if (item != null) { - setText(item.identifier()); + setText(item.getCheck().identifier()); + item.stateProperty().addListener(this::stateChanged); + setGraphic(stateIcon); + stateIcon.setGlyph(glyphForState(item.getState())); + setContentDisplay(ContentDisplay.LEFT); + } else { + setText(null); + setContentDisplay(ContentDisplay.TEXT_ONLY); } } + + private void stateChanged(ObservableValue observable, Worker.State oldState, Worker.State newState) { + stateIcon.setGlyph(glyphForState(newState)); + } + + private FontAwesome5Icon glyphForState(Worker.State state) { + // TODO choose appropriate glyphs + return switch (state) { + case READY, SCHEDULED -> FontAwesome5Icon.ANCHOR; + case RUNNING -> FontAwesome5Icon.SPINNER; + case FAILED, CANCELLED -> FontAwesome5Icon.EXCLAMATION_TRIANGLE; + case SUCCEEDED -> FontAwesome5Icon.CHECK; + }; + } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckModule.java b/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckModule.java index c6b370469..37915edd6 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckModule.java @@ -3,6 +3,7 @@ package org.cryptomator.ui.health; import dagger.Binds; import dagger.Module; import dagger.Provides; +import dagger.multibindings.ElementsIntoSet; import dagger.multibindings.IntoMap; import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptofs.VaultConfig; @@ -26,9 +27,12 @@ import javafx.beans.value.ChangeListener; import javafx.scene.Scene; import javafx.stage.Modality; import javafx.stage.Stage; +import java.security.SecureRandom; +import java.util.Collection; import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @Module(subcomponents = {KeyLoadingComponent.class}) @@ -36,8 +40,8 @@ abstract class HealthCheckModule { @Provides @HealthCheckScoped - static ObjectProperty selectedHealthCheck() { - return new SimpleObjectProperty(); + static ObjectProperty selectedHealthCheckTask() { + return new SimpleObjectProperty<>(); } @Provides @@ -59,6 +63,12 @@ abstract class HealthCheckModule { return compBuilder.vault(vault).window(window).build().keyloadingStrategy(); } + @Provides + @HealthCheckScoped + static Collection provideHealthCheckTasks(@HealthCheckWindow Vault vault, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, SecureRandom csprng) { + return HealthCheck.allChecks().stream().map(check -> new HealthCheckTask(vault.getPath(), vaultConfigRef.get(), masterkeyRef.get(), csprng, check)).toList(); + } + @Provides @HealthCheckWindow @HealthCheckScoped @@ -72,7 +82,7 @@ abstract class HealthCheckModule { static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle, ChangeListener showingListener) { Stage stage = factory.create(); stage.setTitle(resourceBundle.getString("health.title")); - stage.setResizable(false); + stage.setResizable(true); stage.initModality(Modality.WINDOW_MODAL); stage.initOwner(owner); stage.showingProperty().addListener(showingListener); diff --git a/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java b/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java index 7243fcd3d..ba3b5a85d 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java +++ b/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java @@ -8,7 +8,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javafx.application.Platform; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.concurrent.Task; +import javafx.concurrent.Worker; import java.nio.file.Path; import java.security.SecureRandom; import java.util.Objects; @@ -24,15 +29,15 @@ class HealthCheckTask extends Task { private final Masterkey masterkey; private final SecureRandom csprng; private final HealthCheck check; - private final Consumer resultConsumer; + private final ObservableList results; - public HealthCheckTask(Path vaultPath, VaultConfig vaultConfig, Masterkey masterkey, SecureRandom csprng, HealthCheck check, Consumer resultConsumer) { - this.vaultPath = vaultPath; - this.vaultConfig = vaultConfig; - this.masterkey = masterkey; - this.csprng = csprng; + public HealthCheckTask(Path vaultPath, VaultConfig vaultConfig, Masterkey masterkey, SecureRandom csprng, HealthCheck check) { + this.vaultPath = Objects.requireNonNull(vaultPath); + this.vaultConfig = Objects.requireNonNull(vaultConfig); + this.masterkey = Objects.requireNonNull(masterkey); + this.csprng = Objects.requireNonNull(csprng); this.check = Objects.requireNonNull(check); - this.resultConsumer = resultConsumer; + this.results = FXCollections.observableArrayList(); } @Override @@ -42,7 +47,13 @@ class HealthCheckTask extends Task { if (isCancelled()) { throw new CancellationException(); } - Platform.runLater(() -> resultConsumer.accept(result)); + // FIXME: slowdown for demonstration purposes only: + try { + Thread.sleep(200); + } catch (InterruptedException e) { + e.printStackTrace(); + } + Platform.runLater(() -> results.add(result)); }); } return null; @@ -57,4 +68,15 @@ class HealthCheckTask extends Task { protected void done() { LOG.info("finished {}", check.identifier()); } + + /* Getter */ + + public ObservableList results() { + return results; + } + + public HealthCheck getCheck() { + return check; + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckTaskFactory.java b/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckTaskFactory.java deleted file mode 100644 index 3d192e0fe..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckTaskFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.cryptomator.ui.health; - -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.cryptofs.VaultConfig; -import org.cryptomator.cryptofs.health.api.DiagnosticResult; -import org.cryptomator.cryptofs.health.api.HealthCheck; -import org.cryptomator.cryptolib.api.Masterkey; - -import javax.inject.Inject; -import java.security.SecureRandom; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; - -@HealthCheckScoped -class HealthCheckTaskFactory { - - private final Vault vault; - private final Masterkey masterkey; - private final VaultConfig vaultConfig; - private final SecureRandom csprng; - - @Inject - public HealthCheckTaskFactory(@HealthCheckWindow Vault vault, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, SecureRandom csprng) { - this.vault = vault; - this.masterkey = Objects.requireNonNull(masterkeyRef.get()); - this.vaultConfig = Objects.requireNonNull(vaultConfigRef.get()); - this.csprng = csprng; - } - - public HealthCheckTask newTask(HealthCheck healthCheck, Consumer resultConsumer) { - return new HealthCheckTask(vault.getPath(), vaultConfig, masterkey, csprng, healthCheck, resultConsumer); - } - -} diff --git a/main/ui/src/main/java/org/cryptomator/ui/health/ResultListCell.java b/main/ui/src/main/java/org/cryptomator/ui/health/ResultListCell.java index 1b0d02a11..6458b4ff4 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/health/ResultListCell.java +++ b/main/ui/src/main/java/org/cryptomator/ui/health/ResultListCell.java @@ -12,6 +12,8 @@ class ResultListCell extends ListCell { super.updateItem(item, empty); if (item != null) { setText(item.toString()); + } else { + setText(null); } } } diff --git a/main/ui/src/main/resources/fxml/health_check.fxml b/main/ui/src/main/resources/fxml/health_check.fxml index 0ed5ff104..d67f9e1a9 100644 --- a/main/ui/src/main/resources/fxml/health_check.fxml +++ b/main/ui/src/main/resources/fxml/health_check.fxml @@ -24,9 +24,10 @@ - + -