mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-17 10:11:27 +00:00
implemented cancellable health checks
This commit is contained in:
@@ -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<HealthCheck> selectedCheck;
|
||||
private final ObservableList<HealthCheck> checks;
|
||||
private final ObservableList<DiagnosticResult> results;
|
||||
private final ObjectProperty<HealthCheckTask> selectedTask;
|
||||
private final ObservableList<HealthCheckTask> tasks;
|
||||
private final ObjectBinding<ObservableList<DiagnosticResult>> selectedResults;
|
||||
private final OptionalBinding<Worker.State> selectedTaskState;
|
||||
private final BooleanExpression ready;
|
||||
private final BooleanExpression running;
|
||||
|
||||
public ListView<HealthCheck> checksListView;
|
||||
public ListView<HealthCheckTask> checksListView;
|
||||
public ListView<DiagnosticResult> resultsListView;
|
||||
|
||||
|
||||
@Inject
|
||||
public CheckController(@HealthCheckWindow Stage window, AtomicReference<VaultConfig> vaultConfigRef, HealthCheckTaskFactory healthCheckTaskFactory, ExecutorService executor, ObjectProperty<HealthCheck> selectedCheck) {
|
||||
public CheckController(@HealthCheckWindow Stage window, AtomicReference<VaultConfig> vaultConfigRef, Lazy<Collection<HealthCheckTask>> tasks, ExecutorService executor, ObjectProperty<HealthCheckTask> 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<HealthCheck> list) {
|
||||
return new CheckListCell();
|
||||
}
|
||||
|
||||
private ResultListCell createResultListCell(ListView<DiagnosticResult> 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<HealthCheck> selectedCheckProperty() {
|
||||
return selectedCheck;
|
||||
public BooleanExpression runningProperty() {
|
||||
return running;
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return ready.get();
|
||||
}
|
||||
|
||||
public BooleanExpression readyProperty() {
|
||||
return ready;
|
||||
}
|
||||
|
||||
private ObservableList<DiagnosticResult> getSelectedResults() {
|
||||
if (selectedTask.get() == null) {
|
||||
return FXCollections.emptyObservableList();
|
||||
} else {
|
||||
return selectedTask.get().results();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<HealthCheck> {
|
||||
class CheckListCell extends ListCell<HealthCheckTask> {
|
||||
|
||||
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<? extends Worker.State> 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<HealthCheck> selectedHealthCheck() {
|
||||
return new SimpleObjectProperty<HealthCheck>();
|
||||
static ObjectProperty<HealthCheckTask> selectedHealthCheckTask() {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -59,6 +63,12 @@ abstract class HealthCheckModule {
|
||||
return compBuilder.vault(vault).window(window).build().keyloadingStrategy();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@HealthCheckScoped
|
||||
static Collection<HealthCheckTask> provideHealthCheckTasks(@HealthCheckWindow Vault vault, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> 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<Boolean> 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);
|
||||
|
||||
@@ -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<Void> {
|
||||
private final Masterkey masterkey;
|
||||
private final SecureRandom csprng;
|
||||
private final HealthCheck check;
|
||||
private final Consumer<DiagnosticResult> resultConsumer;
|
||||
private final ObservableList<DiagnosticResult> results;
|
||||
|
||||
public HealthCheckTask(Path vaultPath, VaultConfig vaultConfig, Masterkey masterkey, SecureRandom csprng, HealthCheck check, Consumer<DiagnosticResult> 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<Void> {
|
||||
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<Void> {
|
||||
protected void done() {
|
||||
LOG.info("finished {}", check.identifier());
|
||||
}
|
||||
|
||||
/* Getter */
|
||||
|
||||
public ObservableList<DiagnosticResult> results() {
|
||||
return results;
|
||||
}
|
||||
|
||||
public HealthCheck getCheck() {
|
||||
return check;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Masterkey> masterkeyRef, AtomicReference<VaultConfig> 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<DiagnosticResult> resultConsumer) {
|
||||
return new HealthCheckTask(vault.getPath(), vaultConfig, masterkey, csprng, healthCheck, resultConsumer);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,6 +12,8 @@ class ResultListCell extends ListCell<DiagnosticResult> {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null) {
|
||||
setText(item.toString());
|
||||
} else {
|
||||
setText(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
|
||||
<ListView fx:id="resultsListView"/>
|
||||
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+X">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CA">
|
||||
<buttons>
|
||||
<Button text="TODO run check" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#runCheck" disable="${selectedCheck.isNull}"/>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" onAction="#cancelCheck" disable="${!controller.running}"/>
|
||||
<Button text="TODO run check" ButtonBar.buttonData="APPLY" defaultButton="true" onAction="#runCheck" disable="${!controller.ready}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
|
||||
Reference in New Issue
Block a user