Pārlūkot izejas kodu

moved mount + reveal from UnlockController to UnlockedController

Sebastian Stenzel 7 gadi atpakaļ
vecāks
revīzija
4ea3e8de8b

+ 30 - 49
main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java

@@ -21,7 +21,6 @@ import org.cryptomator.common.settings.VaultSettings;
 import org.cryptomator.cryptolib.api.InvalidPassphraseException;
 import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
 import org.cryptomator.frontend.webdav.ServerLifecycleException;
-import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
 import org.cryptomator.keychain.KeychainAccess;
 import org.cryptomator.ui.controls.SecPasswordField;
 import org.cryptomator.ui.l10n.Localization;
@@ -38,7 +37,6 @@ import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
 
 import javafx.application.Application;
-import javafx.application.Platform;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 import javafx.event.ActionEvent;
@@ -345,59 +343,42 @@ public class UnlockController implements ViewController {
 	private void didClickUnlockButton(ActionEvent event) {
 		advancedOptions.setDisable(true);
 		progressIndicator.setVisible(true);
-		downloadsPageLink.setVisible(false);
-		CharSequence password = passwordField.getCharacters();
-		asyncTaskService.asyncTaskOf(() -> this.unlock(password)).run();
-	}
 
-	private void unlock(CharSequence password) {
-		try {
+		CharSequence password = passwordField.getCharacters();
+		asyncTaskService.asyncTaskOf(() -> {
 			vault.unlock(password);
-			if (mountAfterUnlock.isSelected()) {
-				vault.mount();
-				if (revealAfterMount.isSelected()) {
-					vault.reveal();
-				}
-			}
-			Platform.runLater(() -> {
-				messageText.setText(null);
-				listener.ifPresent(lstnr -> lstnr.didUnlock(vault));
-			});
 			if (keychainAccess.isPresent() && savePassword.isSelected()) {
 				keychainAccess.get().storePassphrase(vault.getId(), password);
-			} else {
-				Platform.runLater(passwordField::swipe);
 			}
-		} catch (InvalidPassphraseException e) {
-			Platform.runLater(() -> {
-				messageText.setText(localization.getString("unlock.errorMessage.wrongPassword"));
-				passwordField.selectAll();
-				passwordField.requestFocus();
-			});
-		} catch (UnsupportedVaultFormatException e) {
-			Platform.runLater(() -> {
-				if (e.isVaultOlderThanSoftware()) {
-					// whitespace after localized text used as separator between text and hyperlink
-					messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
-					downloadsPageLink.setVisible(true);
-				} else if (e.isSoftwareOlderThanVault()) {
-					messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
-					downloadsPageLink.setVisible(true);
-				} else if (e.getDetectedVersion() == Integer.MAX_VALUE) {
-					messageText.setText(localization.getString("unlock.errorMessage.unauthenticVersionMac"));
-				}
-			});
-		} catch (ServerLifecycleException | CommandFailedException e) {
+		}).onSuccess(() -> {
+			messageText.setText(null);
+			downloadsPageLink.setVisible(false);
+			listener.ifPresent(lstnr -> lstnr.didUnlock(vault));
+		}).onError(InvalidPassphraseException.class, e -> {
+			messageText.setText(localization.getString("unlock.errorMessage.wrongPassword"));
+			passwordField.selectAll();
+			passwordField.requestFocus();
+		}).onError(UnsupportedVaultFormatException.class, e -> {
+			if (e.isVaultOlderThanSoftware()) {
+				// whitespace after localized text used as separator between text and hyperlink
+				messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
+				downloadsPageLink.setVisible(true);
+			} else if (e.isSoftwareOlderThanVault()) {
+				messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
+				downloadsPageLink.setVisible(true);
+			} else if (e.getDetectedVersion() == Integer.MAX_VALUE) {
+				messageText.setText(localization.getString("unlock.errorMessage.unauthenticVersionMac"));
+			}
+		}).onError(ServerLifecycleException.class, e -> {
 			LOG.error("Unlock failed for technical reasons.", e);
-			Platform.runLater(() -> {
-				messageText.setText(localization.getString("unlock.errorMessage.mountingFailed"));
-			});
-		} finally {
-			Platform.runLater(() -> {
-				advancedOptions.setDisable(false);
-				progressIndicator.setVisible(false);
-			});
-		}
+			messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
+		}).andFinally(() -> {
+			if (!savePassword.isSelected()) {
+				passwordField.swipe();
+			}
+			advancedOptions.setDisable(false);
+			progressIndicator.setVisible(false);
+		}).run();
 	}
 
 	/* callback */

+ 37 - 7
main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java

@@ -14,6 +14,7 @@ import java.util.Optional;
 
 import javax.inject.Inject;
 
+import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
 import org.cryptomator.ui.l10n.Localization;
 import org.cryptomator.ui.model.Vault;
 import org.cryptomator.ui.util.AsyncTaskService;
@@ -25,6 +26,7 @@ import org.slf4j.LoggerFactory;
 import javafx.animation.Animation;
 import javafx.animation.KeyFrame;
 import javafx.animation.Timeline;
+import javafx.beans.binding.BooleanExpression;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.event.ActionEvent;
@@ -58,6 +60,7 @@ public class UnlockedController implements ViewController {
 	private final Localization localization;
 	private final AsyncTaskService asyncTaskService;
 	private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
+	private final BooleanExpression vaultMounted = BooleanExpression.booleanExpression(EasyBind.select(vault).selectObject(Vault::mountedProperty).orElse(false));
 	private Optional<LockListener> listener = Optional.empty();
 	private Timeline ioAnimation;
 
@@ -76,6 +79,9 @@ public class UnlockedController implements ViewController {
 	@FXML
 	private ContextMenu moreOptionsMenu;
 
+	@FXML
+	private MenuItem mountVaultMenuItem;
+
 	@FXML
 	private MenuItem revealVaultMenuItem;
 
@@ -90,7 +96,8 @@ public class UnlockedController implements ViewController {
 
 	@Override
 	public void initialize() {
-		revealVaultMenuItem.disableProperty().bind(EasyBind.map(vault, vault -> vault != null && !vault.isMounted()));
+		revealVaultMenuItem.disableProperty().bind(vaultMounted.not());
+		mountVaultMenuItem.disableProperty().bind(vaultMounted);
 
 		EasyBind.subscribe(vault, this::vaultChanged);
 		EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected);
@@ -106,9 +113,8 @@ public class UnlockedController implements ViewController {
 			return;
 		}
 
-		if (newVault.getVaultSettings().mountAfterUnlock().get() && !newVault.isMounted()) {
-			// TODO Markus Kreusch #393: hyperlink auf FAQ oder sowas?
-			messageLabel.setText(localization.getString("unlocked.label.mountFailed"));
+		if (newVault.getVaultSettings().mountAfterUnlock().get()) {
+			mountVault(newVault);
 		}
 
 		// (re)start throughput statistics:
@@ -185,12 +191,36 @@ public class UnlockedController implements ViewController {
 		}
 	}
 
+	@FXML
+	public void didClickMountVault(ActionEvent event) {
+		mountVault(vault.get());
+	}
+
+	private void mountVault(Vault vault) {
+		asyncTaskService.asyncTaskOf(() -> {
+			vault.mount();
+		}).onSuccess(() -> {
+			messageLabel.setText(null);
+			if (vault.getVaultSettings().revealAfterMount().get()) {
+				revealVault(vault);
+			}
+		}).onError(CommandFailedException.class, e -> {
+			// TODO Markus Kreusch #393: hyperlink auf FAQ oder sowas?
+			messageLabel.setText(localization.getString("unlocked.label.mountFailed"));
+		}).run();
+	}
+
 	@FXML
 	private void didClickRevealVault(ActionEvent event) {
+		revealVault(vault.get());
+	}
+
+	private void revealVault(Vault vault) {
 		asyncTaskService.asyncTaskOf(() -> {
-			vault.get().reveal();
-		}).onError(RuntimeException.class, () -> {
-			// TODO overheadhunter catch more specific exception type thrown by reveal()
+			vault.reveal();
+		}).onSuccess(() -> {
+			messageLabel.setText(null);
+		}).onError(CommandFailedException.class, () -> {
 			messageLabel.setText(localization.getString("unlocked.label.revealFailed"));
 		}).run();
 	}

+ 2 - 1
main/ui/src/main/java/org/cryptomator/ui/model/Vault.java

@@ -33,6 +33,7 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties;
 import org.cryptomator.cryptofs.CryptoFileSystemProvider;
 import org.cryptomator.cryptolib.api.CryptoException;
 import org.cryptomator.cryptolib.api.InvalidPassphraseException;
+import org.cryptomator.frontend.webdav.ServerLifecycleException;
 import org.cryptomator.frontend.webdav.WebDavServer;
 import org.cryptomator.frontend.webdav.mount.MountParams;
 import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
@@ -108,7 +109,7 @@ public class Vault {
 		CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase);
 	}
 
-	public synchronized void unlock(CharSequence passphrase) {
+	public synchronized void unlock(CharSequence passphrase) throws ServerLifecycleException {
 		try {
 			FileSystem fs = getCryptoFileSystem(passphrase);
 			if (!server.isRunning()) {

+ 40 - 0
main/ui/src/main/java/org/cryptomator/ui/util/AsyncTaskService.java

@@ -32,6 +32,12 @@ public class AsyncTaskService {
 		this.executor = executor;
 	}
 
+	/**
+	 * Creates a new async task
+	 * 
+	 * @param task Tasks to be invoked in a background thread.
+	 * @return The async task
+	 */
 	public AsyncTaskWithoutSuccessHandler<Void> asyncTaskOf(RunnableThrowingException<?> task) {
 		return new AsyncTaskImpl<>(() -> {
 			task.run();
@@ -39,6 +45,12 @@ public class AsyncTaskService {
 		});
 	}
 
+	/**
+	 * Creates a new async task
+	 * 
+	 * @param task Tasks to be invoked in a background thread.
+	 * @return The async task
+	 */
 	public <ResultType> AsyncTaskWithoutSuccessHandler<ResultType> asyncTaskOf(SupplierThrowingException<ResultType, ?> task) {
 		return new AsyncTaskImpl<>(task);
 	}
@@ -153,27 +165,55 @@ public class AsyncTaskService {
 
 	public interface AsyncTaskWithoutSuccessHandler<ResultType> extends AsyncTaskWithoutErrorHandler {
 
+		/**
+		 * @param handler Tasks to be invoked on the JavaFX application thread.
+		 * @return The async task
+		 */
 		AsyncTaskWithoutErrorHandler onSuccess(ConsumerThrowingException<ResultType, ?> handler);
 
+		/**
+		 * @param handler Tasks to be invoked on the JavaFX application thread.
+		 * @return The async task
+		 */
 		AsyncTaskWithoutErrorHandler onSuccess(RunnableThrowingException<?> handler);
 
 	}
 
 	public interface AsyncTaskWithoutErrorHandler extends AsyncTaskWithoutFinallyHandler {
 
+		/**
+		 * @param type Exception type to catch
+		 * @param handler Tasks to be invoked on the JavaFX application thread.
+		 * @return The async task
+		 */
 		<ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, ConsumerThrowingException<ErrorType, ?> handler);
 
+		/**
+		 * @param type Exception type to catch
+		 * @param handler Tasks to be invoked on the JavaFX application thread.
+		 * @return The async task
+		 */
 		<ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, RunnableThrowingException<?> handler);
 
 	}
 
 	public interface AsyncTaskWithoutFinallyHandler extends AsyncTask {
 
+		/**
+		 * @param handler Tasks to be invoked on the JavaFX application thread.
+		 * @return The async task
+		 */
 		AsyncTask andFinally(RunnableThrowingException<?> handler);
 
 	}
 
 	public interface AsyncTask extends Runnable {
+
+		/**
+		 * Starts the async task.
+		 */
+		@Override
+		void run();
 	}
 
 }

+ 3 - 0
main/ui/src/main/resources/fxml/unlocked.fxml

@@ -28,6 +28,9 @@
 	<fx:define>
 		<ContextMenu fx:id="moreOptionsMenu">
 			<items>
+				<MenuItem fx:id="mountVaultMenuItem" text="%unlocked.moreOptions.mount" onAction="#didClickMountVault">
+					<graphic><Label text="&#xf139;" styleClass="ionicons"/></graphic>
+				</MenuItem>
 				<MenuItem fx:id="revealVaultMenuItem" text="%unlocked.moreOptions.reveal" onAction="#didClickRevealVault">
 					<graphic><Label text="&#xf133;" styleClass="ionicons"/></graphic>
 				</MenuItem>

+ 2 - 1
main/ui/src/main/resources/localization/en.txt

@@ -72,7 +72,7 @@ unlock.savePassword.delete.confirmation.header=Do you really want to delete the
 unlock.savePassword.delete.confirmation.content=The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled.
 unlock.choicebox.winDriveLetter.auto=Assign automatically
 unlock.errorMessage.wrongPassword=Wrong password
-unlock.errorMessage.mountingFailed=Mounting failed. See log file for details.
+unlock.errorMessage.unlockFailed=Unlock failed. See log file for details.
 unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
 unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
 unlock.errorMessage.unauthenticVersionMac=Could not authenticate version MAC.
@@ -89,6 +89,7 @@ changePassword.errorMessage.decryptionFailed=Decryption failed
 
 # unlocked.fxml
 unlocked.button.lock=Lock Vault
+unlocked.moreOptions.mount=Mount Drive
 unlocked.moreOptions.reveal=Reveal Drive
 unlocked.moreOptions.copyUrl=Copy WebDAV URL
 unlocked.label.mountFailed=Connecting drive failed