From a2c3b38a753536d50a06645ef535c4fc1702e70b Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Wed, 10 Dec 2014 22:39:13 +0100 Subject: [PATCH] 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"); + } + } + +}