diff --git a/main/ui/src/main/java/org/cryptomator/ui/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/UnlockController.java index 43f59b414..613920764 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/UnlockController.java +++ b/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 */ diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Directory.java b/main/ui/src/main/java/org/cryptomator/ui/model/Directory.java index d5837967a..2e92e2ca1 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Directory.java +++ b/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 callback) { + final FutureTask 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; diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandResult.java b/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandResult.java index 5ff8742e4..3798f4c7f 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandResult.java +++ b/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); diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandRunner.java b/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandRunner.java index 5cbbd1f03..b52f56f02 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandRunner.java +++ b/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 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); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/command/Script.java b/main/ui/src/main/java/org/cryptomator/ui/util/command/Script.java index 87d3fcbe0..1cb717a6c 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/command/Script.java +++ b/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 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; + } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/mount/LinuxGvfsWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/mount/LinuxGvfsWebDavMounter.java index 2ed00c705..dac1582ed 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/mount/LinuxGvfsWebDavMounter.java +++ b/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(); } }; } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/mount/MacOsXWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/mount/MacOsXWebDavMounter.java index 9df4801cf..2641d3c4e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/mount/MacOsXWebDavMounter.java +++ b/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(); } }; } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/mount/WindowsWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/mount/WindowsWebDavMounter.java index 0f456abaa..8d94b8d99 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/mount/WindowsWebDavMounter.java +++ b/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(); } }; } diff --git a/main/ui/src/main/resources/unlock.fxml b/main/ui/src/main/resources/unlock.fxml index bf228a565..e0b734df0 100644 --- a/main/ui/src/main/resources/unlock.fxml +++ b/main/ui/src/main/resources/unlock.fxml @@ -15,6 +15,7 @@ + @@ -39,6 +40,9 @@