refactored WebDavMounter, now using strategy pattern

This commit is contained in:
Markus Kreusch
2014-12-10 22:39:13 +01:00
parent 2fb35c59d4
commit a2c3b38a75
11 changed files with 408 additions and 107 deletions

View File

@@ -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:", 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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.
* <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("\\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");
}
}
}