mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-04-22 18:46:53 -04:00
Merge branch 'develop' into feature/modular
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -39,7 +39,7 @@ public enum FontAwesome5Icon {
|
||||
REDO("\uF01E"), //
|
||||
SEARCH("\uF002"), //
|
||||
SPINNER("\uF110"), //
|
||||
STOPWATCH("\uF2F2"), //
|
||||
STETHOSCOPE("\uF0f1"), //
|
||||
SYNC("\uF021"), //
|
||||
TIMES("\uF00D"), //
|
||||
TRASH("\uF1F8"), //
|
||||
|
||||
@@ -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<FontAwesome5Icon> glyph = new SimpleObjectProperty<>(this, "glyph", DEFAULT_GLYPH);
|
||||
private DoubleProperty glyphSize = new SimpleDoubleProperty(this, "glyphSize", DEFAULT_GLYPH_SIZE);
|
||||
private final ObjectProperty<FontAwesome5Icon> 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) {
|
||||
|
||||
@@ -16,7 +16,6 @@ public class BatchService extends Service<Void> {
|
||||
|
||||
private final Iterator<HealthCheckTask> remainingTasks;
|
||||
|
||||
@Inject
|
||||
public BatchService(Iterable<HealthCheckTask> tasks) {
|
||||
this.remainingTasks = tasks.iterator();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import java.util.stream.Stream;
|
||||
@HealthCheckScoped
|
||||
public class CheckDetailController implements FxController {
|
||||
|
||||
private final EasyObservableList<DiagnosticResult> results;
|
||||
private final EasyObservableList<Result> results;
|
||||
private final OptionalBinding<Worker.State> taskState;
|
||||
private final Binding<String> taskName;
|
||||
private final Binding<String> taskDuration;
|
||||
@@ -39,7 +39,7 @@ public class CheckDetailController implements FxController {
|
||||
private final ResultListCellFactory resultListCellFactory;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
public ListView<DiagnosticResult> resultsListView;
|
||||
public ListView<Result> resultsListView;
|
||||
private Subscription resultSubscription;
|
||||
|
||||
@Inject
|
||||
@@ -71,8 +71,8 @@ public class CheckDetailController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private Function<Stream<? extends DiagnosticResult>, Long> countSeverity(DiagnosticResult.Severity severity) {
|
||||
return stream -> stream.filter(item -> severity.equals(item.getSeverity())).count();
|
||||
private Function<Stream<? extends Result>, Long> countSeverity(DiagnosticResult.Severity severity) {
|
||||
return stream -> stream.filter(item -> severity.equals(item.diagnosis().getSeverity())).count();
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -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<HealthCheckTask> {
|
||||
|
||||
private final FontAwesome5IconView stateIcon = new FontAwesome5IconView();
|
||||
private final Callback<HealthCheckTask, BooleanProperty> selectedGetter;
|
||||
private final ObjectProperty<State> stateProperty;
|
||||
|
||||
private CheckBox checkBox = new CheckBox();
|
||||
private BooleanProperty selectedProperty;
|
||||
|
||||
CheckListCell(Callback<HealthCheckTask, BooleanProperty> selectedGetter, ObservableValue<Boolean> 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<DiagnosticResult.Severity> isProblem = severity -> switch (severity) {
|
||||
case WARN, CRITICAL -> true;
|
||||
case INFO, GOOD -> false;
|
||||
};
|
||||
return item.results().stream().map(Result::diagnosis).map(DiagnosticResult::getSeverity).anyMatch(isProblem);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<HealthCheckTask> tasks;
|
||||
private final FilteredList<HealthCheckTask> chosenTasks;
|
||||
private final ReportWriter reportWriter;
|
||||
private final ExecutorService executorService;
|
||||
private final ObjectProperty<HealthCheckTask> selectedTask;
|
||||
@@ -50,19 +48,18 @@ public class CheckListController implements FxController {
|
||||
private final SimpleObjectProperty<Worker<?>> runningTask;
|
||||
private final Binding<Boolean> running;
|
||||
private final Binding<Boolean> finished;
|
||||
private final Map<HealthCheckTask, BooleanProperty> listPickIndicators;
|
||||
private final IntegerProperty numberOfPickedChecks;
|
||||
private final IntegerBinding chosenTaskCount;
|
||||
private final BooleanBinding anyCheckSelected;
|
||||
private final BooleanProperty showResultScreen;
|
||||
|
||||
/* FXML */
|
||||
public ListView<HealthCheckTask> checksListView;
|
||||
|
||||
|
||||
@Inject
|
||||
public CheckListController(@HealthCheckWindow Stage window, Lazy<Collection<HealthCheckTask>> tasks, ReportWriter reportWriteTask, ObjectProperty<HealthCheckTask> selectedTask, ExecutorService executorService, Lazy<ErrorComponent.Builder> errorComponentBuilder) {
|
||||
public CheckListController(@HealthCheckWindow Stage window, Lazy<List<HealthCheckTask>> tasks, ReportWriter reportWriteTask, ObjectProperty<HealthCheckTask> selectedTask, ExecutorService executorService, Lazy<ErrorComponent.Builder> 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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<DiagnosticResult> consumer) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
public static class DummyCheck2 implements HealthCheck {
|
||||
|
||||
@Override
|
||||
public void check(Path path, VaultConfig vaultConfig, Masterkey masterkey, Cryptor cryptor, Consumer<DiagnosticResult> consumer) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
public static class DummyCheck3 implements HealthCheck {
|
||||
|
||||
@Override
|
||||
public void check(Path path, VaultConfig vaultConfig, Masterkey masterkey, Cryptor cryptor, Consumer<DiagnosticResult> consumer) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<HealthCheckTask> provideAvailableHealthCheckTasks(Collection<HealthCheck> availableHealthChecks, @HealthCheckWindow Vault vault, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, SecureRandom csprng, ResourceBundle resourceBundle) {
|
||||
static List<HealthCheckTask> provideAvailableHealthCheckTasks(Collection<HealthCheck> availableHealthChecks, @HealthCheckWindow Vault vault, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> 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<Boolean> 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;
|
||||
}
|
||||
|
||||
@@ -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<Void> {
|
||||
private final Masterkey masterkey;
|
||||
private final SecureRandom csprng;
|
||||
private final HealthCheck check;
|
||||
private final ObservableList<DiagnosticResult> results;
|
||||
private final ObservableList<Result> 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<Void> {
|
||||
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<Void> {
|
||||
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<Void> {
|
||||
|
||||
/* Getter */
|
||||
|
||||
public ObservableList<DiagnosticResult> results() {
|
||||
Observable[] observables() {
|
||||
return new Observable[]{results, chosenForExecution};
|
||||
}
|
||||
|
||||
public ObservableList<Result> results() {
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -105,4 +104,11 @@ class HealthCheckTask extends Task<Void> {
|
||||
return durationInMillis.get();
|
||||
}
|
||||
|
||||
public BooleanProperty chosenForExecutionProperty() {
|
||||
return chosenForExecution;
|
||||
}
|
||||
|
||||
public boolean isChosenForExecution() {
|
||||
return chosenForExecution.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
43
src/main/java/org/cryptomator/ui/health/Result.java
Normal file
43
src/main/java/org/cryptomator/ui/health/Result.java
Normal file
@@ -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> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Masterkey> masterkeyRef, AtomicReference<VaultConfig> 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<Void> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DiagnosticResult> result;
|
||||
private final Logger LOG = LoggerFactory.getLogger(ResultListCellController.class);
|
||||
|
||||
private final ObjectProperty<Result> result;
|
||||
private final Binding<String> description;
|
||||
private final ResultFixApplier fixApplier;
|
||||
private final OptionalBinding<Result.FixState> fixState;
|
||||
private final ObjectBinding<FontAwesome5Icon> 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<DiagnosticResult> resultProperty() {
|
||||
public ObjectProperty<Result> resultProperty() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -86,7 +91,49 @@ public class ResultListCellController implements FxController {
|
||||
return description.getValue();
|
||||
}
|
||||
|
||||
public ObjectBinding<FontAwesome5Icon> 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<String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<ListView<DiagnosticResult>, ListCell<DiagnosticResult>> {
|
||||
public class ResultListCellFactory implements Callback<ListView<Result>, ListCell<Result>> {
|
||||
|
||||
private final FxmlLoaderFactory fxmlLoaders;
|
||||
|
||||
@@ -25,7 +24,7 @@ public class ResultListCellFactory implements Callback<ListView<DiagnosticResult
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListCell<DiagnosticResult> call(ListView<DiagnosticResult> param) {
|
||||
public ListCell<Result> call(ListView<Result> 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<ListView<DiagnosticResult
|
||||
}
|
||||
}
|
||||
|
||||
private static class Cell extends ListCell<DiagnosticResult> {
|
||||
private static class Cell extends ListCell<Result> {
|
||||
|
||||
private final Parent node;
|
||||
private final ResultListCellController controller;
|
||||
@@ -45,7 +44,7 @@ public class ResultListCellFactory implements Callback<ListView<DiagnosticResult
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(DiagnosticResult item, boolean empty) {
|
||||
protected void updateItem(Result item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item == null || empty) {
|
||||
setText(null);
|
||||
|
||||
@@ -18,11 +18,16 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@@ -31,38 +36,40 @@ public class StartController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StartController.class);
|
||||
|
||||
private final Vault vault;
|
||||
private final Stage window;
|
||||
private final Optional<VaultConfig.UnverifiedVaultConfig> unverifiedVaultConfig;
|
||||
private final CompletableFuture<VaultConfig.UnverifiedVaultConfig> unverifiedVaultConfig;
|
||||
private final KeyLoadingStrategy keyLoadingStrategy;
|
||||
private final ExecutorService executor;
|
||||
private final AtomicReference<Masterkey> masterkeyRef;
|
||||
private final AtomicReference<VaultConfig> vaultConfigRef;
|
||||
private final Lazy<Scene> checkScene;
|
||||
private final Lazy<ErrorComponent.Builder> errorComponent;
|
||||
private final ObjectProperty<State> 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<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, @FxmlScene(FxmlFile.HEALTH_CHECK_LIST) Lazy<Scene> checkScene, Lazy<ErrorComponent.Builder> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Number> {
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<WhenUnlocked> 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<WhenUnlocked> {
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
@@ -89,4 +86,22 @@ public class GeneralVaultOptionsController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private static class IdleTimeSecondsConverter extends StringConverter<Number> {
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,11 @@ public enum SelectedVaultOptionsTab {
|
||||
|
||||
/**
|
||||
* Show Auto-Lock tab
|
||||
*
|
||||
*/
|
||||
AUTOLOCK,
|
||||
|
||||
/**
|
||||
* Show health tab
|
||||
*/
|
||||
HEALTH;
|
||||
}
|
||||
|
||||
@@ -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<SelectedVaultOptionsTab> selectedTabProperty) {
|
||||
@@ -49,6 +50,7 @@ public class VaultOptionsController implements FxController {
|
||||
case MOUNT -> mountTab;
|
||||
case KEY -> keyTab;
|
||||
case AUTOLOCK -> autoLockTab;
|
||||
case HEALTH -> healthTab;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user