diff --git a/README.md b/README.md
index bcff4c978..5e847208c 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
- File names get encrypted
- Folder structure gets obfuscated
- Use as many vaults in your Dropbox as you want, each having individual passwords
-- Two thousand commits for the security of your data!! :tada:
+- Three thousand commits for the security of your data!! :tada:
### Privacy
diff --git a/pom.xml b/pom.xml
index 1a86c335e..a6bc06257 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
com.github.serceman,com.github.jnr,org.ow2.asm,net.java.dev.jna,org.apache.jackrabbit,org.apache.httpcomponents
- 2.1.0-beta7
+ 2.1.0-beta8
1.0.0-rc1
1.0.0-beta2
1.0.0-beta2
diff --git a/src/main/java/org/cryptomator/common/keychain/KeychainManager.java b/src/main/java/org/cryptomator/common/keychain/KeychainManager.java
index 64db08018..57af6c685 100644
--- a/src/main/java/org/cryptomator/common/keychain/KeychainManager.java
+++ b/src/main/java/org/cryptomator/common/keychain/KeychainManager.java
@@ -59,8 +59,10 @@ public class KeychainManager implements KeychainAccessProvider {
@Override
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
- getKeychainOrFail().changePassphrase(key, passphrase);
- setPassphraseStored(key, true);
+ if (isPassphraseStored(key)) {
+ getKeychainOrFail().changePassphrase(key, passphrase);
+ setPassphraseStored(key, true);
+ }
}
@Override
diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java
index 4b4e02ed2..28b8384bb 100644
--- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java
+++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java
@@ -5,8 +5,8 @@ import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
-import org.cryptomator.cryptofs.VaultCipherCombo;
import org.cryptomator.cryptolib.api.CryptoException;
+import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
@@ -182,7 +182,7 @@ public class CreateNewVaultPasswordController implements FxController {
// 2. initialize vault:
try {
MasterkeyLoader loader = ignored -> masterkey.clone();
- CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(VaultCipherCombo.SIV_CTRMAC).withKeyLoader(loader).build();
+ CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties().withCipherCombo(CryptorProvider.Scheme.SIV_CTRMAC).withKeyLoader(loader).build();
CryptoFileSystemProvider.initialize(path, fsProps, DEFAULT_KEY_ID);
// 3. write vault-internal readme file:
diff --git a/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java b/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java
index a273447bb..6ec3cddb4 100644
--- a/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java
+++ b/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java
@@ -39,7 +39,7 @@ public enum FontAwesome5Icon {
REDO("\uF01E"), //
SEARCH("\uF002"), //
SPINNER("\uF110"), //
- STOPWATCH("\uF2F2"), //
+ STETHOSCOPE("\uF0f1"), //
SYNC("\uF021"), //
TIMES("\uF00D"), //
TRASH("\uF1F8"), //
diff --git a/src/main/java/org/cryptomator/ui/controls/FontAwesome5IconView.java b/src/main/java/org/cryptomator/ui/controls/FontAwesome5IconView.java
index 4d0797eaa..3bfa70c46 100644
--- a/src/main/java/org/cryptomator/ui/controls/FontAwesome5IconView.java
+++ b/src/main/java/org/cryptomator/ui/controls/FontAwesome5IconView.java
@@ -21,8 +21,8 @@ public class FontAwesome5IconView extends Text {
private static final String FONT_PATH = "/css/fontawesome5-free-solid.otf";
private static final Font FONT;
- private ObjectProperty glyph = new SimpleObjectProperty<>(this, "glyph", DEFAULT_GLYPH);
- private DoubleProperty glyphSize = new SimpleDoubleProperty(this, "glyphSize", DEFAULT_GLYPH_SIZE);
+ private final ObjectProperty glyph = new SimpleObjectProperty<>(this, "glyph", DEFAULT_GLYPH);
+ private final DoubleProperty glyphSize = new SimpleDoubleProperty(this, "glyphSize", DEFAULT_GLYPH_SIZE);
static {
try {
@@ -42,7 +42,7 @@ public class FontAwesome5IconView extends Text {
}
private void glyphChanged(@SuppressWarnings("unused") ObservableValue extends FontAwesome5Icon> observable, @SuppressWarnings("unused") FontAwesome5Icon oldValue, FontAwesome5Icon newValue) {
- setText(newValue.unicode());
+ setText(newValue == null ? null : newValue.unicode());
}
private void glyphSizeChanged(@SuppressWarnings("unused") ObservableValue extends Number> observable, @SuppressWarnings("unused") Number oldValue, Number newValue) {
diff --git a/src/main/java/org/cryptomator/ui/health/BatchService.java b/src/main/java/org/cryptomator/ui/health/BatchService.java
index f3968c27d..40f4e173f 100644
--- a/src/main/java/org/cryptomator/ui/health/BatchService.java
+++ b/src/main/java/org/cryptomator/ui/health/BatchService.java
@@ -16,7 +16,6 @@ public class BatchService extends Service {
private final Iterator remainingTasks;
- @Inject
public BatchService(Iterable tasks) {
this.remainingTasks = tasks.iterator();
}
diff --git a/src/main/java/org/cryptomator/ui/health/CheckDetailController.java b/src/main/java/org/cryptomator/ui/health/CheckDetailController.java
index 68a74c4a0..7ccc9e09c 100644
--- a/src/main/java/org/cryptomator/ui/health/CheckDetailController.java
+++ b/src/main/java/org/cryptomator/ui/health/CheckDetailController.java
@@ -23,7 +23,7 @@ import java.util.stream.Stream;
@HealthCheckScoped
public class CheckDetailController implements FxController {
- private final EasyObservableList results;
+ private final EasyObservableList results;
private final OptionalBinding taskState;
private final Binding taskName;
private final Binding taskDuration;
@@ -39,7 +39,7 @@ public class CheckDetailController implements FxController {
private final ResultListCellFactory resultListCellFactory;
private final ResourceBundle resourceBundle;
- public ListView resultsListView;
+ public ListView resultsListView;
private Subscription resultSubscription;
@Inject
@@ -71,8 +71,8 @@ public class CheckDetailController implements FxController {
}
}
- private Function, Long> countSeverity(DiagnosticResult.Severity severity) {
- return stream -> stream.filter(item -> severity.equals(item.getSeverity())).count();
+ private Function, Long> countSeverity(DiagnosticResult.Severity severity) {
+ return stream -> stream.filter(item -> severity.equals(item.diagnosis().getSeverity())).count();
}
@FXML
diff --git a/src/main/java/org/cryptomator/ui/health/CheckListCell.java b/src/main/java/org/cryptomator/ui/health/CheckListCell.java
index 63d9d8d6b..76a8f3c27 100644
--- a/src/main/java/org/cryptomator/ui/health/CheckListCell.java
+++ b/src/main/java/org/cryptomator/ui/health/CheckListCell.java
@@ -1,13 +1,10 @@
package org.cryptomator.ui.health;
+import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.cryptomator.ui.controls.FontAwesome5IconView;
import javafx.beans.binding.Bindings;
-import javafx.beans.property.BooleanProperty;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
@@ -15,101 +12,60 @@ import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ListCell;
-import javafx.util.Callback;
+import java.util.function.Predicate;
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 extends Boolean> 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()));
+ stateIcon.glyphProperty().bind(Bindings.createObjectBinding(() -> glyphForState(item), item.stateProperty()));
+ checkBox.selectedProperty().bindBidirectional(item.chosenForExecutionProperty());
} else {
graphicProperty().unbind();
setGraphic(null);
setText(null);
+ checkBox.selectedProperty().unbind();
}
}
- private void stateChanged(ObservableValue extends Worker.State> observable, Worker.State oldState, Worker.State newState) {
- stateIcon.setGlyph(glyphForState(newState));
- stateIcon.setVisible(true);
- }
-
private Node graphicForState(Worker.State state) {
return switch (state) {
- case READY -> null;
+ case READY -> checkBox;
case SCHEDULED, RUNNING, FAILED, CANCELLED, SUCCEEDED -> stateIcon;
};
}
- private FontAwesome5Icon glyphForState(Worker.State state) {
- return switch (state) {
+ private FontAwesome5Icon glyphForState(HealthCheckTask item) {
+ return switch (item.getState()) {
case READY -> FontAwesome5Icon.COG; //just a placeholder
case SCHEDULED -> FontAwesome5Icon.CLOCK;
case RUNNING -> FontAwesome5Icon.SPINNER;
case FAILED -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
case CANCELLED -> FontAwesome5Icon.BAN;
- case SUCCEEDED -> FontAwesome5Icon.CHECK;
+ case SUCCEEDED -> checkFoundProblems(item) ? FontAwesome5Icon.EXCLAMATION_TRIANGLE : FontAwesome5Icon.CHECK;
};
}
- private enum State {
- SELECTION,
- RUN;
+ private boolean checkFoundProblems(HealthCheckTask item) {
+ Predicate isProblem = severity -> switch (severity) {
+ case WARN, CRITICAL -> true;
+ case INFO, GOOD -> false;
+ };
+ return item.results().stream().map(Result::diagnosis).map(DiagnosticResult::getSeverity).anyMatch(isProblem);
}
+
}
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/DummyHealthChecks.java b/src/main/java/org/cryptomator/ui/health/DummyHealthChecks.java
new file mode 100644
index 000000000..d2bccff37
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/health/DummyHealthChecks.java
@@ -0,0 +1,41 @@
+package org.cryptomator.ui.health;
+
+import org.cryptomator.cryptofs.VaultConfig;
+import org.cryptomator.cryptofs.health.api.DiagnosticResult;
+import org.cryptomator.cryptofs.health.api.HealthCheck;
+import org.cryptomator.cryptolib.api.Cryptor;
+import org.cryptomator.cryptolib.api.Masterkey;
+
+import java.nio.file.Path;
+import java.util.function.Consumer;
+
+/**
+ * FIXME: Remove in production release
+ */
+public class DummyHealthChecks {
+
+ public static class DummyCheck1 implements HealthCheck {
+
+ @Override
+ public void check(Path path, VaultConfig vaultConfig, Masterkey masterkey, Cryptor cryptor, Consumer consumer) {
+ // no-op
+ }
+ }
+
+ public static class DummyCheck2 implements HealthCheck {
+
+ @Override
+ public void check(Path path, VaultConfig vaultConfig, Masterkey masterkey, Cryptor cryptor, Consumer consumer) {
+ // no-op
+ }
+ }
+
+ public static class DummyCheck3 implements HealthCheck {
+
+ @Override
+ public void check(Path path, VaultConfig vaultConfig, Masterkey masterkey, Cryptor cryptor, Consumer consumer) {
+ // no-op
+ }
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/health/HealthCheckComponent.java b/src/main/java/org/cryptomator/ui/health/HealthCheckComponent.java
index 48b16f694..365ab63df 100644
--- a/src/main/java/org/cryptomator/ui/health/HealthCheckComponent.java
+++ b/src/main/java/org/cryptomator/ui/health/HealthCheckComponent.java
@@ -7,6 +7,7 @@ import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
+import javax.inject.Named;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -17,6 +18,9 @@ public interface HealthCheckComponent {
@HealthCheckWindow
Stage window();
+ @Named("windowToClose")
+ Stage windowToClose();
+
@FxmlScene(FxmlFile.HEALTH_START)
Lazy scene();
@@ -24,6 +28,7 @@ public interface HealthCheckComponent {
Stage stage = window();
stage.setScene(scene().get());
stage.show();
+ windowToClose().close();
return stage;
}
@@ -33,6 +38,9 @@ public interface HealthCheckComponent {
@BindsInstance
Builder vault(@HealthCheckWindow Vault vault);
+ @BindsInstance
+ Builder windowToClose(@Named("windowToClose") Stage window);
+
HealthCheckComponent build();
}
diff --git a/src/main/java/org/cryptomator/ui/health/HealthCheckModule.java b/src/main/java/org/cryptomator/ui/health/HealthCheckModule.java
index e33a9f2f1..78643f011 100644
--- a/src/main/java/org/cryptomator/ui/health/HealthCheckModule.java
+++ b/src/main/java/org/cryptomator/ui/health/HealthCheckModule.java
@@ -28,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;
@@ -63,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();
}
@@ -86,10 +87,10 @@ abstract class HealthCheckModule {
@HealthCheckScoped
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.setTitle(resourceBundle.getString("health.title"));
+ stage.setResizable(true);
stage.showingProperty().addListener(showingListener); // bind masterkey lifecycle to window
return stage;
}
diff --git a/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java b/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java
index 7acbfc1c2..3efe333b3 100644
--- a/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java
+++ b/src/main/java/org/cryptomator/ui/health/HealthCheckTask.java
@@ -3,12 +3,16 @@ package org.cryptomator.ui.health;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.cryptofs.health.api.HealthCheck;
+import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.Masterkey;
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;
@@ -31,8 +35,9 @@ class HealthCheckTask extends Task {
private final Masterkey masterkey;
private final SecureRandom csprng;
private final HealthCheck check;
- private final ObservableList results;
+ 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);
@@ -40,7 +45,7 @@ class HealthCheckTask extends Task {
this.masterkey = Objects.requireNonNull(masterkey);
this.csprng = Objects.requireNonNull(csprng);
this.check = Objects.requireNonNull(check);
- this.results = FXCollections.observableArrayList();
+ this.results = FXCollections.observableArrayList(Result::observables);
try {
updateTitle(resourceBundle.getString("health." + check.identifier()));
} catch (MissingResourceException e) {
@@ -48,32 +53,22 @@ class HealthCheckTask extends Task {
updateTitle(check.identifier());
}
this.durationInMillis = new SimpleLongProperty(-1);
+ this.chosenForExecution = new SimpleBooleanProperty();
}
@Override
protected Void call() {
Instant start = Instant.now();
try (var masterkeyClone = masterkey.clone(); //
- var cryptor = vaultConfig.getCipherCombo().getCryptorProvider(csprng).withKey(masterkeyClone)) {
- check.check(vaultPath, vaultConfig, masterkeyClone, cryptor, result -> {
+ var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
+ check.check(vaultPath, vaultConfig, masterkeyClone, cryptor, diagnosis -> {
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(() -> 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()));
return null;
}
@@ -89,7 +84,11 @@ class HealthCheckTask extends Task {
/* Getter */
- public ObservableList results() {
+ Observable[] observables() {
+ return new Observable[]{results, chosenForExecution};
+ }
+
+ public ObservableList results() {
return results;
}
@@ -105,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/java/org/cryptomator/ui/health/ReportWriter.java b/src/main/java/org/cryptomator/ui/health/ReportWriter.java
index 2bc4c28d9..6039901ef 100644
--- a/src/main/java/org/cryptomator/ui/health/ReportWriter.java
+++ b/src/main/java/org/cryptomator/ui/health/ReportWriter.java
@@ -72,7 +72,7 @@ public class ReportWriter {
case SUCCEEDED -> {
writer.write("STATUS: SUCCESS\nRESULTS:\n");
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");
diff --git a/src/main/java/org/cryptomator/ui/health/Result.java b/src/main/java/org/cryptomator/ui/health/Result.java
new file mode 100644
index 000000000..23d812f50
--- /dev/null
+++ b/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) {
+
+ 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);
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/ui/health/ResultFixApplier.java b/src/main/java/org/cryptomator/ui/health/ResultFixApplier.java
index 3c5403ab7..841a8f5c4 100644
--- a/src/main/java/org/cryptomator/ui/health/ResultFixApplier.java
+++ b/src/main/java/org/cryptomator/ui/health/ResultFixApplier.java
@@ -4,25 +4,30 @@ import com.google.common.base.Preconditions;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.health.api.DiagnosticResult;
+import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.Masterkey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import javafx.scene.control.Alert;
+import javafx.application.Platform;
import java.nio.file.Path;
import java.security.SecureRandom;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
@HealthCheckScoped
class ResultFixApplier {
- private static final Logger LOG = LoggerFactory.getLogger(ResultFixApplier.class);
-
private final Path vaultPath;
private final SecureRandom csprng;
private final Masterkey masterkey;
private final VaultConfig vaultConfig;
+ private final ExecutorService sequentialExecutor;
@Inject
public ResultFixApplier(@HealthCheckWindow Vault vault, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, SecureRandom csprng) {
@@ -30,18 +35,32 @@ class ResultFixApplier {
this.masterkey = masterkeyRef.get();
this.vaultConfig = vaultConfigRef.get();
this.csprng = csprng;
+ this.sequentialExecutor = Executors.newSingleThreadExecutor();
}
- public void fix(DiagnosticResult result) {
- Preconditions.checkArgument(result.getSeverity() == DiagnosticResult.Severity.WARN, "Unfixable result");
+ public CompletionStage fix(Result result) {
+ Preconditions.checkArgument(result.getState() == Result.FixState.FIXABLE);
+ result.setState(Result.FixState.FIXING);
+ return CompletableFuture.runAsync(() -> fix(result.diagnosis()), sequentialExecutor)
+ .whenCompleteAsync((unused, throwable) -> {
+ var fixed = throwable == null ? Result.FixState.FIXED : Result.FixState.FIX_FAILED;
+ result.setState(fixed);
+ }, Platform::runLater);
+ }
+
+ public void fix(DiagnosticResult diagnosis) {
+ Preconditions.checkArgument(diagnosis.getSeverity() == DiagnosticResult.Severity.WARN, "Unfixable result");
try (var masterkeyClone = masterkey.clone(); //
- var cryptor = vaultConfig.getCipherCombo().getCryptorProvider(csprng).withKey(masterkeyClone)) {
- result.fix(vaultPath, vaultConfig, masterkeyClone, cryptor);
+ var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
+ diagnosis.fix(vaultPath, vaultConfig, masterkeyClone, cryptor);
} catch (Exception e) {
- LOG.error("Failed to apply fix", e);
- Alert alert = new Alert(Alert.AlertType.ERROR, e.getMessage());
- alert.showAndWait();
- //TODO: real error/not supported handling
+ throw new FixFailedException(e);
+ }
+ }
+
+ public static class FixFailedException extends CompletionException {
+ private FixFailedException(Throwable cause) {
+ super(cause);
}
}
}
diff --git a/src/main/java/org/cryptomator/ui/health/ResultListCellController.java b/src/main/java/org/cryptomator/ui/health/ResultListCellController.java
index 4506bc602..683d0fb93 100644
--- a/src/main/java/org/cryptomator/ui/health/ResultListCellController.java
+++ b/src/main/java/org/cryptomator/ui/health/ResultListCellController.java
@@ -1,84 +1,89 @@
package org.cryptomator.ui.health;
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.controls.FontAwesome5Icon;
import org.cryptomator.ui.controls.FontAwesome5IconView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.inject.Inject;
+import javafx.application.Platform;
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.SimpleObjectProperty;
-import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
// unscoped because each cell needs its own controller
public class ResultListCellController implements FxController {
- private final ResultFixApplier fixApplier;
- private final ObjectProperty result;
+ private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
+
+ private final ObjectProperty result;
private final Binding description;
+ private final ResultFixApplier fixApplier;
+ private final OptionalBinding fixState;
+ private final ObjectBinding glyph;
+ private final BooleanBinding fixable;
+ private final BooleanBinding fixing;
+ private final BooleanBinding fixed;
public FontAwesome5IconView iconView;
- public Button actionButton;
+ public Button fixButton;
@Inject
public ResultListCellController(ResultFixApplier fixApplier) {
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;
- 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
- public void runResultAction() {
- final var realResult = result.get();
- if (realResult != null) {
- fixApplier.fix(realResult);
+ 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));
+ }
+
+ @FXML
+ public void fix() {
+ Result r = result.get();
+ if (r != null) {
+ fixApplier.fix(r).whenCompleteAsync(this::fixFinished, Platform::runLater);
}
}
+
+ private void fixFinished(Void unused, Throwable exception) {
+ if (exception != null) {
+ LOG.error("Failed to apply fix", exception);
+ // TODO ...
+ }
+ }
+
+
/* Getter & Setter */
-
- public DiagnosticResult getResult() {
+ public Result getResult() {
return result.get();
}
- public void setResult(DiagnosticResult result) {
+ public void setResult(Result result) {
this.result.set(result);
}
- public ObjectProperty resultProperty() {
+ public ObjectProperty resultProperty() {
return result;
}
@@ -86,7 +91,49 @@ public class ResultListCellController implements FxController {
return description.getValue();
}
+ public ObjectBinding 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 descriptionProperty() {
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);
+ }
+
}
diff --git a/src/main/java/org/cryptomator/ui/health/ResultListCellFactory.java b/src/main/java/org/cryptomator/ui/health/ResultListCellFactory.java
index 7acada487..86c793bf7 100644
--- a/src/main/java/org/cryptomator/ui/health/ResultListCellFactory.java
+++ b/src/main/java/org/cryptomator/ui/health/ResultListCellFactory.java
@@ -1,7 +1,6 @@
package org.cryptomator.ui.health;
-import org.cryptomator.cryptofs.health.api.DiagnosticResult;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import javax.inject.Inject;
@@ -15,7 +14,7 @@ import java.io.IOException;
import java.io.UncheckedIOException;
@HealthCheckScoped
-public class ResultListCellFactory implements Callback, ListCell> {
+public class ResultListCellFactory implements Callback, ListCell> {
private final FxmlLoaderFactory fxmlLoaders;
@@ -25,7 +24,7 @@ public class ResultListCellFactory implements Callback call(ListView param) {
+ public ListCell call(ListView param) {
try {
FXMLLoader fxmlLoader = fxmlLoaders.load("/fxml/health_result_listcell.fxml");
return new ResultListCellFactory.Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
@@ -34,7 +33,7 @@ public class ResultListCellFactory implements Callback {
+ private static class Cell extends ListCell {
private final Parent node;
private final ResultListCellController controller;
@@ -45,7 +44,7 @@ public class ResultListCellFactory implements Callback unverifiedVaultConfig;
+ private final CompletableFuture unverifiedVaultConfig;
private final KeyLoadingStrategy keyLoadingStrategy;
private final ExecutorService executor;
private final AtomicReference masterkeyRef;
private final AtomicReference vaultConfigRef;
private final Lazy checkScene;
private final Lazy errorComponent;
+ private final ObjectProperty state = new SimpleObjectProperty<>(State.LOADING);
+ private final BooleanBinding loading = state.isEqualTo(State.LOADING);
+ private final BooleanBinding failed = state.isEqualTo(State.FAILED);
+ private final BooleanBinding loaded = state.isEqualTo(State.LOADED);
+
+ public enum State {
+ LOADING,
+ FAILED,
+ LOADED
+ }
/* FXML */
@Inject
public StartController(@HealthCheckWindow Vault vault, @HealthCheckWindow Stage window, @HealthCheckWindow KeyLoadingStrategy keyLoadingStrategy, ExecutorService executor, AtomicReference masterkeyRef, AtomicReference vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy checkScene, Lazy errorComponent) {
+ this.vault = vault;
this.window = window;
+ this.unverifiedVaultConfig = CompletableFuture.supplyAsync(this::loadConfig, executor);
this.keyLoadingStrategy = keyLoadingStrategy;
this.executor = executor;
this.masterkeyRef = masterkeyRef;
this.vaultConfigRef = vaultConfigRef;
this.checkScene = checkScene;
this.errorComponent = errorComponent;
-
- //TODO: this is ugly
- //idea: delay the loading of the vault config and show a spinner (something like "check/load config") and react to the result of the loading
- //or: load vault config in a previous step to see if it is loadable.
- VaultConfig.UnverifiedVaultConfig tmp;
- try {
- tmp = vault.getUnverifiedVaultConfig();
- } catch (IOException e) {
- e.printStackTrace();
- tmp = null;
- }
- this.unverifiedVaultConfig = Optional.ofNullable(tmp);
+ this.unverifiedVaultConfig.whenCompleteAsync(this::loadedConfig, Platform::runLater);
}
@FXML
@@ -74,41 +81,64 @@ public class StartController implements FxController {
@FXML
public void next() {
LOG.trace("StartController.next()");
- executor.submit(this::loadKey);
+ CompletableFuture.runAsync(this::loadKey, executor).whenCompleteAsync(this::loadedKey, Platform::runLater);
+ }
+
+ private VaultConfig.UnverifiedVaultConfig loadConfig() {
+ assert !Platform.isFxApplicationThread();
+ try {
+ return this.vault.getUnverifiedVaultConfig();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private void loadedConfig(VaultConfig.UnverifiedVaultConfig cfg, Throwable exception) {
+ assert Platform.isFxApplicationThread();
+ if (exception != null) {
+ state.set(State.FAILED);
+ } else {
+ assert cfg != null;
+ state.set(State.LOADED);
+ }
}
private void loadKey() {
assert !Platform.isFxApplicationThread();
- assert unverifiedVaultConfig.isPresent();
- try (var masterkey = keyLoadingStrategy.loadKey(unverifiedVaultConfig.orElseThrow().getKeyId())) {
- var unverifiedCfg = unverifiedVaultConfig.get();
+ assert unverifiedVaultConfig.isDone();
+ var unverifiedCfg = unverifiedVaultConfig.join();
+ try (var masterkey = keyLoadingStrategy.loadKey(unverifiedCfg.getKeyId())) {
var verifiedCfg = unverifiedCfg.verify(masterkey.getEncoded(), unverifiedCfg.allegedVaultVersion());
vaultConfigRef.set(verifiedCfg);
var old = masterkeyRef.getAndSet(masterkey.clone());
if (old != null) {
old.destroy();
}
- Platform.runLater(this::loadedKey);
} catch (MasterkeyLoadingFailedException e) {
if (keyLoadingStrategy.recoverFromException(e)) {
// retry
loadKey();
} else {
- Platform.runLater(() -> loadingKeyFailed(e));
+ throw new LoadingFailedException(e);
}
- } catch (VaultKeyInvalidException e) {
- Platform.runLater(() -> loadingKeyFailed(e));
} catch (VaultConfigLoadException e) {
- Platform.runLater(() -> loadingKeyFailed(e));
+ throw new LoadingFailedException(e);
}
}
- private void loadedKey() {
- LOG.debug("Loaded valid key");
- window.setScene(checkScene.get());
+ private void loadedKey(Void unused, Throwable exception) {
+ assert Platform.isFxApplicationThread();
+ if (exception instanceof LoadingFailedException) {
+ loadingKeyFailed(exception.getCause());
+ } else if (exception != null) {
+ loadingKeyFailed(exception);
+ } else {
+ LOG.debug("Loaded valid key");
+ window.setScene(checkScene.get());
+ }
}
- private void loadingKeyFailed(Exception e) {
+ private void loadingKeyFailed(Throwable e) {
if (e instanceof UnlockCancelledException) {
// ok
} else if (e instanceof VaultKeyInvalidException) {
@@ -120,8 +150,37 @@ public class StartController implements FxController {
}
}
- public boolean isInvalidConfig() {
- return unverifiedVaultConfig.isEmpty();
+ /* Getter */
+
+ public BooleanBinding loadingProperty() {
+ return loading;
}
+ public boolean isLoading() {
+ return loading.get();
+ }
+
+ public BooleanBinding failedProperty() {
+ return failed;
+ }
+
+ public boolean isFailed() {
+ return failed.get();
+ }
+
+ public BooleanBinding loadedProperty() {
+ return loaded;
+ }
+
+ public boolean isLoaded() {
+ return loaded.get();
+ }
+
+ /* internal types */
+
+ private static class LoadingFailedException extends CompletionException {
+ LoadingFailedException(Throwable cause) {
+ super(cause);
+ }
+ }
}
diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/AutoLockVaultOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/AutoLockVaultOptionsController.java
deleted file mode 100644
index 1bb74690f..000000000
--- a/src/main/java/org/cryptomator/ui/vaultoptions/AutoLockVaultOptionsController.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.cryptomator.ui.vaultoptions;
-
-import org.cryptomator.common.vaults.Vault;
-import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.controls.NumericTextField;
-
-import javax.inject.Inject;
-import javafx.beans.binding.Bindings;
-import javafx.fxml.FXML;
-import javafx.scene.control.CheckBox;
-import javafx.util.StringConverter;
-
-@VaultOptionsScoped
-public class AutoLockVaultOptionsController implements FxController {
-
- private final Vault vault;
-
- public CheckBox lockAfterTimeCheckbox;
- public NumericTextField lockTimeInMinutesTextField;
-
- @Inject
- AutoLockVaultOptionsController(@VaultOptionsWindow Vault vault) {
- this.vault = vault;
- }
-
- @FXML
- public void initialize() {
- lockAfterTimeCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().autoLockWhenIdle());
- Bindings.bindBidirectional(lockTimeInMinutesTextField.textProperty(), vault.getVaultSettings().autoLockIdleSeconds(), new IdleTimeSecondsConverter());
- }
-
- private static class IdleTimeSecondsConverter extends StringConverter {
-
- @Override
- public String toString(Number seconds) {
- int minutes = seconds.intValue() / 60; // int-truncate
- return Integer.toString(minutes);
- }
-
- @Override
- public Number fromString(String string) {
- try {
- int minutes = Integer.valueOf(string);
- return minutes * 60;
- } catch (NumberFormatException e) {
- return 0;
- }
- }
- }
-
-}
diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java
index 79d3b53ad..0ccea096e 100644
--- a/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java
+++ b/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java
@@ -3,10 +3,11 @@ package org.cryptomator.ui.vaultoptions;
import org.cryptomator.common.settings.WhenUnlocked;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.health.HealthCheckComponent;
+import org.cryptomator.ui.controls.NumericTextField;
import javax.inject.Inject;
import javafx.beans.Observable;
+import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
@@ -23,18 +24,18 @@ public class GeneralVaultOptionsController implements FxController {
private final Stage window;
private final Vault vault;
- private final HealthCheckComponent.Builder healthCheckWindow;
private final ResourceBundle resourceBundle;
public TextField vaultName;
public CheckBox unlockOnStartupCheckbox;
public ChoiceBox actionAfterUnlockChoiceBox;
+ public CheckBox lockAfterTimeCheckbox;
+ public NumericTextField lockTimeInMinutesTextField;
@Inject
- GeneralVaultOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, HealthCheckComponent.Builder healthCheckWindow, ResourceBundle resourceBundle) {
+ GeneralVaultOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, ResourceBundle resourceBundle) {
this.window = window;
this.vault = vault;
- this.healthCheckWindow = healthCheckWindow;
this.resourceBundle = resourceBundle;
}
@@ -47,6 +48,8 @@ public class GeneralVaultOptionsController implements FxController {
actionAfterUnlockChoiceBox.getItems().addAll(WhenUnlocked.values());
actionAfterUnlockChoiceBox.valueProperty().bindBidirectional(vault.getVaultSettings().actionAfterUnlock());
actionAfterUnlockChoiceBox.setConverter(new WhenUnlockedConverter(resourceBundle));
+ lockAfterTimeCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().autoLockWhenIdle());
+ Bindings.bindBidirectional(lockTimeInMinutesTextField.textProperty(), vault.getVaultSettings().autoLockIdleSeconds(), new IdleTimeSecondsConverter());
}
private void trimVaultNameOnFocusLoss(Observable observable, Boolean wasFocussed, Boolean isFocussed) {
@@ -64,12 +67,6 @@ public class GeneralVaultOptionsController implements FxController {
}
}
- @FXML
- public void showHealthCheck() {
- healthCheckWindow.vault(vault).build().showHealthCheckWindow();
- }
-
-
private static class WhenUnlockedConverter extends StringConverter {
private final ResourceBundle resourceBundle;
@@ -89,4 +86,22 @@ public class GeneralVaultOptionsController implements FxController {
}
}
+ private static class IdleTimeSecondsConverter extends StringConverter {
+
+ @Override
+ public String toString(Number seconds) {
+ int minutes = seconds.intValue() / 60; // int-truncate
+ return Integer.toString(minutes);
+ }
+
+ @Override
+ public Number fromString(String string) {
+ try {
+ int minutes = Integer.valueOf(string);
+ return minutes * 60;
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+ }
}
diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/HealthVaultOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/HealthVaultOptionsController.java
new file mode 100644
index 000000000..7b0842e97
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/vaultoptions/HealthVaultOptionsController.java
@@ -0,0 +1,30 @@
+package org.cryptomator.ui.vaultoptions;
+
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.health.HealthCheckComponent;
+
+import javax.inject.Inject;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.stage.Stage;
+
+@VaultOptionsScoped
+public class HealthVaultOptionsController implements FxController {
+
+ private final Stage window;
+ private final Vault vault;
+ private final HealthCheckComponent.Builder healthCheckWindow;
+
+ @Inject
+ public HealthVaultOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, HealthCheckComponent.Builder healthCheckWindow) {
+ this.window = window;
+ this.vault = vault;
+ this.healthCheckWindow = healthCheckWindow;
+ }
+
+ @FXML
+ public void startHealthCheck(ActionEvent event) {
+ healthCheckWindow.vault(vault).windowToClose(window).build().showHealthCheckWindow();
+ }
+}
diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/SelectedVaultOptionsTab.java b/src/main/java/org/cryptomator/ui/vaultoptions/SelectedVaultOptionsTab.java
index 03a4922d4..bfaff147a 100644
--- a/src/main/java/org/cryptomator/ui/vaultoptions/SelectedVaultOptionsTab.java
+++ b/src/main/java/org/cryptomator/ui/vaultoptions/SelectedVaultOptionsTab.java
@@ -23,7 +23,11 @@ public enum SelectedVaultOptionsTab {
/**
* Show Auto-Lock tab
- *
*/
AUTOLOCK,
+
+ /**
+ * Show health tab
+ */
+ HEALTH;
}
diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java
index 20dac7594..662232a49 100644
--- a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java
+++ b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsController.java
@@ -24,6 +24,7 @@ public class VaultOptionsController implements FxController {
public Tab mountTab;
public Tab keyTab;
public Tab autoLockTab;
+ public Tab healthTab;
@Inject
VaultOptionsController(@VaultOptionsWindow Stage window, ObjectProperty selectedTabProperty) {
@@ -49,6 +50,7 @@ public class VaultOptionsController implements FxController {
case MOUNT -> mountTab;
case KEY -> keyTab;
case AUTOLOCK -> autoLockTab;
+ case HEALTH -> healthTab;
};
}
diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java
index 89cef234b..2de9421a0 100644
--- a/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java
+++ b/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java
@@ -86,7 +86,6 @@ abstract class VaultOptionsModule {
@Binds
@IntoMap
- @FxControllerKey(AutoLockVaultOptionsController.class)
- abstract FxController bindAutoLockVaultOptionsController(AutoLockVaultOptionsController controller);
-
+ @FxControllerKey(HealthVaultOptionsController.class)
+ abstract FxController bindHealthOptionsController(HealthVaultOptionsController controller);
}
diff --git a/src/main/resources/META-INF/services/org.cryptomator.cryptofs.health.api.HealthCheck b/src/main/resources/META-INF/services/org.cryptomator.cryptofs.health.api.HealthCheck
new file mode 100644
index 000000000..e78e709b4
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.cryptomator.cryptofs.health.api.HealthCheck
@@ -0,0 +1,3 @@
+org.cryptomator.ui.health.DummyHealthChecks$DummyCheck1
+org.cryptomator.ui.health.DummyHealthChecks$DummyCheck2
+org.cryptomator.ui.health.DummyHealthChecks$DummyCheck3
\ No newline at end of file
diff --git a/src/main/resources/fxml/health_check_list.fxml b/src/main/resources/fxml/health_check_list.fxml
index 407627891..91cd02975 100644
--- a/src/main/resources/fxml/health_check_list.fxml
+++ b/src/main/resources/fxml/health_check_list.fxml
@@ -3,17 +3,18 @@
+
+
-
-
@@ -28,7 +29,7 @@
-
+
@@ -39,7 +40,7 @@
-
+
diff --git a/src/main/resources/fxml/health_result_listcell.fxml b/src/main/resources/fxml/health_result_listcell.fxml
index 6e30dd0ec..687842917 100644
--- a/src/main/resources/fxml/health_result_listcell.fxml
+++ b/src/main/resources/fxml/health_result_listcell.fxml
@@ -6,6 +6,10 @@
+
+
+
+
-
+
-
+
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/health_start.fxml b/src/main/resources/fxml/health_start.fxml
index 79d946884..24b256fa8 100644
--- a/src/main/resources/fxml/health_start.fxml
+++ b/src/main/resources/fxml/health_start.fxml
@@ -17,23 +17,25 @@
-
-
-
-
diff --git a/src/main/resources/fxml/vault_options.fxml b/src/main/resources/fxml/vault_options.fxml
index 994853b96..1c9418052 100644
--- a/src/main/resources/fxml/vault_options.fxml
+++ b/src/main/resources/fxml/vault_options.fxml
@@ -36,12 +36,12 @@
-
+
-
+
-
+
diff --git a/src/main/resources/fxml/vault_options_autolock.fxml b/src/main/resources/fxml/vault_options_autolock.fxml
deleted file mode 100644
index 8d63b792d..000000000
--- a/src/main/resources/fxml/vault_options_autolock.fxml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/resources/fxml/vault_options_general.fxml b/src/main/resources/fxml/vault_options_general.fxml
index dce834c95..a9e18e852 100644
--- a/src/main/resources/fxml/vault_options_general.fxml
+++ b/src/main/resources/fxml/vault_options_general.fxml
@@ -8,6 +8,10 @@
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/src/main/resources/fxml/vault_options_health.fxml b/src/main/resources/fxml/vault_options_health.fxml
new file mode 100644
index 000000000..aadfa43ff
--- /dev/null
+++ b/src/main/resources/fxml/vault_options_health.fxml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties
index f4e1117da..70fca2804 100644
--- a/src/main/resources/i18n/strings.properties
+++ b/src/main/resources/i18n/strings.properties
@@ -148,7 +148,6 @@ migration.impossible.moreInfo=The vault can still be opened with an older versio
# Health Check
health.title=Vault Health Check
-health.start.introduction=The Vault Health Check is a collection of checks to detect and possibly fix problems in the internal structure of your vault. Please note, that not all problems are fixable. You need the vault password to perform the checks.
health.start.configValid=Reading and parsing vault configuration file was successful. Proceed to select checks.
health.start.configInvalid=Error while reading and parsing the vault configuration file.
health.checkList.header=Available Health Checks
@@ -304,12 +303,13 @@ wrongFileAlert.link=For further assistance, visit
## General
vaultOptions.general=General
vaultOptions.general.vaultName=Vault Name
+vaultOptions.general.autoLock.lockAfterTimePart1=Lock when idle for
+vaultOptions.general.autoLock.lockAfterTimePart2=minutes
vaultOptions.general.unlockAfterStartup=Unlock vault when starting Cryptomator
vaultOptions.general.actionAfterUnlock=After successful unlock
vaultOptions.general.actionAfterUnlock.ignore=Do nothing
vaultOptions.general.actionAfterUnlock.reveal=Reveal Drive
vaultOptions.general.actionAfterUnlock.ask=Ask
-vaultOptions.general.healthBtn=Start Health Check
## Mount
vaultOptions.mount=Mounting
vaultOptions.mount.readonly=Read-Only
@@ -328,10 +328,16 @@ vaultOptions.masterkey.forgetSavedPasswordBtn=Forget Saved Password
vaultOptions.masterkey.recoveryKeyExplanation=A recovery key is your only means to restore access to a vault if you lose your password.
vaultOptions.masterkey.showRecoveryKeyBtn=Display Recovery Key
vaultOptions.masterkey.recoverPasswordBtn=Recover Password
-## Auto Lock
-vaultOptions.autoLock=Auto-Lock
-vaultOptions.autoLock.lockAfterTimePart1=Lock when idle for
-vaultOptions.autoLock.lockAfterTimePart2=minutes
+## Health
+vaultOptions.health=Health Check
+vaultOptions.health.startBtn=Start Health Check
+vaultOptions.health.introduction=Health Check is a collection of checks to detect and possibly fix problems in the internal structure of your vault.
+vaultOptions.health.remarks=Please keep in mind:
+vaultOptions.health.remarkSync=Incomplete synchronisation causes most problems. Ensure that every device is completely synced.
+vaultOptions.health.remarkFix=Not all problems can be fixed.
+vaultOptions.health.remarkBackup=If data is corrupted, only a backup can help.
+vaultOptions.health.affirmation=I have read and understood the above information.
+
# Recovery Key
recoveryKey.title=Recovery Key