diff --git a/src/main/java/org/cryptomator/ui/health/CheckListCell.java b/src/main/java/org/cryptomator/ui/health/CheckListCell.java index 63d9d8d6b..ac87b56ca 100644 --- a/src/main/java/org/cryptomator/ui/health/CheckListCell.java +++ b/src/main/java/org/cryptomator/ui/health/CheckListCell.java @@ -20,68 +20,29 @@ import javafx.util.Callback; class CheckListCell extends ListCell { private final FontAwesome5IconView stateIcon = new FontAwesome5IconView(); - private final Callback selectedGetter; - private final ObjectProperty stateProperty; - private CheckBox checkBox = new CheckBox(); - private BooleanProperty selectedProperty; - CheckListCell(Callback selectedGetter, ObservableValue switchIndicator) { - this.selectedGetter = selectedGetter; - this.stateProperty = new SimpleObjectProperty<>(State.SELECTION); - switchIndicator.addListener(this::changeState); + CheckListCell() { setPadding(new Insets(6)); setAlignment(Pos.CENTER_LEFT); setContentDisplay(ContentDisplay.LEFT); getStyleClass().add("label"); } - private void changeState(ObservableValue observableValue, boolean oldValue, boolean newValue) { - if (newValue) { - stateProperty.set(State.RUN); - } else { - stateProperty.set(State.SELECTION); - } - } - @Override protected void updateItem(HealthCheckTask item, boolean empty) { super.updateItem(item, empty); if (item != null) { setText(item.getTitle()); - } - switch (stateProperty.get()) { - case SELECTION -> updateItemSelection(item, empty); - case RUN -> updateItemRun(item, empty); - } - } - - private void updateItemSelection(HealthCheckTask item, boolean empty) { - if (!empty) { - setGraphic(checkBox); - - if (selectedProperty != null) { - checkBox.selectedProperty().unbindBidirectional(selectedProperty); - } - selectedProperty = selectedGetter.call(item); - if (selectedProperty != null) { - checkBox.selectedProperty().bindBidirectional(selectedProperty); - } - } else { - setGraphic(null); - setText(null); - } - } - - private void updateItemRun(HealthCheckTask item, boolean empty) { - if (item != null) { item.stateProperty().addListener(this::stateChanged); graphicProperty().bind(Bindings.createObjectBinding(() -> graphicForState(item.getState()), item.stateProperty())); stateIcon.setGlyph(glyphForState(item.getState())); + checkBox.selectedProperty().bindBidirectional(item.chosenForExecutionProperty()); } else { graphicProperty().unbind(); setGraphic(null); setText(null); + checkBox.selectedProperty().unbind(); } } @@ -92,7 +53,7 @@ class CheckListCell extends ListCell { private Node graphicForState(Worker.State state) { return switch (state) { - case READY -> null; + case READY -> checkBox; case SCHEDULED, RUNNING, FAILED, CANCELLED, SUCCEEDED -> stateIcon; }; } @@ -108,8 +69,4 @@ class CheckListCell extends ListCell { }; } - private enum State { - SELECTION, - RUN; - } } diff --git a/src/main/java/org/cryptomator/ui/health/CheckListController.java b/src/main/java/org/cryptomator/ui/health/CheckListController.java index 5c7e5af7d..7710eb14d 100644 --- a/src/main/java/org/cryptomator/ui/health/CheckListController.java +++ b/src/main/java/org/cryptomator/ui/health/CheckListController.java @@ -1,6 +1,7 @@ package org.cryptomator.ui.health; import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; import com.tobiasdiez.easybind.EasyBind; import dagger.Lazy; import org.cryptomator.ui.common.ErrorComponent; @@ -10,28 +11,24 @@ 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.IntegerBinding; import javafx.beans.property.BooleanProperty; -import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; import javafx.concurrent.Worker; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.CheckBox; import javafx.scene.control.ListView; -import javafx.scene.control.cell.CheckBoxListCell; import javafx.stage.Stage; -import javafx.util.StringConverter; import java.io.IOException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import java.util.Set; import java.util.concurrent.ExecutorService; @@ -43,6 +40,7 @@ public class CheckListController implements FxController { private final Stage window; private final ObservableList tasks; + private final FilteredList chosenTasks; private final ReportWriter reportWriter; private final ExecutorService executorService; private final ObjectProperty selectedTask; @@ -50,19 +48,18 @@ public class CheckListController implements FxController { private final SimpleObjectProperty> runningTask; private final Binding running; private final Binding finished; - private final Map listPickIndicators; - private final IntegerProperty numberOfPickedChecks; + private final IntegerBinding chosenTaskCount; private final BooleanBinding anyCheckSelected; private final BooleanProperty showResultScreen; /* FXML */ public ListView checksListView; - @Inject - public CheckListController(@HealthCheckWindow Stage window, Lazy> tasks, ReportWriter reportWriteTask, ObjectProperty selectedTask, ExecutorService executorService, Lazy errorComponentBuilder) { + public CheckListController(@HealthCheckWindow Stage window, Lazy> tasks, ReportWriter reportWriteTask, ObjectProperty selectedTask, ExecutorService executorService, Lazy errorComponentBuilder) { this.window = window; - this.tasks = FXCollections.observableArrayList(tasks.get()); + this.tasks = FXCollections.observableList(tasks.get(), HealthCheckTask::observables); + this.chosenTasks = this.tasks.filtered(HealthCheckTask::isChosenForExecution); this.reportWriter = reportWriteTask; this.executorService = executorService; this.selectedTask = selectedTask; @@ -70,13 +67,7 @@ public class CheckListController implements FxController { this.runningTask = new SimpleObjectProperty<>(); this.running = EasyBind.wrapNullable(runningTask).mapObservable(Worker::runningProperty).orElse(false); this.finished = EasyBind.wrapNullable(runningTask).mapObservable(Worker::stateProperty).map(END_STATES::contains).orElse(false); - this.listPickIndicators = new HashMap<>(); - this.numberOfPickedChecks = new SimpleIntegerProperty(0); - this.tasks.forEach(task -> { - var entrySelectedProp = new SimpleBooleanProperty(false); - entrySelectedProp.addListener((observable, oldValue, newValue) -> numberOfPickedChecks.set(numberOfPickedChecks.get() + (newValue ? 1 : -1))); - listPickIndicators.put(task, entrySelectedProp); - }); + this.chosenTaskCount = Bindings.size(this.chosenTasks); this.anyCheckSelected = selectedTask.isNotNull(); this.showResultScreen = new SimpleBooleanProperty(false); } @@ -84,27 +75,31 @@ public class CheckListController implements FxController { @FXML public void initialize() { checksListView.setItems(tasks); - checksListView.setCellFactory(view -> new CheckListCell(listPickIndicators::get, showResultScreen)); + checksListView.setCellFactory(view -> new CheckListCell()); selectedTask.bind(checksListView.getSelectionModel().selectedItemProperty()); } @FXML public void toggleSelectAll(ActionEvent event) { if (event.getSource() instanceof CheckBox c) { - listPickIndicators.forEach( (task, pickProperty) -> pickProperty.set(c.isSelected())); + tasks.forEach(t -> t.chosenForExecutionProperty().set(c.isSelected())); } } @FXML public void runSelectedChecks() { Preconditions.checkState(runningTask.get() == null); - var batch = checksListView.getItems().filtered(item -> listPickIndicators.get(item).get()); - var batchService = new BatchService(batch); + + // prevent further interaction by cancelling non-chosen tasks: + tasks.filtered(Predicates.not(chosenTasks::contains)).forEach(HealthCheckTask::cancel); + + // run chosen tasks: + var batchService = new BatchService(chosenTasks); batchService.setExecutor(executorService); batchService.start(); runningTask.set(batchService); showResultScreen.set(true); - checksListView.getSelectionModel().select(batch.get(0)); + checksListView.getSelectionModel().select(chosenTasks.get(0)); checksListView.refresh(); window.sizeToScene(); } @@ -158,13 +153,12 @@ public class CheckListController implements FxController { return showResultScreen; } - public int getNumberOfPickedChecks() { - return numberOfPickedChecks.get(); + public int getChosenTaskCount() { + return chosenTaskCount.getValue(); } - public IntegerProperty numberOfPickedChecksProperty() { - return numberOfPickedChecks; + public IntegerBinding chosenTaskCountProperty() { + return chosenTaskCount; } - } diff --git a/src/main/java/org/cryptomator/ui/health/HealthCheckModule.java b/src/main/java/org/cryptomator/ui/health/HealthCheckModule.java index 061aee4ce..78643f011 100644 --- a/src/main/java/org/cryptomator/ui/health/HealthCheckModule.java +++ b/src/main/java/org/cryptomator/ui/health/HealthCheckModule.java @@ -19,8 +19,6 @@ import org.cryptomator.ui.keyloading.KeyLoadingComponent; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.cryptomator.ui.mainwindow.MainWindow; -import javax.annotation.Nullable; -import javax.inject.Named; import javax.inject.Provider; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -30,6 +28,7 @@ import javafx.stage.Modality; import javafx.stage.Stage; import java.security.SecureRandom; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; @@ -65,7 +64,7 @@ abstract class HealthCheckModule { /* Only inject with Lazy-Wrapper!*/ @Provides @HealthCheckScoped - static Collection provideAvailableHealthCheckTasks(Collection availableHealthChecks, @HealthCheckWindow Vault vault, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, SecureRandom csprng, ResourceBundle resourceBundle) { + static List provideAvailableHealthCheckTasks(Collection availableHealthChecks, @HealthCheckWindow Vault vault, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, SecureRandom csprng, ResourceBundle resourceBundle) { return availableHealthChecks.stream().map(check -> new HealthCheckTask(vault.getPath(), vaultConfigRef.get(), masterkeyRef.get(), csprng, check, resourceBundle)).toList(); } diff --git a/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java b/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java index e64891fd4..03d67ebef 100644 --- a/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java +++ b/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java @@ -9,7 +9,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javafx.application.Platform; +import javafx.beans.Observable; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.LongProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleLongProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -34,6 +37,7 @@ class HealthCheckTask extends Task { private final HealthCheck check; private final ObservableList results; private final LongProperty durationInMillis; + private final BooleanProperty chosenForExecution; public HealthCheckTask(Path vaultPath, VaultConfig vaultConfig, Masterkey masterkey, SecureRandom csprng, HealthCheck check, ResourceBundle resourceBundle) { this.vaultPath = Objects.requireNonNull(vaultPath); @@ -49,6 +53,7 @@ class HealthCheckTask extends Task { updateTitle(check.identifier()); } this.durationInMillis = new SimpleLongProperty(-1); + this.chosenForExecution = new SimpleBooleanProperty(); } @Override @@ -60,21 +65,10 @@ class HealthCheckTask extends Task { if (isCancelled()) { throw new CancellationException(); } - // FIXME: slowdown for demonstration purposes only: - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - if (isCancelled()) { - return; - } else { - Thread.currentThread().interrupt(); - throw new RuntimeException(e); - } - } Platform.runLater(() -> results.add(result)); }); } - Platform.runLater(() ->durationInMillis.set(Duration.between(start, Instant.now()).toMillis())); + Platform.runLater(() -> durationInMillis.set(Duration.between(start, Instant.now()).toMillis())); return null; } @@ -90,6 +84,10 @@ class HealthCheckTask extends Task { /* Getter */ + Observable[] observables() { + return new Observable[]{results, chosenForExecution}; + } + public ObservableList results() { return results; } @@ -106,4 +104,11 @@ class HealthCheckTask extends Task { return durationInMillis.get(); } + public BooleanProperty chosenForExecutionProperty() { + return chosenForExecution; + } + + public boolean isChosenForExecution() { + return chosenForExecution.get(); + } } diff --git a/src/main/resources/fxml/health_check_list.fxml b/src/main/resources/fxml/health_check_list.fxml index a3a9c2f93..73344171f 100644 --- a/src/main/resources/fxml/health_check_list.fxml +++ b/src/main/resources/fxml/health_check_list.fxml @@ -3,13 +3,13 @@ + + - -