add function to export a report of health checks

This commit is contained in:
Armin Schrenk
2021-04-29 16:31:39 +02:00
parent f827b1fc89
commit e2565097cd
3 changed files with 100 additions and 1 deletions

View File

@@ -24,6 +24,7 @@ import java.util.concurrent.ExecutorService;
public class CheckController implements FxController {
private final HealthCheckSupervisor supervisor;
private final HealthReportWriteTask reportWriter;
private final ExecutorService executorService;
private final ObjectProperty<HealthCheckTask> selectedTask;
private final Binding<ObservableList<DiagnosticResult>> selectedResults;
@@ -40,8 +41,9 @@ public class CheckController implements FxController {
@Inject
public CheckController(HealthCheckSupervisor supervisor, ExecutorService executorService) {
public CheckController(HealthCheckSupervisor supervisor, HealthReportWriteTask reportWriteTask, ExecutorService executorService) {
this.supervisor = supervisor;
this.reportWriter = reportWriteTask;
this.executorService = executorService;
this.selectedTask = new SimpleObjectProperty<>();
this.selectedResults = EasyBind.wrapNullable(selectedTask).map(HealthCheckTask::results).orElse(FXCollections.emptyObservableList());
@@ -71,6 +73,11 @@ public class CheckController implements FxController {
}
@FXML
public void exportResults() {
executorService.execute(reportWriter);
}
/* Getter&Setter */
public boolean isRunning() {

View File

@@ -0,0 +1,91 @@
package org.cryptomator.ui.health;
import dagger.Lazy;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import javax.inject.Inject;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@HealthCheckScoped
public class HealthReportWriteTask extends Task<Void> {
private static final String REPORT_HEADER = """
**************************************
* Cryptomator Vault Health Report *
**************************************
Analyzed vault: %s (Current name \"%s\")
Vault storage path: %s
""";
private static final String REPORT_CHECK_SUCCESS = "\tCheck %s successful. Results:\n";
private static final String REPORT_CHECK_RESULT = "\t\t %s - %s\n";
private static final String REPORT_CHECK_CANCELED = "\tCheck %s canceled.\n";
private static final String REPORT_CHECK_FAILED = "\tCheck %s failed.\n";
private final Vault vault;
private final VaultConfig vaultConfig;
private final Lazy<Collection<HealthCheckTask>> tasks;
private final Environment env;
@Inject
public HealthReportWriteTask(@HealthCheckWindow Vault vault, AtomicReference<VaultConfig> vaultConfigRef, Lazy<Collection<HealthCheckTask>> tasks, Environment env) {
this.vault = vault;
this.vaultConfig = Objects.requireNonNull(vaultConfigRef.get());
this.tasks = tasks;
this.env = env;
}
@Override
protected Void call() throws IOException {
var path = env.getLogDir().orElse(Path.of(System.getProperty("user.home"))).resolve("healthReport_" + vault.getDisplayName() + "(" + vaultConfig.getId() + ")" + ".log");
final var tasks = this.tasks.get();
//use file channel, since results can be pretty big
try (var channel = Files.newByteChannel(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
internalWrite(channel, String.format(REPORT_HEADER, vaultConfig.getId(), vault.getDisplayName(), vault.getPath()));
for (var task : tasks) {
final var state = task.getEndState();
if (state == Worker.State.SUCCEEDED) {
internalWrite(channel, REPORT_CHECK_SUCCESS, task.getCheck().identifier());
for (var result : task.results()) {
internalWrite(channel, REPORT_CHECK_RESULT, result.getServerity(), result);
}
} else if (state == Worker.State.CANCELLED) {
internalWrite(channel, REPORT_CHECK_CANCELED, task.getCheck().identifier());
} else if (state == Worker.State.FAILED) {
internalWrite(channel, REPORT_CHECK_FAILED, task.getCheck().identifier());
internalWrite(channel, prepareFailureMsg(task));
} else {
throw new IllegalStateException("Cannot export unfinished task");
}
}
}
return null;
}
private void internalWrite(ByteChannel channel, String s, Object... formatArguments) throws IOException {
channel.write(ByteBuffer.wrap(s.formatted(formatArguments).getBytes(StandardCharsets.UTF_8)));
}
private String prepareFailureMsg(HealthCheckTask task) {
return task.getExceptionOnDone() //
.map(t -> ExceptionUtils.getStackTrace(t)).orElse("Unknown reason of failure.") //
.lines().map(line -> "\t\t" + line + "\n") //
.collect(Collectors.joining());
}
}

View File

@@ -24,6 +24,7 @@
<Label fx:id="listHeading" text="Health checks"/>
<ListView fx:id="checksListView"/>
<Button text="%generic.button.cancel" onAction="#cancelCheck" disable="${!controller.running}" maxWidth="Infinity"/>
<Button text="Export Results" onAction="#exportResults" disable="${controller.running}" maxWidth="Infinity"/>
</VBox>
<VBox spacing="6">
<Label fx:id="checkTitle" styleClass="label-large" text="${controller.selectedTaskName}" />