Kaynağa Gözat

add function to export a report of health checks

Armin Schrenk 4 yıl önce
ebeveyn
işleme
e2565097cd

+ 8 - 1
main/ui/src/main/java/org/cryptomator/ui/health/CheckController.java

@@ -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() {

+ 91 - 0
main/ui/src/main/java/org/cryptomator/ui/health/HealthReportWriteTask.java

@@ -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());
+	}
+
+}

+ 1 - 0
main/ui/src/main/resources/fxml/health_check.fxml

@@ -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}" />