mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-17 18:21:26 +00:00
Compare commits
18 Commits
feature/sp
...
feature/16
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b4ff4d3a2 | ||
|
|
1edc6e1189 | ||
|
|
97222d3d67 | ||
|
|
172593517a | ||
|
|
16c64a20e3 | ||
|
|
d6f8ab13aa | ||
|
|
d92a8e7980 | ||
|
|
374493e8b4 | ||
|
|
f3953c2fb1 | ||
|
|
5c9c336a33 | ||
|
|
6b113f26ba | ||
|
|
dbef1466c1 | ||
|
|
6b0d8a48c2 | ||
|
|
c7b9735f13 | ||
|
|
714a0c6664 | ||
|
|
3762441230 | ||
|
|
d0d161023d | ||
|
|
6cd0fc6807 |
@@ -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
|
||||
|
||||
|
||||
@@ -34,11 +34,13 @@ import java.util.SortedSet;
|
||||
* this volume, even if {@code #prepare(Volume, Path)} fails.</i>
|
||||
*
|
||||
* <p>If {@code #chooseMountPoint(Volume)} yields no result, the next MPC is executed
|
||||
* <i>without</i> first calling the {@code #prepare(Volume, Path)} method of the current MPC.
|
||||
* <i>without</i> calling the {@code #prepare(Volume, Path)} method of the current MPC first.
|
||||
* This is repeated until<br>
|
||||
* <ul>
|
||||
* <li><b>either</b> a mountpoint is returned by {@code #chooseMountPoint(Volume)}
|
||||
* and {@code #prepare(Volume, Path)} succeeds or fails, ending the entire operation</li>
|
||||
* <li><b>or</b> {@code #chooseMountPoint(Volume)} throws an exception,
|
||||
* ending the entire operation</li>
|
||||
* <li><b>or</b> no MPC remains and an {@link InvalidMountPointException} is thrown.</li>
|
||||
* </ul>
|
||||
* If the {@code #prepare(Volume, Path)} method of a MPC fails, the entire
|
||||
@@ -72,12 +74,13 @@ public interface MountPointChooser {
|
||||
* Developers should override this method to find or extract a mountpoint for
|
||||
* the volume <b>without</b> preparing it. Preparation should be done by
|
||||
* {@link #prepare(Volume, Path)} instead.
|
||||
* Exceptions in this method should be handled gracefully and result in returning
|
||||
* {@link Optional#empty()} instead of throwing an exception.
|
||||
* The Mountpoint-Choosing-Operation will fail if an exception occurs.
|
||||
* Consequently developers should try to restrict throwing exceptions to those cases where
|
||||
* aborting the entire operation is sensible. Failure to choose a suitable path should
|
||||
* be indicated by returning {@link Optional#empty()} instead.
|
||||
*
|
||||
* @param caller The Volume that is calling the method to choose a mountpoint
|
||||
* @return the chosen path or {@link Optional#empty()} if an exception occurred
|
||||
* or no mountpoint could be found.
|
||||
* @return the chosen path or {@link Optional#empty()} if no mountpoint could be found.
|
||||
* @see #isApplicable(Volume)
|
||||
* @see #prepare(Volume, Path)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public enum InvalidSetting {
|
||||
|
||||
;
|
||||
|
||||
private final static String DEFAULT_TRACKER = "https://github.com/cryptomator/cryptomator/issues/";
|
||||
private final static ResourceBundle BUNDLE = ResourceBundle.getBundle("i18n.strings");
|
||||
private final static String UNKNOWN_KEY_FORMAT = "<Unknown key: %s>";
|
||||
|
||||
private final URI issue;
|
||||
private final String resourceKey;
|
||||
|
||||
InvalidSetting(URI issue, String resourceKey) {
|
||||
this.issue = issue;
|
||||
this.resourceKey = Objects.requireNonNull(resourceKey);
|
||||
}
|
||||
|
||||
InvalidSetting(int issueId, String resourceKey) {
|
||||
this(onDefaultTracker(issueId), Objects.requireNonNull(resourceKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the corresponding issue URI of this setting.<br>
|
||||
* The issue URI usually resolves to a page on the
|
||||
* <a href="https://github.com/cryptomator/cryptomator/issues">Cryptomator Bugtracker.</a>
|
||||
*
|
||||
* @return the issue URI or {@code null} if none is provided.
|
||||
*/
|
||||
public URI getIssueURI() {
|
||||
return this.issue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a (preferably localized) message, that helps the user understand the
|
||||
* issue in their configuration and how to fix it.
|
||||
*
|
||||
* @return the non-null description of the issue.
|
||||
*/
|
||||
public String getMessage() {
|
||||
if (!BUNDLE.containsKey(this.resourceKey)) {
|
||||
return UNKNOWN_KEY_FORMAT.formatted(this.resourceKey);
|
||||
}
|
||||
return BUNDLE.getString(this.resourceKey);
|
||||
}
|
||||
|
||||
private static URI onDefaultTracker(int id) {
|
||||
return URI.create(DEFAULT_TRACKER + id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
|
||||
public class InvalidSettingException extends RuntimeException {
|
||||
|
||||
private final InvalidSetting reason;
|
||||
private final String additionalMessage;
|
||||
|
||||
public InvalidSettingException(InvalidSetting reason) {
|
||||
this(reason, null, null);
|
||||
}
|
||||
|
||||
public InvalidSettingException(InvalidSetting reason, String additionalMessage) {
|
||||
this(reason, additionalMessage, null);
|
||||
}
|
||||
|
||||
public InvalidSettingException(InvalidSetting reason, Throwable cause) {
|
||||
this(reason, null, cause);
|
||||
}
|
||||
|
||||
public InvalidSettingException(InvalidSetting reason, String additionalMessage, Throwable cause) {
|
||||
super(composeMessage(reason, additionalMessage), cause);
|
||||
this.reason = reason;
|
||||
this.additionalMessage = additionalMessage;
|
||||
}
|
||||
|
||||
public InvalidSetting getReason() {
|
||||
return this.reason;
|
||||
}
|
||||
|
||||
public String getAdditionalMessage() {
|
||||
return this.additionalMessage;
|
||||
}
|
||||
|
||||
private static String composeMessage(InvalidSetting reason, String additionalMessage) {
|
||||
if (additionalMessage == null) {
|
||||
return reason.getMessage();
|
||||
}
|
||||
return reason.getMessage() + "Additionally: " + additionalMessage;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,8 +19,6 @@ import org.cryptomator.ui.keyloading.KeyLoadingComponent;
|
||||
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
|
||||
import org.cryptomator.ui.mainwindow.MainWindow;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
@@ -30,6 +28,7 @@ import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
@@ -65,7 +64,7 @@ abstract class HealthCheckModule {
|
||||
/* Only inject with Lazy-Wrapper!*/
|
||||
@Provides
|
||||
@HealthCheckScoped
|
||||
static Collection<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();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -32,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);
|
||||
@@ -41,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) {
|
||||
@@ -49,6 +53,7 @@ class HealthCheckTask extends Task<Void> {
|
||||
updateTitle(check.identifier());
|
||||
}
|
||||
this.durationInMillis = new SimpleLongProperty(-1);
|
||||
this.chosenForExecution = new SimpleBooleanProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,25 +61,14 @@ class HealthCheckTask extends Task<Void> {
|
||||
Instant start = Instant.now();
|
||||
try (var masterkeyClone = masterkey.clone(); //
|
||||
var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
||||
check.check(vaultPath, vaultConfig, masterkeyClone, cryptor, result -> {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -90,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;
|
||||
}
|
||||
|
||||
@@ -106,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,20 +10,24 @@ 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) {
|
||||
@@ -31,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 = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
||||
result.fix(vaultPath, vaultConfig, masterkeyClone, cryptor);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ 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;
|
||||
@@ -23,8 +27,4 @@ public class HealthVaultOptionsController implements FxController {
|
||||
public void startHealthCheck(ActionEvent event) {
|
||||
healthCheckWindow.vault(vault).windowToClose(window).build().showHealthCheckWindow();
|
||||
}
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final HealthCheckComponent.Builder healthCheckWindow;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
org.cryptomator.ui.health.DummyHealthChecks$DummyCheck1
|
||||
org.cryptomator.ui.health.DummyHealthChecks$DummyCheck2
|
||||
org.cryptomator.ui.health.DummyHealthChecks$DummyCheck3
|
||||
@@ -3,17 +3,18 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import java.lang.Integer?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.health.CheckListController"
|
||||
minHeight="145"
|
||||
prefWidth="600"
|
||||
prefHeight="400"
|
||||
spacing="12">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
@@ -28,7 +29,7 @@
|
||||
<CheckBox onAction="#toggleSelectAll" text="%health.checkList.selectAllBox" visible="${!controller.showResultScreen}" managed="${!controller.showResultScreen}" />
|
||||
<ListView fx:id="checksListView" VBox.vgrow="ALWAYS"/>
|
||||
</VBox>
|
||||
<StackPane visible="${controller.showResultScreen}" managed="${controller.showResultScreen}" HBox.hgrow="ALWAYS" >
|
||||
<StackPane visible="${controller.showResultScreen}" HBox.hgrow="ALWAYS" >
|
||||
<VBox minWidth="300" alignment="CENTER" visible="${!controller.anyCheckSelected}" managed="${!controller.anyCheckSelected}" >
|
||||
<Label text="%health.check.detail.noSelectedCheck" wrapText="true" alignment="CENTER" />
|
||||
</VBox>
|
||||
@@ -39,7 +40,7 @@
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" onAction="#cancelCheck" disable="${!controller.running}" visible="${controller.showResultScreen}" managed="${controller.showResultScreen}" />
|
||||
<Button text="%health.check.exportBtn" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" disable="${!controller.finished}" visible="${controller.showResultScreen}" managed="${controller.showResultScreen}" onAction="#exportResults"/>
|
||||
<Button text="%health.check.runBatchBtn" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#runSelectedChecks" disable="${controller.numberOfPickedChecks == ZERO}" visible="${!controller.showResultScreen}" managed="${!controller.showResultScreen}"/>
|
||||
<Button text="%health.check.runBatchBtn" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#runSelectedChecks" disable="${controller.chosenTaskCount == ZERO}" visible="${!controller.showResultScreen}" managed="${!controller.showResultScreen}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</children>
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.control.ProgressIndicator?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.health.ResultListCellController"
|
||||
@@ -18,12 +22,18 @@
|
||||
<Insets topRightBottomLeft="6"/>
|
||||
</padding>
|
||||
<children>
|
||||
<FontAwesome5IconView fx:id="iconView" HBox.hgrow="NEVER" glyphSize="16"/>
|
||||
<FontAwesome5IconView fx:id="iconView" HBox.hgrow="NEVER" glyphSize="16" glyph="${controller.glyph}"/>
|
||||
<Label text="${controller.description}"/>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<!-- TODO: setting the minWidth of the button is just a workaround.
|
||||
What we actually want to do is to prevent shrinking the button more than the text
|
||||
-> own subclass of HBox is needed -->
|
||||
<Button fx:id="actionButton" text="%health.check.fixBtn" onAction="#runResultAction" alignment="CENTER" visible="false" minWidth="-Infinity"/>
|
||||
<StackPane HBox.hgrow="NEVER">
|
||||
<children>
|
||||
<Button fx:id="fixButton" text="%health.check.fixBtn" visible="${controller.fixable}" managed="${controller.fixable}" onAction="#fix" alignment="CENTER" minWidth="-Infinity"/>
|
||||
<ProgressIndicator progress="-1" prefWidth="12" prefHeight="12" visible="${controller.fixing}" managed="${controller.fixing}"/>
|
||||
<FontAwesome5IconView glyph="CHECK" glyphSize="16" visible="${controller.fixed}" managed="${controller.fixed}"/>
|
||||
</children>
|
||||
</StackPane>
|
||||
</children>
|
||||
</HBox>
|
||||
|
||||
@@ -17,22 +17,25 @@
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
|
||||
<!-- TODO: combine the two below labels to one and bind the properties accordingly or, preferably think about a new flow -->
|
||||
<Label text="%health.start.configInvalid" visible="${controller.invalidConfig}" managed="${controller.invalidConfig}" wrapText="true" contentDisplay="LEFT">
|
||||
<Label text="TODO loading config..." visible="${controller.loading}" managed="${controller.loading}" wrapText="true" contentDisplay="LEFT">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red" />
|
||||
<FontAwesome5IconView glyph="SPINNER"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%health.start.configValid" visible="${!controller.invalidConfig}" managed="${!controller.invalidConfig}" wrapText="true" contentDisplay="LEFT">
|
||||
<Label text="%health.start.configInvalid" visible="${controller.failed}" managed="${controller.failed}" wrapText="true" contentDisplay="LEFT">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CHECK" styleClass="glyph-icon-primary" />
|
||||
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<Label text="%health.start.configValid" visible="${controller.loaded}" managed="${controller.loaded}" wrapText="true" contentDisplay="LEFT">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="CHECK" styleClass="glyph-icon-primary"/>
|
||||
</graphic>
|
||||
</Label>
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" disable="${controller.invalidConfig}" defaultButton="true" onAction="#next"/>
|
||||
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" disable="${!controller.loaded}" defaultButton="true" onAction="#next"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</children>
|
||||
|
||||
Reference in New Issue
Block a user