Browse Source

Show stack trace in UI for vaults in error state

Sebastian Stenzel 5 years ago
parent
commit
243c74b0cb

+ 26 - 1
main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java

@@ -27,6 +27,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 import javax.inject.Provider;
 import java.io.IOException;
 import java.nio.file.NoSuchFileException;
@@ -51,6 +52,7 @@ public class Vault {
 	private final StringBinding defaultMountFlags;
 	private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
 	private final ObjectProperty<VaultState> state;
+	private final ObjectProperty<Exception> lastKnownException;
 	private final VaultStats stats;
 	private final StringBinding displayableName;
 	private final StringBinding displayablePath;
@@ -59,18 +61,20 @@ public class Vault {
 	private final BooleanBinding unlocked;
 	private final BooleanBinding missing;
 	private final BooleanBinding needsMigration;
+	private final BooleanBinding unknownError;
 	private final StringBinding accessPoint;
 	private final BooleanBinding accessPointPresent;
 
 	private volatile Volume volume;
 
 	@Inject
-	Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, ObjectProperty<VaultState> state, VaultStats stats) {
+	Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, ObjectProperty<VaultState> state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats) {
 		this.vaultSettings = vaultSettings;
 		this.volumeProvider = volumeProvider;
 		this.defaultMountFlags = defaultMountFlags;
 		this.cryptoFileSystem = cryptoFileSystem;
 		this.state = state;
+		this.lastKnownException = lastKnownException;
 		this.stats = stats;
 		this.displayableName = Bindings.createStringBinding(this::getDisplayableName, vaultSettings.path());
 		this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
@@ -79,6 +83,7 @@ public class Vault {
 		this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state);
 		this.missing = Bindings.createBooleanBinding(this::isMissing, state);
 		this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
+		this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
 		this.accessPoint = Bindings.createStringBinding(this::getAccessPoint, state);
 		this.accessPointPresent = this.accessPoint.isNotEmpty();
 	}
@@ -149,6 +154,18 @@ public class Vault {
 		state.setValue(value);
 	}
 
+	public ObjectProperty<Exception> lastKnownExceptionProperty() {
+		return lastKnownException;
+	}
+
+	public Exception getLastKnownException() {
+		return lastKnownException.get();
+	}
+
+	public void setLastKnownException(Exception e) {
+		lastKnownException.setValue(e);
+	}
+
 	public BooleanBinding lockedProperty() {
 		return locked;
 	}
@@ -189,6 +206,14 @@ public class Vault {
 		return state.get() == VaultState.NEEDS_MIGRATION;
 	}
 
+	public BooleanBinding unknownErrorProperty() {
+		return unknownError;
+	}
+
+	public boolean isUnknownError() {
+		return state.get() == VaultState.ERROR;
+	}
+
 	public StringBinding displayableNameProperty() {
 		return displayableName;
 	}

+ 6 - 0
main/commons/src/main/java/org/cryptomator/common/vaults/VaultComponent.java

@@ -10,6 +10,9 @@ import org.cryptomator.common.settings.VaultSettings;
 
 import dagger.Subcomponent;
 
+import javax.annotation.Nullable;
+import javax.inject.Named;
+
 @PerVault
 @Subcomponent(modules = {VaultModule.class})
 public interface VaultComponent {
@@ -25,6 +28,9 @@ public interface VaultComponent {
 		@BindsInstance
 		Builder initialVaultState(VaultState vaultState);
 
+		@BindsInstance
+		Builder initialErrorCause(@Nullable @Named("lastKnownException") Exception initialErrorCause);
+
 		VaultComponent build();
 	}
 

+ 22 - 20
main/commons/src/main/java/org/cryptomator/common/vaults/VaultListManager.java

@@ -31,9 +31,9 @@ import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
 
 @Singleton
 public class VaultListManager {
-	
+
 	private static final Logger LOG = LoggerFactory.getLogger(VaultListManager.class);
-	
+
 	private final VaultComponent.Builder vaultComponentBuilder;
 	private final ObservableList<Vault> vaultList;
 
@@ -41,7 +41,7 @@ public class VaultListManager {
 	public VaultListManager(VaultComponent.Builder vaultComponentBuilder, Settings settings) {
 		this.vaultComponentBuilder = vaultComponentBuilder;
 		this.vaultList = FXCollections.observableArrayList(Vault::observables);
-		
+
 		addAll(settings.getDirectories());
 		vaultList.addListener(new VaultListChangeListener(settings.getDirectories()));
 	}
@@ -65,12 +65,12 @@ public class VaultListManager {
 			return newVault;
 		}
 	}
-	
+
 	private void addAll(Collection<VaultSettings> vaultSettings) {
 		Collection<Vault> vaults = vaultSettings.stream().map(this::create).collect(Collectors.toList());
 		vaultList.addAll(vaults);
 	}
-	
+
 	private Optional<Vault> get(Path vaultPath) {
 		return vaultList.stream().filter(v -> {
 			try {
@@ -82,23 +82,25 @@ public class VaultListManager {
 	}
 
 	private Vault create(VaultSettings vaultSettings) {
-		VaultState vaultState = determineVaultState(vaultSettings.path().get());
-		VaultComponent comp = vaultComponentBuilder.vaultSettings(vaultSettings).initialVaultState(vaultState).build();
-		return comp.vault();
-	}
-
-	public static VaultState determineVaultState(Path pathToVault) {
+		VaultComponent.Builder compBuilder = vaultComponentBuilder.vaultSettings(vaultSettings);
 		try {
-			if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
-				return VaultState.MISSING;
-			} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {
-				return VaultState.NEEDS_MIGRATION;
-			} else {
-				return VaultState.LOCKED;
-			}
+			VaultState vaultState = determineVaultState(vaultSettings.path().get());
+			compBuilder.initialVaultState(vaultState);
 		} catch (IOException e) {
-			LOG.warn("Could not determine vault state of " + pathToVault + " due to unexpected exception.", e);
-			return VaultState.ERROR;
+			LOG.warn("Failed to determine vault state for " + vaultSettings.path().get(), e);
+			compBuilder.initialVaultState(VaultState.ERROR);
+			compBuilder.initialErrorCause(e);
+		}
+		return compBuilder.build().vault();
+	}
+
+	public static VaultState determineVaultState(Path pathToVault) throws IOException {
+		if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
+			return VaultState.MISSING;
+		} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {
+			return VaultState.NEEDS_MIGRATION;
+		} else {
+			return VaultState.LOCKED;
 		}
 	}
 

+ 10 - 0
main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java

@@ -23,6 +23,8 @@ import org.cryptomator.cryptofs.CryptoFileSystem;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.annotation.Nullable;
+import javax.inject.Named;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -46,6 +48,14 @@ public class VaultModule {
 		return new SimpleObjectProperty<>(initialState);
 	}
 
+	@Provides
+	@Named("lastKnownException")
+	@PerVault
+	public ObjectProperty<Exception> provideLastKnownException(@Named("lastKnownException") @Nullable Exception initialErrorCause) {
+		return new SimpleObjectProperty<>(initialErrorCause);
+	}
+
+
 	@Provides
 	public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
 		VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();

+ 2 - 1
main/ui/src/main/java/org/cryptomator/ui/common/ErrorModule.java

@@ -21,10 +21,11 @@ abstract class ErrorModule {
 	static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
 		return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
 	}
-	
+
 	@Provides
 	@Named("stackTrace")
 	static String provideStackTrace(Throwable cause) {
+		// TODO deduplicate VaultDetailUnknownErrorController.java
 		ByteArrayOutputStream baos = new ByteArrayOutputStream();
 		cause.printStackTrace(new PrintStream(baos));
 		return baos.toString(StandardCharsets.UTF_8);

+ 7 - 7
main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java

@@ -6,13 +6,9 @@ import dagger.Provides;
 import dagger.multibindings.IntoMap;
 import javafx.scene.Scene;
 import javafx.scene.image.Image;
-import javafx.scene.input.KeyCode;
-import javafx.scene.input.KeyCodeCombination;
-import javafx.scene.input.KeyCombination;
 import javafx.stage.Stage;
 import javafx.stage.StageStyle;
 import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
-import org.cryptomator.ui.common.DefaultSceneFactory;
 import org.cryptomator.ui.common.FXMLLoaderFactory;
 import org.cryptomator.ui.common.FxController;
 import org.cryptomator.ui.common.FxControllerKey;
@@ -87,7 +83,7 @@ abstract class MainWindowModule {
 	@IntoMap
 	@FxControllerKey(VaultDetailController.class)
 	abstract FxController bindVaultDetailController(VaultDetailController controller);
-	
+
 	@Binds
 	@IntoMap
 	@FxControllerKey(WelcomeController.class)
@@ -102,7 +98,7 @@ abstract class MainWindowModule {
 	@IntoMap
 	@FxControllerKey(VaultDetailUnlockedController.class)
 	abstract FxController bindVaultDetailUnlockedController(VaultDetailUnlockedController controller);
-	
+
 	@Binds
 	@IntoMap
 	@FxControllerKey(VaultDetailMissingVaultController.class)
@@ -113,11 +109,15 @@ abstract class MainWindowModule {
 	@FxControllerKey(VaultDetailNeedsMigrationController.class)
 	abstract FxController bindVaultDetailNeedsMigrationController(VaultDetailNeedsMigrationController controller);
 
+	@Binds
+	@IntoMap
+	@FxControllerKey(VaultDetailUnknownErrorController.class)
+	abstract FxController bindVaultDetailUnknownErrorController(VaultDetailUnknownErrorController controller);
+
 	@Binds
 	@IntoMap
 	@FxControllerKey(VaultListCellController.class)
 	abstract FxController bindVaultListCellController(VaultListCellController controller);
 
 
-
 }

+ 40 - 0
main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailUnknownErrorController.java

@@ -0,0 +1,40 @@
+package org.cryptomator.ui.mainwindow;
+
+import javafx.beans.binding.Binding;
+import javafx.beans.property.ObjectProperty;
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.ui.common.FxController;
+import org.fxmisc.easybind.EasyBind;
+
+import javax.inject.Inject;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+
+@MainWindowScoped
+public class VaultDetailUnknownErrorController implements FxController {
+
+	private final Binding<String> stackTrace;
+
+	@Inject
+	public VaultDetailUnknownErrorController(ObjectProperty<Vault> vault) {
+		this.stackTrace = EasyBind.select(vault).selectObject(Vault::lastKnownExceptionProperty).map(this::provideStackTrace).orElse("");
+	}
+
+	private String provideStackTrace(Throwable cause) {
+		// TODO deduplicate ErrorModule.java
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		cause.printStackTrace(new PrintStream(baos));
+		return baos.toString(StandardCharsets.UTF_8);
+	}
+
+	/* Getter/Setter */
+
+	public Binding<String> stackTraceProperty() {
+		return stackTrace;
+	}
+
+	public String getStackTrace() {
+		return stackTrace.getValue();
+	}
+}

+ 9 - 2
main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java

@@ -18,6 +18,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
+import java.io.IOException;
 
 @MainWindowScoped
 public class VaultListController implements FxController {
@@ -68,8 +69,14 @@ public class VaultListController implements FxController {
 			case LOCKED:
 			case NEEDS_MIGRATION:
 			case MISSING:
-				VaultState determinedState = VaultListManager.determineVaultState(newValue.getPath());
-				newValue.setState(determinedState);
+				try {
+					VaultState determinedState = VaultListManager.determineVaultState(newValue.getPath());
+					newValue.setState(determinedState);
+				} catch (IOException e) {
+					LOG.warn("Failed to determine vault state for " + newValue.getPath(), e);
+					newValue.setState(VaultState.ERROR);
+					newValue.setLastKnownException(e);
+				}
 				break;
 			case ERROR:
 			case UNLOCKED:

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

@@ -52,5 +52,6 @@
 		<fx:include VBox.vgrow="ALWAYS" source="vault_detail_unlocked.fxml" visible="${controller.vault.unlocked}" managed="${controller.vault.unlocked}"/>
 		<fx:include VBox.vgrow="ALWAYS" source="vault_detail_missing.fxml" visible="${controller.vault.missing}" managed="${controller.vault.missing}"/>
 		<fx:include VBox.vgrow="ALWAYS" source="vault_detail_needsmigration.fxml" visible="${controller.vault.needsMigration}" managed="${controller.vault.needsMigration}"/>
+		<fx:include VBox.vgrow="ALWAYS" source="vault_detail_unknownerror.fxml" visible="${controller.vault.unknownError}" managed="${controller.vault.unknownError}"/>
 	</children>
 </VBox>

+ 15 - 0
main/ui/src/main/resources/fxml/vault_detail_unknownerror.fxml

@@ -0,0 +1,15 @@
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.control.TextArea?>
+<?import javafx.scene.layout.VBox?>
+<VBox xmlns="http://javafx.com/javafx"
+	  xmlns:fx="http://javafx.com/fxml"
+	  fx:controller="org.cryptomator.ui.mainwindow.VaultDetailUnknownErrorController"
+	  alignment="CENTER"
+	  spacing="24">
+	<children>
+		<Label text="%generic.error.title" wrapText="true"/>
+		<Label text="%generic.error.instruction" wrapText="true"/>
+
+		<TextArea VBox.vgrow="ALWAYS" text="${controller.stackTrace}" prefRowCount="5" editable="false"/>
+	</children>
+</VBox>