allow user to specify whether to use dav:// or webdav:// scheme for Linux GVFS mounts. Fixes #307

This commit is contained in:
Sebastian Stenzel
2016-07-25 10:08:21 +02:00
parent a8ad335aed
commit f071efe1b9
21 changed files with 245 additions and 154 deletions

View File

@@ -14,7 +14,12 @@ import java.util.Optional;
public interface Frontend extends AutoCloseable {
public enum MountParam {
MOUNT_NAME, HOSTNAME, WIN_DRIVE_LETTER
MOUNT_NAME, HOSTNAME, WIN_DRIVE_LETTER,
/**
* "dav" or "webdav"
*/
PREFERRED_GVFS_SCHEME
}
void mount(Map<MountParam, Optional<String>> map) throws CommandFailedException;

View File

@@ -45,7 +45,7 @@ class WebDavFrontend implements Frontend {
@Override
public void mount(Map<MountParam, Optional<String>> mountParams) throws CommandFailedException {
mount = webdavMounterProvider.get().mount(uri, mountParams);
mount = webdavMounterProvider.chooseMounter(mountParams).mount(uri, mountParams);
}
@Override

View File

@@ -0,0 +1,11 @@
package org.cryptomator.frontend.webdav;
import org.cryptomator.common.CommonsModule;
import org.cryptomator.frontend.webdav.mount.WebDavMounterModule;
import dagger.Module;
@Module(includes = {CommonsModule.class, WebDavMounterModule.class})
public class WebDavModule {
}

View File

@@ -23,7 +23,7 @@ import org.cryptomator.frontend.Frontend.MountParam;
final class FallbackWebDavMounter implements WebDavMounterStrategy {
@Override
public boolean shouldWork() {
public boolean shouldWork(Map<MountParam, Optional<String>> mountParams) {
return true;
}

View File

@@ -0,0 +1,94 @@
/*******************************************************************************
* Copyright (c) 2014, 2016 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
* Mohit Raju - Added fallback schema-name "webdav" when opening file managers
******************************************************************************/
package org.cryptomator.frontend.webdav.mount;
import java.net.URI;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.frontend.CommandFailedException;
import org.cryptomator.frontend.Frontend.MountParam;
import org.cryptomator.frontend.webdav.mount.command.Script;
@Singleton
final class LinuxGvfsDavMounter implements WebDavMounterStrategy {
@Inject
LinuxGvfsDavMounter() {
}
@Override
public boolean shouldWork(Map<MountParam, Optional<String>> mountParams) {
if (SystemUtils.IS_OS_LINUX) {
Optional<String> prefScheme = mountParams.getOrDefault(MountParam.PREFERRED_GVFS_SCHEME, Optional.empty());
boolean prefSchemeIsUnspecifiedOrDav = !prefScheme.isPresent() || prefScheme.get().equalsIgnoreCase("dav");
final Script checkScripts = Script.fromLines("which gvfs-mount xdg-open");
try {
checkScripts.execute();
return prefSchemeIsUnspecifiedOrDav;
} catch (CommandFailedException e) {
return false;
}
} else {
return false;
}
}
@Override
public void warmUp(int serverPort) {
// no-op
}
@Override
public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException {
final Script mountScript = Script.fromLines("set -x", "gvfs-mount \"dav:$DAV_SSP\"").addEnv("DAV_SSP", uri.getRawSchemeSpecificPart());
mountScript.execute();
return new LinuxGvfsDavMount(uri);
}
private static class LinuxGvfsDavMount extends AbstractWebDavMount {
private final URI webDavUri;
private final Script testMountStillExistsScript;
private final Script unmountScript;
private LinuxGvfsDavMount(URI webDavUri) {
this.webDavUri = webDavUri;
this.testMountStillExistsScript = Script.fromLines("set -x", "test `gvfs-mount --list | grep \"$DAV_SSP\" | wc -l` -eq 1").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart());
this.unmountScript = Script.fromLines("set -x", "gvfs-mount -u \"dav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart());
}
@Override
public void unmount() throws CommandFailedException {
boolean mountStillExists;
try {
testMountStillExistsScript.execute();
mountStillExists = true;
} catch (CommandFailedException e) {
mountStillExists = false;
}
// only attempt unmount if user didn't unmount manually:
if (mountStillExists) {
unmountScript.execute();
}
}
@Override
public void reveal() throws CommandFailedException {
Script.fromLines("set -x", "xdg-open \"webdav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()).execute();
}
}
}

View File

@@ -30,12 +30,14 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
}
@Override
public boolean shouldWork() {
public boolean shouldWork(Map<MountParam, Optional<String>> mountParams) {
if (SystemUtils.IS_OS_LINUX) {
Optional<String> prefScheme = mountParams.getOrDefault(MountParam.PREFERRED_GVFS_SCHEME, Optional.empty());
boolean prefSchemeIsUnspecifiedOrWebDav = !prefScheme.isPresent() || prefScheme.get().equalsIgnoreCase("webdav");
final Script checkScripts = Script.fromLines("which gvfs-mount xdg-open");
try {
checkScripts.execute();
return true;
return prefSchemeIsUnspecifiedOrWebDav;
} catch (CommandFailedException e) {
return false;
}
@@ -84,15 +86,7 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
@Override
public void reveal() throws CommandFailedException {
try {
openMountWithWebdavUri("dav:" + webDavUri.getRawSchemeSpecificPart()).execute();
} catch (CommandFailedException exception) {
openMountWithWebdavUri("webdav:" + webDavUri.getRawSchemeSpecificPart()).execute();
}
}
private Script openMountWithWebdavUri(String webdavUri) {
return Script.fromLines("set -x", "xdg-open \"$DAV_URI\"").addEnv("DAV_URI", webdavUri);
Script.fromLines("set -x", "xdg-open \"webdav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()).execute();
}
}

View File

@@ -38,7 +38,7 @@ final class MacOsXAppleScriptWebDavMounter implements WebDavMounterStrategy {
}
@Override
public boolean shouldWork() {
public boolean shouldWork(Map<MountParam, Optional<String>> mountParams) {
return SystemUtils.IS_OS_MAC_OSX && semVerComparator.compare(SystemUtils.OS_VERSION, "10.10") >= 0;
}

View File

@@ -37,7 +37,7 @@ final class MacOsXShellScriptWebDavMounter implements WebDavMounterStrategy {
}
@Override
public boolean shouldWork() {
public boolean shouldWork(Map<MountParam, Optional<String>> mountParams) {
return SystemUtils.IS_OS_MAC_OSX && semVerComparator.compare(SystemUtils.OS_VERSION, "10.10") < 0;
}

View File

@@ -1,105 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016 Sebastian Stenzel and others.
* 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.frontend.webdav.mount;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import java.util.Collection;
import java.util.Iterator;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
class MountStrategies implements Collection<WebDavMounterStrategy> {
private final Collection<WebDavMounterStrategy> delegate;
@Inject
MountStrategies(LinuxGvfsWebDavMounter linuxMounter, MacOsXAppleScriptWebDavMounter osxAppleScriptMounter, MacOsXShellScriptWebDavMounter osxShellScriptMounter, WindowsWebDavMounter winMounter) {
delegate = unmodifiableList(asList(linuxMounter, osxAppleScriptMounter, osxShellScriptMounter, winMounter));
}
@Override
public int size() {
return delegate.size();
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean contains(Object o) {
return delegate.contains(o);
}
@Override
public Iterator<WebDavMounterStrategy> iterator() {
return delegate.iterator();
}
@Override
public Object[] toArray() {
return delegate.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return delegate.toArray(a);
}
@Override
public boolean add(WebDavMounterStrategy e) {
return delegate.add(e);
}
@Override
public boolean remove(Object o) {
return delegate.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return delegate.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends WebDavMounterStrategy> c) {
return delegate.addAll(c);
}
@Override
public boolean removeAll(Collection<?> c) {
return delegate.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
return delegate.retainAll(c);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public boolean equals(Object o) {
return delegate.equals(o);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
}

View File

@@ -0,0 +1,31 @@
package org.cryptomator.frontend.webdav.mount;
import java.util.Set;
import javax.inject.Named;
import javax.inject.Singleton;
import com.google.common.collect.Sets;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
@Module
public class WebDavMounterModule {
@Provides
@ElementsIntoSet
static Set<WebDavMounterStrategy> provideMounters(LinuxGvfsWebDavMounter linuxWebDavMounter, LinuxGvfsDavMounter linuxDavMounter, MacOsXAppleScriptWebDavMounter osxAppleScriptMounter,
MacOsXShellScriptWebDavMounter osxShellScriptMounter, WindowsWebDavMounter winMounter) {
return Sets.newHashSet(linuxWebDavMounter, linuxDavMounter, osxAppleScriptMounter, osxShellScriptMounter, winMounter);
}
@Provides
@Singleton
@Named("fallback")
static WebDavMounterStrategy provideFallbackStrategy() {
return new FallbackWebDavMounter();
}
}

View File

@@ -10,34 +10,35 @@
package org.cryptomator.frontend.webdav.mount;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Named;
import javax.inject.Singleton;
import org.cryptomator.frontend.Frontend.MountParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class WebDavMounterProvider implements Provider<WebDavMounter> {
public class WebDavMounterProvider {
private static final Logger LOG = LoggerFactory.getLogger(WebDavMounterProvider.class);
private final WebDavMounterStrategy choosenStrategy;
private final Collection<WebDavMounterStrategy> availableStrategies;
private final WebDavMounterStrategy fallbackStrategy;
@Inject
public WebDavMounterProvider(MountStrategies availableStrategies) {
this.choosenStrategy = getStrategyWhichShouldWork(availableStrategies);
public WebDavMounterProvider(Set<WebDavMounterStrategy> availableStrategies, @Named("fallback") WebDavMounterStrategy fallbackStrategy) {
this.availableStrategies = availableStrategies;
this.fallbackStrategy = fallbackStrategy;
}
@Override
public WebDavMounter get() {
return this.choosenStrategy;
}
private WebDavMounterStrategy getStrategyWhichShouldWork(Collection<WebDavMounterStrategy> availableStrategies) {
WebDavMounterStrategy strategy = availableStrategies.stream().filter(WebDavMounterStrategy::shouldWork).findFirst().orElse(new FallbackWebDavMounter());
LOG.info("Using {}", strategy.getClass().getSimpleName());
return strategy;
public WebDavMounter chooseMounter(Map<MountParam, Optional<String>> mountParams) {
WebDavMounterStrategy result = availableStrategies.stream().filter(strategy -> strategy.shouldWork(mountParams)).findFirst().orElse(fallbackStrategy);
LOG.info("Using {}", result.getClass().getSimpleName());
return result;
}
}

View File

@@ -9,6 +9,11 @@
******************************************************************************/
package org.cryptomator.frontend.webdav.mount;
import java.util.Map;
import java.util.Optional;
import org.cryptomator.frontend.Frontend.MountParam;
/**
* A strategy able to mount a webdav share and display it to the user.
*
@@ -19,7 +24,7 @@ interface WebDavMounterStrategy extends WebDavMounter {
/**
* @return {@code false} if this {@code WebDavMounterStrategy} can not work on the local machine, {@code true} if it could work
*/
boolean shouldWork();
boolean shouldWork(Map<MountParam, Optional<String>> mountParams);
/**
* Invoked when mounting strategy gets chosen. On some operating systems (we don't want to tell names here) mounting might be faster,

View File

@@ -61,7 +61,7 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
}
@Override
public boolean shouldWork() {
public boolean shouldWork(Map<MountParam, Optional<String>> mountParams) {
return SystemUtils.IS_OS_WINDOWS;
}
@@ -102,7 +102,7 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
mountScript.addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
return mountScript.execute(MOUNT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
private void addProxyOverrides(URI uri) throws IOException, CommandFailedException {
try {
// get existing value for ProxyOverride key from reqistry:
@@ -110,7 +110,7 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
Process queryCmd = query.start();
String queryStdOut = IOUtils.toString(queryCmd.getInputStream(), StandardCharsets.UTF_8);
int queryResult = queryCmd.waitFor();
// determine new value for ProxyOverride key:
Set<String> overrides = new HashSet<>();
Matcher matcher = REG_QUERY_PROXY_OVERRIDES_PATTERN.matcher(queryStdOut);
@@ -122,7 +122,7 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
overrides.add("<local>");
overrides.add(uri.getHost());
overrides.add(uri.getHost() + ":" + uri.getPort());
// set new value:
String overridesStr = StringUtils.join(overrides, ';');
ProcessBuilder add = new ProcessBuilder("reg", "add", "\"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\"", "/v", "ProxyOverride", "/d", "\"" + overridesStr + "\"", "/f");

View File

@@ -10,12 +10,10 @@ package org.cryptomator.frontend.webdav;
import javax.inject.Singleton;
import org.cryptomator.common.CommonsModule;
import dagger.Component;
@Singleton
@Component(modules = {CommonsModule.class})
@Component(modules = {WebDavModule.class})
public interface WebDavComponent {
WebDavServer server();

View File

@@ -17,9 +17,8 @@ import javax.inject.Singleton;
import org.cryptomator.common.CommonsModule;
import org.cryptomator.crypto.engine.impl.CryptoEngineModule;
import org.cryptomator.frontend.FrontendFactory;
import org.cryptomator.frontend.webdav.WebDavModule;
import org.cryptomator.frontend.webdav.WebDavServer;
import org.cryptomator.frontend.webdav.mount.WebDavMounter;
import org.cryptomator.frontend.webdav.mount.WebDavMounterProvider;
import org.cryptomator.ui.model.VaultObjectMapperProvider;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.settings.SettingsProvider;
@@ -32,7 +31,7 @@ import dagger.Provides;
import javafx.application.Application;
import javafx.stage.Stage;
@Module(includes = {CryptoEngineModule.class, CommonsModule.class})
@Module(includes = {CryptoEngineModule.class, CommonsModule.class, WebDavModule.class})
class CryptomatorModule {
private final Application application;
@@ -83,12 +82,6 @@ class CryptomatorModule {
return closer.closeLater(Executors.newCachedThreadPool(), ExecutorService::shutdown).get().orElseThrow(IllegalStateException::new);
}
@Provides
@Singleton
WebDavMounter provideWebDavMounter(WebDavMounterProvider webDavMounterProvider) {
return webDavMounterProvider.get();
}
@Provides
@Singleton
FrontendFactory provideFrontendFactory(DeferredCloser closer, WebDavServer webDavServer, Settings settings) {

View File

@@ -22,6 +22,7 @@ import org.fxmisc.easybind.EasyBind;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
@@ -52,6 +53,12 @@ public class SettingsController extends LocalizedFXMLViewController {
@FXML
private Label versionLabel;
@FXML
private Label prefGvfsSchemeLabel;
@FXML
private ChoiceBox<String> prefGvfsScheme;
@Override
public void initialize() {
checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally());
@@ -62,10 +69,16 @@ public class SettingsController extends LocalizedFXMLViewController {
useIpv6Checkbox.setVisible(SystemUtils.IS_OS_WINDOWS);
useIpv6Checkbox.setSelected(SystemUtils.IS_OS_WINDOWS && settings.shouldUseIpv6());
versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion().orElse("SNAPSHOT")));
prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX);
prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX);
prefGvfsScheme.getItems().add("dav");
prefGvfsScheme.getItems().add("webdav");
prefGvfsScheme.setValue(settings.getPreferredGvfsScheme());
EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), this::checkForUpdateDidChange);
EasyBind.subscribe(portField.textProperty(), this::portDidChange);
EasyBind.subscribe(useIpv6Checkbox.selectedProperty(), this::useIpv6DidChange);
EasyBind.subscribe(prefGvfsScheme.valueProperty(), this::prefGvfsSchemeDidChange);
}
@Override
@@ -101,6 +114,11 @@ public class SettingsController extends LocalizedFXMLViewController {
settings.save();
}
private void prefGvfsSchemeDidChange(String newValue) {
settings.setPreferredGvfsScheme(newValue);
settings.save();
}
private void filterNumericKeyEvents(KeyEvent t) {
if (t.getCharacter() == null || t.getCharacter().length() == 0) {
return;

View File

@@ -154,7 +154,8 @@ public class Vault implements CryptoFileSystemDelegate {
return ImmutableMap.of( //
MountParam.MOUNT_NAME, Optional.ofNullable(mountName), //
MountParam.WIN_DRIVE_LETTER, Optional.ofNullable(CharUtils.toString(winDriveLetter)), //
MountParam.HOSTNAME, Optional.of(hostname) //
MountParam.HOSTNAME, Optional.of(hostname), //
MountParam.PREFERRED_GVFS_SCHEME, Optional.ofNullable(settings.getPreferredGvfsScheme()) //
);
}

View File

@@ -18,7 +18,7 @@ import org.cryptomator.ui.model.Vault;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@JsonPropertyOrder(value = {"directories", "checkForUpdatesEnabled", "port", "useIpv6", "numTrayNotifications"})
@JsonPropertyOrder(value = {"directories", "checkForUpdatesEnabled", "port", "useIpv6", "numTrayNotifications", "preferredGvfsScheme"})
public class Settings implements Serializable {
private static final long serialVersionUID = 7609959894417878744L;
@@ -27,6 +27,7 @@ public class Settings implements Serializable {
public static final int DEFAULT_PORT = 42427;
public static final boolean DEFAULT_USE_IPV6 = false;
public static final Integer DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
public static final String DEFAULT_GVFS_SCHEME = "dav";
private final Consumer<Settings> saveCmd;
@@ -45,6 +46,9 @@ public class Settings implements Serializable {
@JsonProperty("numTrayNotifications")
private Integer numTrayNotifications;
@JsonProperty("preferredGvfsScheme")
private String preferredGvfsScheme;
/**
* Package-private constructor; use {@link SettingsProvider}.
*/
@@ -113,4 +117,12 @@ public class Settings implements Serializable {
this.numTrayNotifications = numTrayNotifications;
}
public String getPreferredGvfsScheme() {
return preferredGvfsScheme == null ? DEFAULT_GVFS_SCHEME : preferredGvfsScheme;
}
public void setPreferredGvfsScheme(String preferredGvfsScheme) {
this.preferredGvfsScheme = preferredGvfsScheme;
}
}

View File

@@ -325,6 +325,32 @@
-fx-background-color: COLOR_TEXT;
}
/*******************************************************************************
* *
* ChoiceBox *
* *
******************************************************************************/
.choice-box {
-fx-background-color: COLOR_BORDER_DARK, COLOR_BACKGROUND;
-fx-background-insets: 0, 1;
-fx-background-radius: 0, 0;
-fx-padding: 0.1em 0.6em 0.1em 0.6em;
-fx-text-fill: COLOR_TEXT;
}
.choice-box > .open-button > .arrow {
-fx-background-color: transparent, COLOR_TEXT;
-fx-background-insets: 0 0 -1 0, 0;
-fx-padding: 0.166667em 0.333333em 0.166667em 0.333333em; /* 2 4 2 4 */
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
}
.choice-box .context-menu {
-fx-background-color: COLOR_BORDER, #FFF;
-fx-background-insets: 0, 1;
}
/****************************************************************************
* *
* ProgressIndicator *

View File

@@ -15,6 +15,7 @@
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.ChoiceBox?>
<VBox prefWidth="400.0" alignment="TOP_CENTER" spacing="12.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
<Label VBox.vgrow="NEVER" fx:id="versionLabel" alignment="CENTER" cacheShape="true" cache="true" />
@@ -40,6 +41,11 @@
<!-- Row 2 -->
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" fx:id="useIpv6Label" text="%settings.useipv6.label" cacheShape="true" cache="true" />
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="useIpv6Checkbox" cacheShape="true" cache="true" />
<!-- Row 3 -->
<Label GridPane.rowIndex="3" GridPane.columnIndex="0" fx:id="prefGvfsSchemeLabel" text="%settings.prefGvfsScheme.label" cacheShape="true" cache="true" />
<ChoiceBox GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="prefGvfsScheme" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
</children>
</GridPane>
<Label VBox.vgrow="NEVER" text="%settings.requiresRestartLabel" alignment="CENTER" cacheShape="true" cache="true" />

View File

@@ -94,6 +94,7 @@ settings.checkForUpdates.label=Check for updates
settings.port.label=WebDAV Port *
settings.port.prompt=0 = Choose automatically
settings.useipv6.label=Use IPv6 literal
settings.prefGvfsScheme.label=WebDAV scheme
settings.requiresRestartLabel=* Cryptomator needs to restart
# tray icon