Explorar o código

Merge branch 'webdav-mounting'

Markus Kreusch %!s(int64=10) %!d(string=hai) anos
pai
achega
4f15645bf9

+ 10 - 8
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<Boolean> unlocked = new SimpleObjectProperty<Boolean>(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:%d", 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) {

+ 0 - 99
main/ui/src/main/java/org/cryptomator/ui/util/WebDavMounter.java

@@ -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);
-		}
-
-	}
-
-}

+ 51 - 0
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;
+		}
+	}
+	
+}

+ 51 - 0
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;
+		}
+	}
+	
+}

+ 113 - 0
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())));
+		}
+	}
+
+}

+ 77 - 0
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;
+
+/**
+ * <p>
+ * Runs commands using a system compatible CLI.
+ * <p>
+ * To detect the system type {@link SystemUtils} is used. The following CLIs are
+ * used by default:
+ * <ul>
+ * <li><i>{@link #WINDOWS_DEFAULT_CLI}</i> if {@link SystemUtils#IS_OS_WINDOWS}
+ * <li><i>{@link #UNIX_DEFAULT_CLI}</i> if {@link SystemUtils#IS_OS_UNIX}
+ * </ul>
+ * <p>
+ * 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.
+ * <p>
+ * 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));
+		}
+	}
+	
+}

+ 58 - 0
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<String,String> 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<String,String> environment() {
+		return environment;
+	}
+	
+	public Script setEnv(Map<String,String> environment) {
+		this.environment.clear();
+		addEnv(environment);
+		return this;
+	}
+	
+	public Script addEnv(Map<String,String> environment) {
+		this.environment.putAll(environment);
+		return this;
+	}
+	
+	public Script addEnv(String name, String value) {
+		environment.put(name, value);
+		return this;
+	}
+	
+}

+ 24 - 0
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);
+	}
+
+}

+ 44 - 0
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
+	}
+
+}

+ 54 - 0
main/ui/src/main/java/org/cryptomator/ui/util/webdav/LinuxGvfsWebDavMounter.java

@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * 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 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() {
+		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 {
+		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 {
+				unmountScript.execute().assertOk();
+			}
+		};
+	}
+
+}

+ 47 - 0
main/ui/src/main/java/org/cryptomator/ui/util/webdav/MacOsXWebDavMounter.java

@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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 java.net.URI;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.ui.util.command.Script;
+
+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();
+		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 {
+				unmountScript.execute().assertOk();
+			}
+		};
+	}
+
+}

+ 26 - 0
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;
+	
+}

+ 62 - 0
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.");
+	}
+
+}

+ 36 - 0
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;
+
+}

+ 96 - 0
main/ui/src/main/java/org/cryptomator/ui/util/webdav/WindowsWebDavMounter.java

@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * 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.command.Script.fromLines;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+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.
+ * <p>
+ * 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("Laufwerk\\s*([A-Z]:)\\s*ist");
+	
+	@Override
+	public boolean shouldWork() {
+		return SystemUtils.IS_OS_WINDOWS;
+	}
+
+	@Override
+	public WebDavMount mount(URI uri) throws CommandFailedException {
+		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 {
+				unmountScript.execute().assertOk();
+			}
+		};
+	}
+
+	private String getDriveLetter(String result) throws CommandFailedException {
+		final Matcher matcher = WIN_MOUNT_DRIVELETTER_PATTERN.matcher(result);
+		if (matcher.find()) {
+			return matcher.group(1);
+		} 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");
+		}
+	}
+
+}