From a2c3b38a753536d50a06645ef535c4fc1702e70b Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Wed, 10 Dec 2014 22:39:13 +0100 Subject: [PATCH 1/2] refactored WebDavMounter, now using strategy pattern --- .../org/cryptomator/ui/model/Directory.java | 18 ++-- .../org/cryptomator/ui/util/CommandUtil.java | 44 +++++++++ .../cryptomator/ui/util/WebDavMounter.java | 99 ------------------- .../util/webdav/CommandFailedException.java | 24 +++++ .../ui/util/webdav/FallbackWebDavMounter.java | 44 +++++++++ .../util/webdav/LinuxGvfsWebDavMounter.java | 38 +++++++ .../ui/util/webdav/MacOsXWebDavMounter.java | 39 ++++++++ .../ui/util/webdav/WebDavMount.java | 26 +++++ .../ui/util/webdav/WebDavMounter.java | 62 ++++++++++++ .../ui/util/webdav/WebDavMounterStrategy.java | 36 +++++++ .../ui/util/webdav/WindowsWebDavMounter.java | 85 ++++++++++++++++ 11 files changed, 408 insertions(+), 107 deletions(-) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/CommandUtil.java delete mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/WebDavMounter.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/webdav/CommandFailedException.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/webdav/FallbackWebDavMounter.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/webdav/LinuxGvfsWebDavMounter.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/webdav/MacOsXWebDavMounter.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/webdav/WebDavMount.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/webdav/WebDavMounter.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/webdav/WebDavMounterStrategy.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/webdav/WindowsWebDavMounter.java 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 6a6d5f4a7..d1245188e 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 @@ -2,20 +2,21 @@ package org.cryptomator.ui.model; import java.io.IOException; import java.io.Serializable; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; -import org.apache.commons.lang3.StringUtils; import org.cryptomator.crypto.Cryptor; import org.cryptomator.crypto.SamplingDecorator; import org.cryptomator.crypto.aes256.Aes256Cryptor; import org.cryptomator.ui.MainApplication; import org.cryptomator.ui.util.MasterKeyFilter; -import org.cryptomator.ui.util.WebDavMounter; -import org.cryptomator.ui.util.WebDavMounter.CommandFailedException; +import org.cryptomator.ui.util.webdav.CommandFailedException; +import org.cryptomator.ui.util.webdav.WebDavMount; +import org.cryptomator.ui.util.webdav.WebDavMounter; import org.cryptomator.webdav.WebDAVServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +36,7 @@ public class Directory implements Serializable { private final ObjectProperty unlocked = new SimpleObjectProperty(this, "unlocked", Boolean.FALSE); private final Path path; // private boolean unlocked; - private String unmountCommand; + private WebDavMount webDavMount; private final Runnable shutdownTask = new ShutdownTask(); public Directory(final Path path) { @@ -69,7 +70,8 @@ public class Directory implements Serializable { public boolean mount() { try { - unmountCommand = WebDavMounter.mount(server.getPort()); + URI shareUri = URI.create(String.format("dav://localhost:", server.getPort())); + webDavMount = WebDavMounter.mount(shareUri); return true; } catch (CommandFailedException e) { LOG.warn("mount failed", e); @@ -79,9 +81,9 @@ public class Directory implements Serializable { public boolean unmount() { try { - if (StringUtils.isNotEmpty(unmountCommand)) { - WebDavMounter.unmount(unmountCommand); - unmountCommand = null; + if (webDavMount != null) { + webDavMount.unmount(); + webDavMount = null; } return true; } catch (CommandFailedException e) { diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/CommandUtil.java b/main/ui/src/main/java/org/cryptomator/ui/util/CommandUtil.java new file mode 100644 index 000000000..536909bee --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/CommandUtil.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2014 Sebastian Stenzel, Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + * Markus Kreusch + ******************************************************************************/ +package org.cryptomator.ui.util; + +import static java.lang.String.join; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; +import org.cryptomator.ui.util.webdav.CommandFailedException; + +public final class CommandUtil { + + private static final int DEFAULT_TIMEOUT_SECONDS = 3; + + public static String exec(String ... command) throws CommandFailedException { + try { + final Process proc = Runtime.getRuntime().exec(command); + if (!proc.waitFor(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + proc.destroy(); + throw new CommandFailedException("Timeout executing command " + join(" ", command)); + } + if (proc.exitValue() != 0) { + throw new CommandFailedException(IOUtils.toString(proc.getErrorStream())); + } + return IOUtils.toString(proc.getInputStream()); + } catch (IOException | InterruptedException | IllegalThreadStateException e) { + throw new CommandFailedException(e); + } + } + + private CommandUtil() { + throw new IllegalStateException("Class is not instantiable"); + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/WebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/WebDavMounter.java deleted file mode 100644 index 2af22f6a7..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/util/WebDavMounter.java +++ /dev/null @@ -1,99 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 Sebastian Stenzel - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Sebastian Stenzel - initial API and implementation - ******************************************************************************/ -package org.cryptomator.ui.util; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.SystemUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class WebDavMounter { - - private static final Logger LOG = LoggerFactory.getLogger(WebDavMounter.class); - private static final int CMD_DEFAULT_TIMEOUT = 3; - private static final Pattern WIN_MOUNT_DRIVELETTER_PATTERN = Pattern.compile("\\s*[A-Z]:\\s*"); - - private WebDavMounter() { - throw new IllegalStateException("not instantiable."); - } - - /** - * @return Unmount Command - */ - public static synchronized String mount(int localPort) throws CommandFailedException { - if (SystemUtils.IS_OS_MAC_OSX) { - exec("mkdir /Volumes/Cryptomator" + localPort, CMD_DEFAULT_TIMEOUT); - exec("mount_webdav -S -v Cryptomator localhost:" + localPort + " /Volumes/Cryptomator" + localPort, CMD_DEFAULT_TIMEOUT); - exec("open /Volumes/Cryptomator" + localPort, CMD_DEFAULT_TIMEOUT); - return "umount /Volumes/Cryptomator" + localPort; - } else if (SystemUtils.IS_OS_WINDOWS) { - final String result = exec("net use * http://127.0.0.1:" + localPort + " /persistent:no", CMD_DEFAULT_TIMEOUT); - final Matcher matcher = WIN_MOUNT_DRIVELETTER_PATTERN.matcher(result); - if (matcher.find()) { - final String driveLetter = matcher.group(); - return "net use " + driveLetter + " /delete"; - } - } else if (SystemUtils.IS_OS_LINUX) { - // TODO check result of "which gvfs-mount" first and choose a good strategy. also refactor this class ;-) - exec("gvfs-mount dav://localhost:" + localPort, CMD_DEFAULT_TIMEOUT); - exec("xdg-open dav://localhost:" + localPort, CMD_DEFAULT_TIMEOUT); - return "gvfs-mount -u dav://localhost:" + localPort; - } - return null; - } - - public static void unmount(String command) throws CommandFailedException { - if (command != null) { - exec(command, CMD_DEFAULT_TIMEOUT); - } - } - - private static String exec(String cmd, int timoutSeconds) throws CommandFailedException { - try { - final Process proc; - if (SystemUtils.IS_OS_WINDOWS) { - proc = Runtime.getRuntime().exec(new String[] {"cmd", "/C", cmd}); - } else { - proc = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", cmd}); - } - if (!proc.waitFor(timoutSeconds, TimeUnit.SECONDS)) { - proc.destroy(); - throw new CommandFailedException("Timeout executing command " + cmd); - } - if (proc.exitValue() != 0) { - throw new CommandFailedException(IOUtils.toString(proc.getErrorStream())); - } - return IOUtils.toString(proc.getInputStream()); - } catch (IOException | InterruptedException | IllegalThreadStateException e) { - LOG.error("Command execution failed.", e); - throw new CommandFailedException(e); - } - - } - - public static class CommandFailedException extends Exception { - - private static final long serialVersionUID = 5784853630182321479L; - - private CommandFailedException(String message) { - super(message); - } - - private CommandFailedException(Throwable cause) { - super(cause); - } - - } - -} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/CommandFailedException.java b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/CommandFailedException.java new file mode 100644 index 000000000..9256a554e --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/CommandFailedException.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2014 Sebastian Stenzel + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + * Markus Kreusch - Refactored WebDavMounter to use strategy pattern + ******************************************************************************/ +package org.cryptomator.ui.util.webdav; + +public class CommandFailedException extends Exception { + + private static final long serialVersionUID = 5784853630182321479L; + + public CommandFailedException(String message) { + super(message); + } + + public CommandFailedException(Throwable cause) { + super(cause); + } + +} \ No newline at end of file diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/FallbackWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/FallbackWebDavMounter.java new file mode 100644 index 000000000..d662ba321 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/FallbackWebDavMounter.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2014 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Markus Kreusch - Refactored WebDavMounter to use strategy pattern + ******************************************************************************/ +package org.cryptomator.ui.util.webdav; + +import java.net.URI; + +/** + * A WebDavMounter acting as fallback if no other mounter works. + * + * @author Markus Kreusch + */ +final class FallbackWebDavMounter implements WebDavMounterStrategy { + + @Override + public boolean shouldWork() { + return true; + } + + @Override + public WebDavMount mount(URI uri) { + displayMountInstructions(); + return new WebDavMount() { + @Override + public void unmount() { + displayUnmountInstructions(); + } + }; + } + + private void displayMountInstructions() { + // TODO display message to user pointing to cryptomator.org/mounting#mount which describes what to do + } + + private void displayUnmountInstructions() { + // TODO display message to user pointing to cryptomator.org/mounting#unmount which describes what to do + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/LinuxGvfsWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/LinuxGvfsWebDavMounter.java new file mode 100644 index 000000000..65e95895c --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/LinuxGvfsWebDavMounter.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2014 Sebastian Stenzel, Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + * Markus Kreusch - Refactored WebDavMounter to use strategy pattern + ******************************************************************************/ +package org.cryptomator.ui.util.webdav; + +import static org.cryptomator.ui.util.CommandUtil.exec; + +import java.net.URI; + +import org.apache.commons.lang3.SystemUtils; + +final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy { + + @Override + public boolean shouldWork() { + // TODO check result of "which gvfs-mount" + return SystemUtils.IS_OS_LINUX; + } + + @Override + public WebDavMount mount(final URI uri) throws CommandFailedException { + exec("gvfs-mount", uri.toString()); + exec("xdg-open", uri.toString()); + return new WebDavMount() { + @Override + public void unmount() throws CommandFailedException { + exec("gvfs-mount", "-u", uri.toString()); + } + }; + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/MacOsXWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/MacOsXWebDavMounter.java new file mode 100644 index 000000000..a51ba0d98 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/MacOsXWebDavMounter.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2014 Sebastian Stenzel, Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + * Markus Kreusch - Refactored WebDavMounter to use strategy pattern + ******************************************************************************/ +package org.cryptomator.ui.util.webdav; + +import static org.cryptomator.ui.util.CommandUtil.exec; + +import java.net.URI; + +import org.apache.commons.lang3.SystemUtils; + +final class MacOsXWebDavMounter implements WebDavMounterStrategy { + + @Override + public boolean shouldWork() { + return SystemUtils.IS_OS_MAC_OSX; + } + + @Override + public WebDavMount mount(URI uri) throws CommandFailedException { + final String path = "/Volumes/Cryptomator" + uri.getPort(); + exec("mkdir", "/Volumes/Cryptomator" + uri.getPort()); + exec("mount_webdav", "-S", "-v", "Cryptomator", uri.toString(), path); + exec("open", path); + return new WebDavMount() { + @Override + public void unmount() throws CommandFailedException { + exec("unmount", path); + } + }; + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WebDavMount.java b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WebDavMount.java new file mode 100644 index 000000000..35c1253bf --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WebDavMount.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2014 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Markus Kreusch - Refactored WebDavMounter to use strategy pattern + ******************************************************************************/ +package org.cryptomator.ui.util.webdav; + + +/** + * A mounted webdav share. + * + * @author Markus Kreusch + */ +public interface WebDavMount { + + /** + * Unmounts this {@code WebDavMount}. + * + * @throws CommandFailedException if the unmount operation fails + */ + void unmount() throws CommandFailedException; + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WebDavMounter.java new file mode 100644 index 000000000..cf1db9cdd --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WebDavMounter.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2014 Sebastian Stenzel + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + * Markus Kreusch - Refactored to use strategy pattern + ******************************************************************************/ +package org.cryptomator.ui.util.webdav; + +import java.net.URI; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class WebDavMounter { + + private static final Logger LOG = LoggerFactory.getLogger(WebDavMounter.class); + + private static final WebDavMounterStrategy[] STRATEGIES = { + new WindowsWebDavMounter(), + new MacOsXWebDavMounter(), + new LinuxGvfsWebDavMounter() + }; + + private static volatile WebDavMounterStrategy choosenStrategy; + + /** + * Tries to mount a given webdav share. + * + * @param uri + * the {@link URI} of the webdav share + * @return a {@link WebDavMount} representing the mounted share + * @throws CommandFailedException if the mount operation fails + */ + public static WebDavMount mount(URI uri) throws CommandFailedException { + return chooseStrategy().mount(uri); + } + + private static WebDavMounterStrategy chooseStrategy() { + if (choosenStrategy == null) { + choosenStrategy = getStrategyWhichShouldWork(); + } + return choosenStrategy; + } + + private static WebDavMounterStrategy getStrategyWhichShouldWork() { + for (WebDavMounterStrategy strategy : STRATEGIES) { + if (strategy.shouldWork()) { + LOG.info("Using {}", strategy.getClass().getSimpleName()); + return strategy; + } + } + return new FallbackWebDavMounter(); + } + + private WebDavMounter() { + throw new IllegalStateException("Class is not instantiable."); + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WebDavMounterStrategy.java b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WebDavMounterStrategy.java new file mode 100644 index 000000000..e00568ac8 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WebDavMounterStrategy.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2014 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Markus Kreusch - Refactored WebDavMounter to use strategy pattern + ******************************************************************************/ +package org.cryptomator.ui.util.webdav; + +import java.net.URI; + +/** + * A strategy able to mount a webdav share and display it to the user. + * + * @author Markus Kreusch + */ +interface WebDavMounterStrategy { + + /** + * @return {@code false} if this {@code WebDavMounterStrategy} can not work + * on the local machine, {@code true} if it could work + */ + boolean shouldWork(); + + /** + * Tries to mount a given webdav share. + * + * @param uri + * the {@link URI} of the webdav share + * @return a {@link WebDavMount} representing the mounted share + * @throws CommandFailedException if the mount operation fails + */ + WebDavMount mount(URI uri) throws CommandFailedException; + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WindowsWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WindowsWebDavMounter.java new file mode 100644 index 000000000..f1e956c55 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WindowsWebDavMounter.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2014 Sebastian Stenzel, Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + * Markus Kreusch - Refactored WebDavMounter to use strategy pattern + ******************************************************************************/ +package org.cryptomator.ui.util.webdav; + +import static java.lang.String.format; +import static org.cryptomator.ui.util.CommandUtil.exec; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.SystemUtils; + +/** + * A {@link WebDavMounterStrategy} utilizing the "net use" command. + *

+ * Tested on Windows 7 but should also work on Windows 8. + * + * @author Markus Kreusch + */ +final class WindowsWebDavMounter implements WebDavMounterStrategy { + + private static final Pattern WIN_MOUNT_DRIVELETTER_PATTERN = Pattern.compile("\\s*[A-Z]:\\s*"); + + @Override + public boolean shouldWork() { + return SystemUtils.IS_OS_WINDOWS; + } + + @Override + public WebDavMount mount(URI uri) throws CommandFailedException { + final String result = exec("net", "use", "*", toHttpUri(uri), "/persistent:no"); + final String driveLetter = getDriveLetter(result); + return new WebDavMount() { + @Override + public void unmount() throws CommandFailedException { + exec("net", "use", driveLetter, "/delete"); + } + }; + } + + private String getDriveLetter(String result) throws CommandFailedException { + final Matcher matcher = WIN_MOUNT_DRIVELETTER_PATTERN.matcher(result); + if (matcher.find()) { + return matcher.group(); + } else { + throw new CommandFailedException("Failed to get a drive letter from net use output."); + } + } + + private String toHttpUri(URI uri) { + if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) { + return uri.toString(); + } else if ("dav".equals(uri.getScheme())) { + return replaceScheme(uri, "http").toString(); + } else if ("davs".equals(uri.getScheme())) { + return replaceScheme(uri, "https").toString(); + } else { + throw new IllegalStateException(format("No webdav uri %s", uri)); + } + } + + private URI replaceScheme(URI uri, String scheme) { + try { + return new URI(scheme, + uri.getUserInfo(), + uri.getHost(), + uri.getPort(), + uri.getPath(), + uri.getQuery(), + uri.getFragment()); + } catch (URISyntaxException e) { + throw new IllegalStateException("Building an URI with replaced scheme failed"); + } + } + +} From c1f4ab6adad753df66d7e6138941ecfd5ca341cd Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Sun, 14 Dec 2014 21:38:16 +0100 Subject: [PATCH 2/2] Refactored script execution --- .../org/cryptomator/ui/model/Directory.java | 2 +- .../org/cryptomator/ui/util/CommandUtil.java | 44 ------- .../ui/util/command/AsyncLineWriter.java | 51 ++++++++ .../ui/util/command/AsyncStreamCopier.java | 51 ++++++++ .../ui/util/command/CommandResult.java | 113 ++++++++++++++++++ .../ui/util/command/CommandRunner.java | 77 ++++++++++++ .../cryptomator/ui/util/command/Script.java | 58 +++++++++ .../util/webdav/LinuxGvfsWebDavMounter.java | 30 +++-- .../ui/util/webdav/MacOsXWebDavMounter.java | 20 +++- .../ui/util/webdav/WindowsWebDavMounter.java | 23 +++- 10 files changed, 405 insertions(+), 64 deletions(-) delete mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/CommandUtil.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncLineWriter.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncStreamCopier.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/command/CommandResult.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/command/CommandRunner.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/command/Script.java 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 d1245188e..e172ac994 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 @@ -70,7 +70,7 @@ public class Directory implements Serializable { public boolean mount() { try { - URI shareUri = URI.create(String.format("dav://localhost:", server.getPort())); + URI shareUri = URI.create(String.format("dav://localhost:%d", server.getPort())); webDavMount = WebDavMounter.mount(shareUri); return true; } catch (CommandFailedException e) { diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/CommandUtil.java b/main/ui/src/main/java/org/cryptomator/ui/util/CommandUtil.java deleted file mode 100644 index 536909bee..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/util/CommandUtil.java +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 Sebastian Stenzel, Markus Kreusch - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Sebastian Stenzel - initial API and implementation - * Markus Kreusch - ******************************************************************************/ -package org.cryptomator.ui.util; - -import static java.lang.String.join; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.io.IOUtils; -import org.cryptomator.ui.util.webdav.CommandFailedException; - -public final class CommandUtil { - - private static final int DEFAULT_TIMEOUT_SECONDS = 3; - - public static String exec(String ... command) throws CommandFailedException { - try { - final Process proc = Runtime.getRuntime().exec(command); - if (!proc.waitFor(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { - proc.destroy(); - throw new CommandFailedException("Timeout executing command " + join(" ", command)); - } - if (proc.exitValue() != 0) { - throw new CommandFailedException(IOUtils.toString(proc.getErrorStream())); - } - return IOUtils.toString(proc.getInputStream()); - } catch (IOException | InterruptedException | IllegalThreadStateException e) { - throw new CommandFailedException(e); - } - } - - private CommandUtil() { - throw new IllegalStateException("Class is not instantiable"); - } - -} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncLineWriter.java b/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncLineWriter.java new file mode 100644 index 000000000..64a202ffb --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncLineWriter.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2014 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Markus Kreusch + ******************************************************************************/ +package org.cryptomator.ui.util.command; + +import java.io.IOException; +import java.io.OutputStream; + +final class AsyncLineWriter extends Thread { + + private final String[] lines; + private final OutputStream output; + + private IOException exception; + + public AsyncLineWriter(String[] lines, OutputStream output) { + this.lines = lines; + this.output = output; + start(); + } + + @Override + public void run() { + try (OutputStream outputToBeClosed = output) { + for (String line : lines) { + output.write(line.getBytes()); + output.write("\n".getBytes()); + output.flush(); + } + } catch (IOException e) { + exception = e; + } + } + + public void assertOk() throws IOException { + try { + join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + if (exception != null) { + throw exception; + } + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncStreamCopier.java b/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncStreamCopier.java new file mode 100644 index 000000000..e490646ae --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/command/AsyncStreamCopier.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2014 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Markus Kreusch + ******************************************************************************/ +package org.cryptomator.ui.util.command; + +import static org.apache.commons.io.IOUtils.copy; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +final class AsyncStreamCopier extends Thread { + + private final InputStream input; + private final OutputStream output; + + private IOException exception; + + public AsyncStreamCopier(InputStream input, OutputStream output) { + this.input = input; + this.output = output; + start(); + } + + @Override + public void run() { + try (InputStream inputToBeClosed = input; + OutputStream outputToBeClosed = output) { + copy(input, output); + } catch (IOException e) { + exception = e; + } + } + + public void assertOk() throws IOException { + try { + join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + if (exception != null) { + throw exception; + } + } + +} 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 new file mode 100644 index 000000000..d5c347c47 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandResult.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2014 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Markus Kreusch + ******************************************************************************/ +package org.cryptomator.ui.util.command; + +import static java.lang.String.format; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.cryptomator.ui.util.webdav.CommandFailedException; +import org.slf4j.Logger; +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(); + private final ByteArrayOutputStream error = new ByteArrayOutputStream(); + private final Process process; + + private final AsyncStreamCopier processOutputCopier; + private final AsyncStreamCopier processErrorCopier; + + private boolean finished; + + public CommandResult(Process process, String[] lines) { + this.process = process; + new AsyncLineWriter(lines, process.getOutputStream()); + processOutputCopier = new AsyncStreamCopier(process.getInputStream(), output); + processErrorCopier = new AsyncStreamCopier(process.getErrorStream(), error); + } + + 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); + return new String(output.toByteArray()); + } + + public String getError(long timeout, TimeUnit unit) throws CommandFailedException { + waitAndAssertOk(timeout, unit); + return new String(error.toByteArray()); + } + + public int getExitValue(long timeout, TimeUnit unit) throws CommandFailedException { + waitAndAssertOk(timeout, unit); + return process.exitValue(); + } + + private void waitAndAssertOk(long timeout, TimeUnit unit) throws CommandFailedException { + if (finished) return; + try { + if (!process.waitFor(timeout, unit)) { + throw new CommandFailedException("Waiting time elapsed before command execution finished"); + } + processOutputCopier.assertOk(); + processErrorCopier.assertOk(); + finished = true; + logDebugInfo(); + } catch (IOException | InterruptedException e) { + throw new CommandFailedException(e); + } + } + + private void logDebugInfo() { + if (LOG.isDebugEnabled()) { + LOG.debug("Command execution finished. Exit code: {}\n" + + "Output:\n" + + "{}\n" + + "Error:\n" + + "{}\n", + process.exitValue(), + new String(output.toByteArray()), + 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); + if (exitValue != 0) { + throw new CommandFailedException(format( + "Command execution failed. Exit code: %d\n" + + "# Output:\n" + + "%s\n" + + "# Error:\n" + + "%s", + exitValue, + new String(output.toByteArray()), + new String(error.toByteArray()))); + } + } + +} 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 new file mode 100644 index 000000000..9ccadd7da --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/command/CommandRunner.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2014 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Markus Kreusch + ******************************************************************************/ +package org.cryptomator.ui.util.command; + +import static java.lang.String.format; +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 org.apache.commons.lang3.SystemUtils; +import org.cryptomator.ui.util.webdav.CommandFailedException; + +/** + *

+ * Runs commands using a system compatible CLI. + *

+ * To detect the system type {@link SystemUtils} is used. The following CLIs are + * used by default: + *

    + *
  • {@link #WINDOWS_DEFAULT_CLI} if {@link SystemUtils#IS_OS_WINDOWS} + *
  • {@link #UNIX_DEFAULT_CLI} if {@link SystemUtils#IS_OS_UNIX} + *
+ *

+ * If the path to the executables differs from the default or the system can not + * be detected the Java system property {@value #CLI_EXECUTABLE_PROPERTY} can be + * set to define it. + *

+ * If a CLI executable can not be determined using these methods operation of + * {@link CommandRunner} will fail with {@link IllegalStateException}s. + * + * @author Markus Kreusch + */ +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"}; + + 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()); + } catch (IOException e) { + throw new CommandFailedException(e); + } + } + + private static CommandResult run(Process process, String[] lines) { + return new CommandResult(process, lines); + } + + private static String[] determineCli() { + final String cliFromProperty = System.getProperty(CLI_EXECUTABLE_PROPERTY); + if (cliFromProperty != null) { + return cliFromProperty.split(""); + } else if (IS_OS_WINDOWS) { + return WINDOWS_DEFAULT_CLI; + } else if (IS_OS_UNIX) { + return UNIX_DEFAULT_CLI; + } else { + throw new IllegalStateException(format( + "Failed to determine cli to use. Set Java system property %s to the executable.", + CLI_EXECUTABLE_PROPERTY)); + } + } + +} 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 new file mode 100644 index 000000000..4417f0281 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/command/Script.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2014 Markus Kreusch + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Markus Kreusch + ******************************************************************************/ +package org.cryptomator.ui.util.command; + +import java.util.HashMap; +import java.util.Map; + +import org.cryptomator.ui.util.webdav.CommandFailedException; + +public final class Script { + + public static Script fromLines(String ... commands) { + return new Script(commands); + } + + private final String[] lines; + private final Map environment = new HashMap<>(); + + private Script(String[] lines) { + this.lines = lines; + setEnv(System.getenv()); + } + + public String[] getLines() { + return lines; + } + + public CommandResult execute() throws CommandFailedException { + return CommandRunner.execute(this); + } + + Map environment() { + return environment; + } + + public Script setEnv(Map environment) { + this.environment.clear(); + addEnv(environment); + return this; + } + + public Script addEnv(Map environment) { + this.environment.putAll(environment); + return this; + } + + public Script addEnv(String name, String value) { + environment.put(name, value); + return this; + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/LinuxGvfsWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/LinuxGvfsWebDavMounter.java index 65e95895c..766bdb1b0 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/LinuxGvfsWebDavMounter.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/LinuxGvfsWebDavMounter.java @@ -9,28 +9,44 @@ ******************************************************************************/ package org.cryptomator.ui.util.webdav; -import static org.cryptomator.ui.util.CommandUtil.exec; - import java.net.URI; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.ui.util.command.Script; final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy { @Override public boolean shouldWork() { - // TODO check result of "which gvfs-mount" - return SystemUtils.IS_OS_LINUX; + if (SystemUtils.IS_OS_LINUX) { + final Script checkScripts = Script.fromLines("which gvfs-mount xdg-open"); + try { + checkScripts.execute().assertOk(); + return true; + } catch (CommandFailedException e) { + return false; + } + } else { + return false; + } } @Override public WebDavMount mount(final URI uri) throws CommandFailedException { - exec("gvfs-mount", uri.toString()); - exec("xdg-open", uri.toString()); + final Script mountScript = Script.fromLines( + "set -x", + "gvfs-mount \"$URI\"", + "xdg-open \"$URI\"") + .addEnv("URI", uri.toString()); + final Script unmountScript = Script.fromLines( + "set -x", + "gvfs-mount -u \"$URI\"") + .addEnv("URI", uri.toString()); + mountScript.execute().assertOk(); return new WebDavMount() { @Override public void unmount() throws CommandFailedException { - exec("gvfs-mount", "-u", uri.toString()); + unmountScript.execute().assertOk(); } }; } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/MacOsXWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/MacOsXWebDavMounter.java index a51ba0d98..c8ef2e6dc 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/MacOsXWebDavMounter.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/MacOsXWebDavMounter.java @@ -9,11 +9,10 @@ ******************************************************************************/ package org.cryptomator.ui.util.webdav; -import static org.cryptomator.ui.util.CommandUtil.exec; - import java.net.URI; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.ui.util.command.Script; final class MacOsXWebDavMounter implements WebDavMounterStrategy { @@ -25,13 +24,22 @@ final class MacOsXWebDavMounter implements WebDavMounterStrategy { @Override public WebDavMount mount(URI uri) throws CommandFailedException { final String path = "/Volumes/Cryptomator" + uri.getPort(); - exec("mkdir", "/Volumes/Cryptomator" + uri.getPort()); - exec("mount_webdav", "-S", "-v", "Cryptomator", uri.toString(), path); - exec("open", path); + final Script mountScript = Script.fromLines( + "set -x", + "mkdir \"$MOUNT_PATH\"", + "mount_webdav -S -v Cryptomator \"$URI\" \"$MOUNT_PATH\"", + "open \"$MOUNT_PATH\"") + .addEnv("URI", uri.toString()) + .addEnv("MOUNT_PATH", path); + final Script unmountScript = Script.fromLines( + "set -x", + "unmount $MOUNT_PATH") + .addEnv("MOUNT_PATH", path); + mountScript.execute().assertOk(); return new WebDavMount() { @Override public void unmount() throws CommandFailedException { - exec("unmount", path); + unmountScript.execute().assertOk(); } }; } diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WindowsWebDavMounter.java b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WindowsWebDavMounter.java index f1e956c55..8dfb74cab 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WindowsWebDavMounter.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/webdav/WindowsWebDavMounter.java @@ -10,7 +10,7 @@ package org.cryptomator.ui.util.webdav; import static java.lang.String.format; -import static org.cryptomator.ui.util.CommandUtil.exec; +import static org.cryptomator.ui.util.command.Script.fromLines; import java.net.URI; import java.net.URISyntaxException; @@ -18,6 +18,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.ui.util.command.CommandResult; +import org.cryptomator.ui.util.command.Script; /** * A {@link WebDavMounterStrategy} utilizing the "net use" command. @@ -28,7 +30,7 @@ import org.apache.commons.lang3.SystemUtils; */ final class WindowsWebDavMounter implements WebDavMounterStrategy { - private static final Pattern WIN_MOUNT_DRIVELETTER_PATTERN = Pattern.compile("\\s*[A-Z]:\\s*"); + private static final Pattern WIN_MOUNT_DRIVELETTER_PATTERN = Pattern.compile("Laufwerk\\s*([A-Z]:)\\s*ist"); @Override public boolean shouldWork() { @@ -37,12 +39,21 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy { @Override public WebDavMount mount(URI uri) throws CommandFailedException { - final String result = exec("net", "use", "*", toHttpUri(uri), "/persistent:no"); - final String driveLetter = getDriveLetter(result); + final Script mountScript = fromLines( + "net use * %URI% /persistent:no", + "if %errorLevel% neq 0 exit %errorLevel%") + .addEnv("URI", toHttpUri(uri)); + 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%") + .addEnv("DRIVE_LETTER", driveLetter); return new WebDavMount() { @Override public void unmount() throws CommandFailedException { - exec("net", "use", driveLetter, "/delete"); + unmountScript.execute().assertOk(); } }; } @@ -50,7 +61,7 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy { private String getDriveLetter(String result) throws CommandFailedException { final Matcher matcher = WIN_MOUNT_DRIVELETTER_PATTERN.matcher(result); if (matcher.find()) { - return matcher.group(); + return matcher.group(1); } else { throw new CommandFailedException("Failed to get a drive letter from net use output."); }