diff --git a/main/pom.xml b/main/pom.xml index 848eddaf0..8c4ad71d3 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -25,7 +25,7 @@ 16 - 2.1.0-beta4 + 2.1.0-beta5 1.0.0-beta2 1.0.0-beta2 1.0.0-beta2 diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java index a1773d7af..32c142762 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -12,7 +12,7 @@ public enum FxmlFile { ERROR("/fxml/error.fxml"), // FORGET_PASSWORD("/fxml/forget_password.fxml"), // HEALTH_START("/fxml/health_start.fxml"), // - HEALTH_CHECK("/fxml/health_check.fxml"), // + HEALTH_CHECK("/fxml/health_check_list.fxml"), // LOCK_FORCED("/fxml/lock_forced.fxml"), // LOCK_FAILED("/fxml/lock_failed.fxml"), // MAIN_WINDOW("/fxml/main_window.fxml"), // diff --git a/main/ui/src/main/java/org/cryptomator/ui/health/BatchService.java b/main/ui/src/main/java/org/cryptomator/ui/health/BatchService.java new file mode 100644 index 000000000..f3968c27d --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/health/BatchService.java @@ -0,0 +1,36 @@ +package org.cryptomator.ui.health; + +import com.google.common.base.Preconditions; +import com.google.common.base.Suppliers; +import dagger.Lazy; + +import javax.inject.Inject; +import javafx.concurrent.Service; +import javafx.concurrent.Task; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; + +public class BatchService extends Service { + + private final Iterator remainingTasks; + + @Inject + public BatchService(Iterable tasks) { + this.remainingTasks = tasks.iterator(); + } + + @Override + protected Task createTask() { + Preconditions.checkState(remainingTasks.hasNext(), "No remaining tasks"); + return remainingTasks.next(); + } + + @Override + protected void succeeded() { + if (remainingTasks.hasNext()) { + this.restart(); + } + } +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/health/CheckController.java b/main/ui/src/main/java/org/cryptomator/ui/health/CheckDetailController.java similarity index 56% rename from main/ui/src/main/java/org/cryptomator/ui/health/CheckController.java rename to main/ui/src/main/java/org/cryptomator/ui/health/CheckDetailController.java index 2af708589..7dd1603c9 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/health/CheckController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/health/CheckDetailController.java @@ -8,99 +8,56 @@ import org.cryptomator.ui.common.FxController; import javax.inject.Inject; import javafx.beans.binding.Binding; import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Worker; import javafx.fxml.FXML; -import javafx.scene.control.ListView; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; -import java.util.concurrent.ExecutorService; -@HealthCheckScoped -public class CheckController implements FxController { +public class CheckDetailController implements FxController { - private final HealthCheckSupervisor supervisor; - private final HealthReportWriteTask reportWriter; - private final ExecutorService executorService; - private final ObjectProperty selectedTask; private final Binding> selectedResults; private final OptionalBinding selectedTaskState; private final Binding selectedTaskName; private final Binding selectedTaskDescription; - private final ReadOnlyBooleanProperty running; - /* FXML */ - public ListView checksListView; public TableView resultsTableView; public TableColumn resultDescriptionColumn; public TableColumn resultSeverityColumn; - @Inject - public CheckController(HealthCheckSupervisor supervisor, HealthReportWriteTask reportWriteTask, ExecutorService executorService) { - this.supervisor = supervisor; - this.reportWriter = reportWriteTask; - this.executorService = executorService; - this.selectedTask = new SimpleObjectProperty<>(); + public CheckDetailController(ObjectProperty selectedTask) { this.selectedResults = EasyBind.wrapNullable(selectedTask).map(HealthCheckTask::results).orElse(FXCollections.emptyObservableList()); this.selectedTaskState = EasyBind.wrapNullable(selectedTask).mapObservable(HealthCheckTask::stateProperty); this.selectedTaskName = EasyBind.wrapNullable(selectedTask).map(HealthCheckTask::getTitle).orElse(""); this.selectedTaskDescription = EasyBind.wrapNullable(selectedTask).map(task -> task.getCheck().toString()).orElse(""); - this.running = supervisor.runningProperty(); } @FXML public void initialize() { - checksListView.setItems(FXCollections.observableArrayList(supervisor.getTasks())); - checksListView.setCellFactory(ignored -> new CheckListCell()); - checksListView.getSelectionModel().select(0); - selectedTask.bind(checksListView.getSelectionModel().selectedItemProperty()); - resultsTableView.itemsProperty().bind(selectedResults); resultDescriptionColumn.setCellValueFactory(cellFeatures -> new SimpleStringProperty(cellFeatures.getValue().toString())); resultSeverityColumn.setCellValueFactory(cellFeatures -> new SimpleStringProperty(cellFeatures.getValue().getServerity().name())); - executorService.execute(supervisor); } - @FXML - public void cancelCheck() { - assert running.get(); - supervisor.cancel(true); - } + /* Getter/Setter */ - - @FXML - public void exportResults() { - executorService.execute(reportWriter); - } - - /* Getter&Setter */ - - public boolean isRunning() { - return running.get(); - } - - public ReadOnlyBooleanProperty runningProperty() { - return running; + public String getSelectedTaskName() { + return selectedTaskName.getValue(); } public Binding selectedTaskNameProperty() { return selectedTaskName; } - public String isSelectedTaskName() { - return selectedTaskName.getValue(); + public String getSelectedTaskDescription() { + return selectedTaskDescription.getValue(); } public Binding selectedTaskDescriptionProperty() { return selectedTaskDescription; } - public String isSelectedTaskDescription() { - return selectedTaskDescription.getValue(); - } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/health/CheckListController.java b/main/ui/src/main/java/org/cryptomator/ui/health/CheckListController.java new file mode 100644 index 000000000..dc340001c --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/health/CheckListController.java @@ -0,0 +1,105 @@ +package org.cryptomator.ui.health; + +import com.google.common.base.Preconditions; +import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.optional.OptionalBinding; +import dagger.Lazy; +import org.cryptomator.cryptofs.health.api.DiagnosticResult; +import org.cryptomator.ui.common.FxController; + +import javax.inject.Inject; +import javafx.beans.binding.Binding; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.concurrent.Worker; +import javafx.fxml.FXML; +import javafx.scene.control.ListView; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import java.util.Collection; +import java.util.concurrent.ExecutorService; + +@HealthCheckScoped +public class CheckListController implements FxController { + + private final ObservableList tasks; + private final HealthReportWriteTask reportWriter; + private final ExecutorService executorService; + private final ObjectProperty selectedTask; + private final SimpleObjectProperty> runningTask; + private final Binding running; + private final BooleanBinding anyCheckSelected; + + /* FXML */ + public ListView checksListView; + + @Inject + public CheckListController(Lazy> tasks, HealthReportWriteTask reportWriteTask, ObjectProperty selectedTask, ExecutorService executorService) { + this.tasks = FXCollections.observableArrayList(tasks.get()); + this.reportWriter = reportWriteTask; + this.executorService = executorService; + this.selectedTask = selectedTask; + this.runningTask = new SimpleObjectProperty<>(); + this.running = EasyBind.wrapNullable(runningTask).map(Worker::isRunning).orElse(false); + this.anyCheckSelected = selectedTask.isNotNull(); + } + + @FXML + public void initialize() { + checksListView.setItems(tasks); + checksListView.setCellFactory(ignored -> new CheckListCell()); + selectedTask.bind(checksListView.getSelectionModel().selectedItemProperty()); + } + + @FXML + public synchronized void runSelectedChecks() { + Preconditions.checkState(runningTask.get() == null); + var batch = new BatchService(checksListView.getSelectionModel().getSelectedItems()); + batch.setExecutor(executorService); + batch.start(); + runningTask.set(batch); + } + + @FXML + public synchronized void runAllChecks() { + Preconditions.checkState(runningTask.get() == null); + var batch = new BatchService(checksListView.getItems()); + batch.setExecutor(executorService); + batch.start(); + runningTask.set(batch); + } + + @FXML + public synchronized void cancelCheck() { + Preconditions.checkState(runningTask.get() != null); + runningTask.get().cancel(); + } + + @FXML + public void exportResults() { + executorService.execute(reportWriter); + } + + /* Getter/Setter */ + + public boolean isRunning() { + return running.getValue(); + } + + public Binding runningProperty() { + return running; + } + + public boolean isAnyCheckSelected() { + return anyCheckSelected.get(); + } + + public BooleanBinding anyCheckSelectedProperty() { + return anyCheckSelected; + } + +} 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 281af947d..fa9b69adc 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 @@ -20,11 +20,13 @@ import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.cryptomator.ui.mainwindow.MainWindow; import javax.inject.Provider; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; import javafx.scene.Scene; import javafx.stage.Modality; import javafx.stage.Stage; import java.security.SecureRandom; -import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Optional; @@ -40,12 +42,6 @@ abstract class HealthCheckModule { return new AtomicReference<>(); } - @Provides - @HealthCheckScoped - static Runnable provideMasterkeyDestructor(AtomicReference masterkeyRef) { - return () -> Optional.ofNullable(masterkeyRef.getAndSet(null)).ifPresent(Masterkey::destroy); - } - @Provides @HealthCheckScoped static AtomicReference provideVaultConfigRef() { @@ -54,15 +50,21 @@ abstract class HealthCheckModule { @Provides @HealthCheckScoped - static Collection provideSelectedHealthChecks() { - return new ArrayList<>(); + static Collection provideAvailableHealthChecks() { + return HealthCheck.allChecks(); + } + + @Provides + @HealthCheckScoped + static ObjectProperty provideSelectedHealthCheckTask() { + return new SimpleObjectProperty<>(); } /* Only inject with Lazy-Wrapper!*/ @Provides @HealthCheckScoped - static Collection provideSelectedHealthCheckTasks(Collection selectedHealthChecks, @HealthCheckWindow Vault vault, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, SecureRandom csprng) { - return selectedHealthChecks.stream().map(check -> new HealthCheckTask(vault.getPath(), vaultConfigRef.get(), masterkeyRef.get(), csprng, check)).toList(); + static Collection provideAvailableHealthCheckTasks(Collection availableHealthChecks, @HealthCheckWindow Vault vault, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, SecureRandom csprng) { + return availableHealthChecks.stream().map(check -> new HealthCheckTask(vault.getPath(), vaultConfigRef.get(), masterkeyRef.get(), csprng, check)).toList(); } @Provides @@ -82,20 +84,26 @@ abstract class HealthCheckModule { @Provides @HealthCheckWindow @HealthCheckScoped - static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle, Runnable masterkeyDestructor) { + static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle, ChangeListener showingListener) { Stage stage = factory.create(); stage.setTitle(resourceBundle.getString("health.title")); stage.setResizable(true); stage.initModality(Modality.WINDOW_MODAL); stage.initOwner(owner); - stage.showingProperty().addListener((observable, wasShowing, isShowing) -> { //TODO: should we use showingProperty or onCloseRequest - if (!isShowing) { - masterkeyDestructor.run(); - } - }); + stage.showingProperty().addListener(showingListener); // bind masterkey lifecycle to window return stage; } + @Provides + @HealthCheckScoped + static ChangeListener provideWindowShowingChangeListener(AtomicReference masterkey) { + return (observable, wasShowing, isShowing) -> { + if (!isShowing) { + Optional.ofNullable(masterkey.getAndSet(null)).ifPresent(Masterkey::destroy); + } + }; + } + @Provides @FxmlScene(FxmlFile.HEALTH_START) @HealthCheckScoped @@ -117,7 +125,13 @@ abstract class HealthCheckModule { @Binds @IntoMap - @FxControllerKey(CheckController.class) - abstract FxController bindCheckController(CheckController controller); + @FxControllerKey(CheckListController.class) + abstract FxController bindCheckController(CheckListController controller); + + @Binds + @IntoMap + @FxControllerKey(CheckDetailController.class) + abstract FxController bindCheckDetailController(CheckDetailController controller); + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckSupervisor.java b/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckSupervisor.java deleted file mode 100644 index 5b42429fd..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/health/HealthCheckSupervisor.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.cryptomator.ui.health; - -import dagger.Lazy; - -import javax.inject.Inject; -import javafx.concurrent.Task; -import java.util.Collection; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicReference; - -@HealthCheckScoped -public class HealthCheckSupervisor extends Task { - - private final ExecutorService executor; - private final Lazy> lazyTasks; - private final Runnable masterkeyDestructor; - private final AtomicReference currentTask; - private final ConcurrentLinkedQueue remainingTasks; - - @Inject - public HealthCheckSupervisor(Lazy> lazyTasks, Runnable masterkeyDestructor) { - this.lazyTasks = lazyTasks; - this.masterkeyDestructor = masterkeyDestructor; - this.currentTask = new AtomicReference<>(null); - this.executor = Executors.newSingleThreadExecutor(); - this.remainingTasks = new ConcurrentLinkedQueue<>(); - } - - public Void call() { - remainingTasks.addAll(lazyTasks.get()); - while (!remainingTasks.isEmpty()) { - final var task = remainingTasks.remove(); - currentTask.set(task); - executor.execute(task); //TODO: think about if we set the scheduled property for all tasks? - try { - task.get(); - } catch (InterruptedException e) { - executor.shutdownNow(); - cleanup(); - ; - Thread.currentThread().interrupt(); //TODO: do we need this? - } catch (ExecutionException e) { - e.printStackTrace(); - } catch (CancellationException e) { - // ok - } - } - return null; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - cleanup(); - currentTask.get().cancel(mayInterruptIfRunning); - if (mayInterruptIfRunning) { - executor.shutdownNow(); - } else { - executor.shutdown(); - } - return super.cancel(mayInterruptIfRunning); - } - - private void cleanup() { - remainingTasks.forEach(task -> task.cancel(false)); - remainingTasks.clear(); - } - - @Override - protected void done() { - masterkeyDestructor.run(); //TODO: if we destroy it, no check can rerun - } - - public Collection getTasks() { - return lazyTasks.get(); - } -} diff --git a/main/ui/src/main/java/org/cryptomator/ui/health/StartController.java b/main/ui/src/main/java/org/cryptomator/ui/health/StartController.java index a614da59c..469503732 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/health/StartController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/health/StartController.java @@ -44,8 +44,6 @@ public class StartController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(StartController.class); private final Stage window; - private final Collection availableChecks; - private final Map availableCheckListSelectProperties; private final Optional unverifiedVaultConfig; private final KeyLoadingStrategy keyLoadingStrategy; private final ExecutorService executor; @@ -55,11 +53,6 @@ public class StartController implements FxController { private final Lazy checkScene; /* FXML */ - public ListView availableChecksList; - public RadioButton customCheckSetButton; - public RadioButton quickCheckSetButton; - public RadioButton fullCheckSetButton; - public ToggleGroup checksSetToggleGroup; @Inject public StartController(@HealthCheckWindow Vault vault, @HealthCheckWindow Stage window, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, Collection selectedChecks, @FxmlScene(FxmlFile.HEALTH_CHECK) Lazy checkScene) { @@ -71,31 +64,6 @@ public class StartController implements FxController { this.vaultConfigRef = vaultConfigRef; this.selectedChecks = selectedChecks; this.checkScene = checkScene; - this.availableChecks = HealthCheck.allChecks(); - this.availableCheckListSelectProperties = new HashMap<>(); - - availableChecks.forEach(check -> availableCheckListSelectProperties.put(check, new SimpleBooleanProperty(false))); - } - - public void initialize() { - availableChecksList.setItems(FXCollections.observableList(availableChecks.stream().toList())); - availableChecksList.setCellFactory(CheckBoxListCell.forListView(availableCheckListSelectProperties::get)); - availableChecksList.setEditable(false); - - var availableCheckIds = availableChecks.stream().map(HealthCheck::identifier).toList(); - - if (PredefinedCheckSet.QUICK.getCheckIds().stream().anyMatch(checkId -> !availableCheckIds.contains(checkId))) { - quickCheckSetButton.setVisible(false); - quickCheckSetButton.setManaged(false); - } - if (PredefinedCheckSet.FULL.getCheckIds().stream().anyMatch(checkId -> !availableCheckIds.contains(checkId))) { - fullCheckSetButton.setVisible(false); - fullCheckSetButton.setManaged(false); - } - - quickCheckSetButton.setUserData(PredefinedCheckSet.QUICK); - fullCheckSetButton.setUserData(PredefinedCheckSet.FULL); - customCheckSetButton.setUserData(PredefinedCheckSet.CUSTOM); } @FXML @@ -106,15 +74,6 @@ public class StartController implements FxController { @FXML public void next() { - switch ((PredefinedCheckSet) checksSetToggleGroup.getSelectedToggle().getUserData()) { - case FULL -> selectedChecks.addAll(availableChecks); - case QUICK -> selectedChecks.addAll(availableChecks.stream().filter(check -> PredefinedCheckSet.QUICK.getCheckIds().contains(check.identifier())).toList()); - case CUSTOM -> availableCheckListSelectProperties.forEach((check, selected) -> { - if (selected.get()) { - selectedChecks.add(check); - } - }); - } LOG.trace("StartController.next()"); executor.submit(this::loadKey); } @@ -166,27 +125,4 @@ public class StartController implements FxController { return unverifiedVaultConfig.isEmpty(); } - public BooleanBinding anyCheckSetSelectedProperty() { - return checksSetToggleGroup.selectedToggleProperty().isNotNull(); - } - - public boolean isAnyCheckSetSelected() { - return anyCheckSetSelectedProperty().get(); - } - - enum PredefinedCheckSet { - QUICK(DirIdCheck.class.getCanonicalName()), //TODO: get identifier via static method? - FULL(DirIdCheck.class.getCanonicalName()), - CUSTOM(); - - private Collection checkIds; - - PredefinedCheckSet(String... checkIds) { - this.checkIds = Set.of(checkIds); - } - - Collection getCheckIds() { - return checkIds; - } - } } diff --git a/main/ui/src/main/resources/fxml/health_check.fxml b/main/ui/src/main/resources/fxml/health_check.fxml deleted file mode 100644 index 7ccc88d73..000000000 --- a/main/ui/src/main/resources/fxml/health_check.fxml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - -