|
@@ -1,28 +1,21 @@
|
|
|
package org.cryptomator.ui.health;
|
|
|
|
|
|
-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.cryptofs.health.api.HealthCheck;
|
|
|
import org.cryptomator.cryptolib.api.CryptorProvider;
|
|
|
import org.cryptomator.cryptolib.api.Masterkey;
|
|
|
|
|
|
import javax.inject.Inject;
|
|
|
-import javafx.application.Platform;
|
|
|
+import javafx.concurrent.Task;
|
|
|
import java.nio.file.Path;
|
|
|
import java.security.SecureRandom;
|
|
|
-import java.util.EnumSet;
|
|
|
+import java.util.ArrayDeque;
|
|
|
import java.util.List;
|
|
|
-import java.util.Set;
|
|
|
-import java.util.concurrent.CancellationException;
|
|
|
-import java.util.concurrent.CompletableFuture;
|
|
|
-import java.util.concurrent.CompletionException;
|
|
|
-import java.util.concurrent.CompletionStage;
|
|
|
+import java.util.Queue;
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
import java.util.concurrent.Executors;
|
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
-import java.util.function.Consumer;
|
|
|
|
|
|
@HealthCheckScoped
|
|
|
public class CheckExecutor {
|
|
@@ -32,8 +25,7 @@ public class CheckExecutor {
|
|
|
private final Masterkey masterkey;
|
|
|
private final VaultConfig vaultConfig;
|
|
|
private final ExecutorService sequentialExecutor;
|
|
|
-
|
|
|
- private volatile boolean isCanceled;
|
|
|
+ private final Queue<CheckTask> runningTasks;
|
|
|
|
|
|
|
|
|
@Inject
|
|
@@ -43,87 +35,79 @@ public class CheckExecutor {
|
|
|
this.vaultConfig = vaultConfigRef.get();
|
|
|
this.csprng = csprng;
|
|
|
this.sequentialExecutor = Executors.newSingleThreadExecutor();
|
|
|
+ this.runningTasks = new ArrayDeque<>();
|
|
|
}
|
|
|
|
|
|
- public synchronized CompletionStage<Void> executeBatch(List<Check> checks) {
|
|
|
- isCanceled = false;
|
|
|
- var scheduledChecks = checks.stream().map(this::execute).toArray(CompletableFuture[]::new);
|
|
|
- return CompletableFuture.allOf(scheduledChecks);
|
|
|
+ public synchronized void executeBatch(List<Check> checks) {
|
|
|
+ checks.stream().map(c -> {
|
|
|
+ c.setState(Check.CheckState.SCHEDULED);
|
|
|
+ var task = new CheckTask(c);
|
|
|
+ runningTasks.add(task); // we need to use CheckTask and not Futures to set state to CANCEL
|
|
|
+ return task;
|
|
|
+ }).forEach(sequentialExecutor::submit);
|
|
|
}
|
|
|
|
|
|
- //@formatter:off
|
|
|
- private CompletionStage<Void> execute(Check check) {
|
|
|
- Preconditions.checkArgument(check.isInReRunState());
|
|
|
- return CompletableFuture.runAsync(() -> check.setState(Check.CheckState.SCHEDULED), Platform::runLater)
|
|
|
- .thenApplyAsync(ignored -> {
|
|
|
- if (isCanceled) {
|
|
|
- throw new CancellationException();
|
|
|
- }
|
|
|
- Platform.runLater(() -> check.setState(Check.CheckState.RUNNING)); //must be set within the lambda
|
|
|
- var seenSeverities = EnumSet.noneOf(DiagnosticResult.Severity.class); //used due to efficiency and compactness
|
|
|
- check(check.getHealthCheck(), diagnosis -> {
|
|
|
- seenSeverities.add(diagnosis.getSeverity());
|
|
|
- Platform.runLater(() -> check.getResults().add(Result.create(diagnosis))); //observableLists need to be changed on FXThread
|
|
|
- if (isCanceled) {
|
|
|
- throw new CancellationException(); //hacky workaround to stop the check. DO NOT catch this exception (might be wrapped!)
|
|
|
- }
|
|
|
- });
|
|
|
- return determineHighesSeverity(seenSeverities); },
|
|
|
- sequentialExecutor)
|
|
|
- .handleAsync((maxSeenSeverity, throwable) -> {
|
|
|
- var endState = determineEndState(maxSeenSeverity,throwable);
|
|
|
- check.setState(endState);
|
|
|
- if( endState != Check.CheckState.CANCELLED) { //canceling throws exception
|
|
|
- check.setError(throwable);
|
|
|
- }
|
|
|
- return null; },
|
|
|
- Platform::runLater);
|
|
|
- }
|
|
|
- //@formatter:on
|
|
|
-
|
|
|
- private DiagnosticResult.Severity determineHighesSeverity(Set<DiagnosticResult.Severity> seenSeverities) {
|
|
|
- if (seenSeverities.contains(DiagnosticResult.Severity.CRITICAL)) {
|
|
|
- return DiagnosticResult.Severity.CRITICAL;
|
|
|
- } else if (seenSeverities.contains(DiagnosticResult.Severity.WARN)) {
|
|
|
- return DiagnosticResult.Severity.WARN;
|
|
|
- } else {
|
|
|
- return DiagnosticResult.Severity.GOOD;
|
|
|
+ public synchronized void cancel() {
|
|
|
+ while (!runningTasks.isEmpty()) {
|
|
|
+ runningTasks.remove().cancel(true);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private Check.CheckState determineEndState(DiagnosticResult.Severity severity, Throwable t) {
|
|
|
- if (isCanceled) {
|
|
|
- //we do not check any exception, because CancellationExc might be wrapped
|
|
|
- return Check.CheckState.CANCELLED;
|
|
|
- } else if (t != null) {
|
|
|
- return Check.CheckState.ERROR;
|
|
|
- } else if (severity == DiagnosticResult.Severity.GOOD) {
|
|
|
- return Check.CheckState.ALL_GOOD;
|
|
|
- } else if (severity == DiagnosticResult.Severity.WARN) {
|
|
|
- return Check.CheckState.WITH_WARNINGS;
|
|
|
- } else {
|
|
|
- return Check.CheckState.WITH_CRITICALS;
|
|
|
+ private class CheckTask extends Task<Void> {
|
|
|
+
|
|
|
+ private Check c;
|
|
|
+ private DiagnosticResult.Severity highestResultSeverity;
|
|
|
+
|
|
|
+ CheckTask(Check c) {
|
|
|
+ this.c = c;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- private void check(HealthCheck healthCheck, Consumer<DiagnosticResult> diagnosisConsumer) {
|
|
|
- try (var masterkeyClone = masterkey.clone(); //
|
|
|
- var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
|
|
- healthCheck.check(vaultPath, vaultConfig, masterkeyClone, cryptor, diagnosisConsumer);
|
|
|
- } catch (Exception e) {
|
|
|
- throw new CheckFailedException(e);
|
|
|
+ @Override
|
|
|
+ protected Void call() throws Exception {
|
|
|
+ try (var masterkeyClone = masterkey.clone(); //
|
|
|
+ var cryptor = CryptorProvider.forScheme(vaultConfig.getCipherCombo()).provide(masterkeyClone, csprng)) {
|
|
|
+ c.getHealthCheck().check(vaultPath, vaultConfig, masterkeyClone, cryptor, diagnosis -> {
|
|
|
+ c.getResults().add(Result.create(diagnosis));
|
|
|
+ compareAndSetSeverity(diagnosis.getSeverity());
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return null;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- public synchronized void cancel() {
|
|
|
- isCanceled = true;
|
|
|
- }
|
|
|
+ private void compareAndSetSeverity(DiagnosticResult.Severity newOne) {
|
|
|
+ if (highestResultSeverity != DiagnosticResult.Severity.CRITICAL && newOne == DiagnosticResult.Severity.CRITICAL) {
|
|
|
+ highestResultSeverity = DiagnosticResult.Severity.CRITICAL;
|
|
|
+ } else if (highestResultSeverity != DiagnosticResult.Severity.WARN && newOne == DiagnosticResult.Severity.WARN) {
|
|
|
+ highestResultSeverity = DiagnosticResult.Severity.WARN;
|
|
|
+ } else if (highestResultSeverity != DiagnosticResult.Severity.GOOD && newOne == DiagnosticResult.Severity.GOOD) {
|
|
|
+ highestResultSeverity = DiagnosticResult.Severity.GOOD;
|
|
|
+ } else {
|
|
|
+ highestResultSeverity = DiagnosticResult.Severity.INFO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void running() {
|
|
|
+ c.setState(Check.CheckState.RUNNING);
|
|
|
+ }
|
|
|
|
|
|
- public static class CheckFailedException extends CompletionException {
|
|
|
+ @Override
|
|
|
+ protected void cancelled() {
|
|
|
+ c.setState(Check.CheckState.CANCELLED);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void succeeded() {
|
|
|
+ c.setState(Check.CheckState.SUCCEEDED);
|
|
|
+ c.setHighestResultSeverity(highestResultSeverity);
|
|
|
+ }
|
|
|
|
|
|
- private CheckFailedException(Throwable cause) {
|
|
|
- super(cause);
|
|
|
+ @Override
|
|
|
+ protected void failed() {
|
|
|
+ c.setState(Check.CheckState.ERROR);
|
|
|
+ c.setError(this.getException());
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
|
|
|
}
|