Explorar el Código

- fixed clean unmounting
- fixed correct subprocess status codes (not using status code of parent shell)

Sebastian Stenzel hace 10 años
padre
commit
c743fa8bdc

+ 20 - 4
main/ui/src/main/java/org/cryptomator/ui/UnlockController.java

@@ -22,8 +22,10 @@ import javafx.beans.value.ObservableValue;
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.fxml.Initializable;
+import javafx.scene.control.Button;
 import javafx.scene.control.ComboBox;
 import javafx.scene.control.Label;
+import javafx.scene.control.ProgressIndicator;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -50,6 +52,9 @@ public class UnlockController implements Initializable {
 
 	@FXML
 	private SecPasswordField passwordField;
+	
+	@FXML
+	private ProgressIndicator progressIndicator;
 
 	@FXML
 	private Label messageLabel;
@@ -83,6 +88,7 @@ public class UnlockController implements Initializable {
 		final CharSequence password = passwordField.getCharacters();
 		InputStream masterKeyInputStream = null;
 		try {
+			progressIndicator.setVisible(true);
 			masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ);
 			directory.getCryptor().decryptMasterKey(masterKeyInputStream, password);
 			if (!directory.startServer()) {
@@ -91,16 +97,16 @@ public class UnlockController implements Initializable {
 				return;
 			}
 			directory.setUnlocked(true);
-			directory.mount();
-			if (listener != null) {
-				listener.didUnlock(this);
-			}
+			directory.mountAsync(this::didUnlockAndMount);
 		} catch (DecryptFailedException | IOException ex) {
+			progressIndicator.setVisible(false);
 			messageLabel.setText(rb.getString("unlock.errorMessage.decryptionFailed"));
 			LOG.error("Decryption failed for technical reasons.", ex);
 		} catch (WrongPasswordException e) {
+			progressIndicator.setVisible(false);
 			messageLabel.setText(rb.getString("unlock.errorMessage.wrongPassword"));
 		} catch (UnsupportedKeyLengthException ex) {
+			progressIndicator.setVisible(false);
 			messageLabel.setText(rb.getString("unlock.errorMessage.unsupportedKeyLengthInstallJCE"));
 			LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
 		} finally {
@@ -127,6 +133,16 @@ public class UnlockController implements Initializable {
 			LOG.trace("Invalid path: " + directory.getPath(), e);
 		}
 	}
+	
+	private Void didUnlockAndMount(boolean mountSuccess) {
+		Platform.runLater(() -> {
+			progressIndicator.setVisible(false);
+			if (listener != null) {
+				listener.didUnlock(this);
+			}
+		});
+		return null;
+	}
 
 	/* Getter/Setter */
 

+ 19 - 1
main/ui/src/main/java/org/cryptomator/ui/model/Directory.java

@@ -4,9 +4,13 @@ import java.io.IOException;
 import java.io.Serializable;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
 
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.SimpleObjectProperty;
+import javafx.util.Callback;
 
 import org.cryptomator.crypto.Cryptor;
 import org.cryptomator.crypto.SamplingDecorator;
@@ -67,7 +71,21 @@ public class Directory implements Serializable {
 		}
 	}
 
-	public boolean mount() {
+	public void mountAsync(Callback<Boolean, Void> callback) {
+		final FutureTask<Boolean> mountTask = new FutureTask<>(this::mount);
+		final Executor exec = Executors.newSingleThreadExecutor();
+		exec.execute(mountTask);
+		exec.execute(() -> {
+			try {
+				final Boolean result = mountTask.get();
+				callback.call(result);
+			} catch (Exception e) {
+				callback.call(false);
+			}
+		});
+	}
+	
+	private boolean mount() {
 		try {
 			webDavMount = WebDavMounter.mount(server.getPort());
 			return true;

+ 7 - 17
main/ui/src/main/java/org/cryptomator/ui/util/command/CommandResult.java

@@ -20,8 +20,6 @@ import org.slf4j.LoggerFactory;
 
 public class CommandResult {
 	
-	private static final int DEFAULT_TIMEOUT_MILLISECONDS = 10000;
-	
 	private static final Logger LOG = LoggerFactory.getLogger(CommandResult.class);
 
 	private final ByteArrayOutputStream output = new ByteArrayOutputStream();
@@ -41,20 +39,16 @@ public class CommandResult {
 	}
 	
 	public String getOutput() throws CommandFailedException {
-		return getOutput(DEFAULT_TIMEOUT_MILLISECONDS, TimeUnit.MICROSECONDS);
-	}
-	
-	public String getError() throws CommandFailedException {
-		return getError(DEFAULT_TIMEOUT_MILLISECONDS, TimeUnit.MICROSECONDS);
-	}
-	
-	public String getOutput(long timeout, TimeUnit unit) throws CommandFailedException {
-		waitAndAssertOk(timeout, unit);
+		if (!finished) {
+			throw new IllegalStateException("Command not yet finished.");
+		}
 		return new String(output.toByteArray());
 	}
 	
-	public String getError(long timeout, TimeUnit unit) throws CommandFailedException {
-		waitAndAssertOk(timeout, unit);
+	public String getError() throws CommandFailedException {
+		if (!finished) {
+			throw new IllegalStateException("Command not yet finished.");
+		}
 		return new String(error.toByteArray());
 	}
 	
@@ -90,10 +84,6 @@ public class CommandResult {
 					new String(error.toByteArray()));
 		}
 	}
-
-	public void assertOk() throws CommandFailedException {
-		assertOk(DEFAULT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
-	}
 	
 	public void assertOk(long timeout, TimeUnit unit) throws CommandFailedException {
 		int exitValue = getExitValue(timeout, unit);

+ 21 - 7
main/ui/src/main/java/org/cryptomator/ui/util/command/CommandRunner.java

@@ -13,7 +13,10 @@ import static org.apache.commons.lang3.SystemUtils.IS_OS_UNIX;
 import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
 
 import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
 
+import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.ui.util.mount.CommandFailedException;
 
@@ -41,15 +44,26 @@ class CommandRunner {
 
 	public static final String CLI_EXECUTABLE_PROPERTY = "cryptomator.cli";
 	
-	public static final String WINDOWS_DEFAULT_CLI[] = {"cmd"};
-	public static final String UNIX_DEFAULT_CLI[] = {"/bin/sh", "-e"};
-
+	public static final String WINDOWS_DEFAULT_CLI[] = {"cmd", "/C"};
+	public static final String UNIX_DEFAULT_CLI[] = {"/bin/sh", "-c"};
+	
+	/**
+	 * Executes all lines in the given script in the specified order. Stops as soon as the first command fails.
+	 * @param script Script containing command lines and environment variables.
+	 * @return Result of the last command, if it exited successfully.
+	 * @throws CommandFailedException If one of the command lines in the given script fails.
+	 */
 	static CommandResult execute(Script script) throws CommandFailedException {
-		ProcessBuilder builder = new ProcessBuilder(determineCli());
-		builder.environment().clear();
-		builder.environment().putAll(script.environment());
 		try {
-			return run(builder.start(), script.getLines());
+			final List<String> env = script.environment().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.toList());
+			CommandResult result = null;
+			for (final String line : script.getLines()) {
+				final String[] cmds = ArrayUtils.add(determineCli(), line);
+				final Process proc = Runtime.getRuntime().exec(cmds, env.toArray(new String[0]));
+				result = run(proc, script.getLines());
+				result.assertOk(script.getTimeout(), script.getTimeoutUnit());
+			}
+			return result;
 		} catch (IOException e) {
 			throw new CommandFailedException(e);
 		}

+ 21 - 0
main/ui/src/main/java/org/cryptomator/ui/util/command/Script.java

@@ -10,17 +10,22 @@ package org.cryptomator.ui.util.command;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 import org.cryptomator.ui.util.mount.CommandFailedException;
 
 public final class Script {
 	
+	private static final int DEFAULT_TIMEOUT_MILLISECONDS = 3000;
+	
 	public static Script fromLines(String ... commands) {
 		return new Script(commands);
 	}
 	
 	private final String[] lines;
 	private final Map<String,String> environment = new HashMap<>();
+	private long timeout = DEFAULT_TIMEOUT_MILLISECONDS;
+	private TimeUnit timeoutUnit = TimeUnit.MILLISECONDS;
 	
 	private Script(String[] lines) {
 		this.lines = lines;
@@ -54,5 +59,21 @@ public final class Script {
 		environment.put(name, value);
 		return this;
 	}
+
+	public long getTimeout() {
+		return timeout;
+	}
+
+	public void setTimeout(long timeout) {
+		this.timeout = timeout;
+	}
+
+	public TimeUnit getTimeoutUnit() {
+		return timeoutUnit;
+	}
+
+	public void setTimeoutUnit(TimeUnit timeoutUnit) {
+		this.timeoutUnit = timeoutUnit;
+	}
 	
 }

+ 3 - 3
main/ui/src/main/java/org/cryptomator/ui/util/mount/LinuxGvfsWebDavMounter.java

@@ -19,7 +19,7 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
 		if (SystemUtils.IS_OS_LINUX) {
 			final Script checkScripts = Script.fromLines("which gvfs-mount xdg-open");
 			try {
-				checkScripts.execute().assertOk();
+				checkScripts.execute();
 				return true;
 			} catch (CommandFailedException e) {
 				return false;
@@ -40,11 +40,11 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
 				"set -x",
 				"gvfs-mount -u \"dav://[::1]:$PORT\"")
 				.addEnv("URI", String.valueOf(localPort));
-		mountScript.execute().assertOk();
+		mountScript.execute();
 		return new WebDavMount() {
 			@Override
 			public void unmount() throws CommandFailedException {
-				unmountScript.execute().assertOk();
+				unmountScript.execute();
 			}
 		};
 	}

+ 2 - 4
main/ui/src/main/java/org/cryptomator/ui/util/mount/MacOsXWebDavMounter.java

@@ -23,21 +23,19 @@ final class MacOsXWebDavMounter implements WebDavMounterStrategy {
 	public WebDavMount mount(int localPort) throws CommandFailedException {
 		final String path = "/Volumes/Cryptomator" + localPort;
 		final Script mountScript = Script.fromLines(
-				"set -x",
 				"mkdir \"$MOUNT_PATH\"",
 				"mount_webdav -S -v Cryptomator \"[::1]:$PORT\" \"$MOUNT_PATH\"",
 				"open \"$MOUNT_PATH\"")
 				.addEnv("PORT", String.valueOf(localPort))
 				.addEnv("MOUNT_PATH", path);
 		final Script unmountScript = Script.fromLines(
-				"set -x",
 				"umount $MOUNT_PATH")
 				.addEnv("MOUNT_PATH", path);
-		mountScript.execute().assertOk();
+		mountScript.execute();
 		return new WebDavMount() {
 			@Override
 			public void unmount() throws CommandFailedException {
-				unmountScript.execute().assertOk();
+				unmountScript.execute();
 			}
 		};
 	}

+ 6 - 8
main/ui/src/main/java/org/cryptomator/ui/util/mount/WindowsWebDavMounter.java

@@ -11,6 +11,7 @@ package org.cryptomator.ui.util.mount;
 
 import static org.cryptomator.ui.util.command.Script.fromLines;
 
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -36,21 +37,18 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
 
 	@Override
 	public WebDavMount mount(int localPort) throws CommandFailedException {
-		final Script mountScript = fromLines(
-				"net use * http://0--1.ipv6-literal.net:%PORT% /persistent:no",
-				"if %errorLevel% neq 0 exit %errorLevel%")
+		final Script mountScript = fromLines("net use * http://0--1.ipv6-literal.net:%PORT% /persistent:no")
 				.addEnv("PORT", String.valueOf(localPort));
+		mountScript.setTimeout(30);
+		mountScript.setTimeoutUnit(TimeUnit.SECONDS);
 		final CommandResult mountResult = mountScript.execute();
-		mountResult.assertOk();
 		final String driveLetter = getDriveLetter(mountResult.getOutput());
-		final Script unmountScript = fromLines(
-				"net use "+driveLetter+" /delete",
-				"if %errorLevel% neq 0 exit %errorLevel%")
+		final Script unmountScript = fromLines("net use "+driveLetter+" /delete")
 				.addEnv("DRIVE_LETTER", driveLetter);
 		return new WebDavMount() {
 			@Override
 			public void unmount() throws CommandFailedException {
-				unmountScript.execute().assertOk();
+				unmountScript.execute();
 			}
 		};
 	}

+ 4 - 0
main/ui/src/main/resources/unlock.fxml

@@ -15,6 +15,7 @@
 <?import java.lang.String?>
 <?import org.cryptomator.ui.controls.SecPasswordField?>
 <?import javafx.scene.control.Label?>
+<?import javafx.scene.control.ProgressIndicator?>
 
 
 <GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.UnlockController" xmlns:fx="http://javafx.com/fxml">
@@ -39,6 +40,9 @@
 		<!-- Row 2 -->
 		<Button text="%unlock.button.unlock" defaultButton="true" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickUnlockButton" focusTraversable="false"/>
 		
+		<!-- Row 3 -->
+		<ProgressIndicator progress="-1" fx:id="progressIndicator" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="CENTER" visible="false"/>
+		
 		<!-- Row 5 -->
 		<Label fx:id="messageLabel" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" />
 	</children>