diff --git a/.travis.yml b/.travis.yml index 98a335288..f227b996d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ addons: build_command: "mvn -fmain/pom.xml clean test -DskipTests" branch_pattern: release.* install: +- curl -o $HOME/.m2/settings.xml https://gist.githubusercontent.com/cryptobot/cf5fbd909c4782aaeeeb7c7f4a1a43da/raw/082261a990b96dcb3e9a3ddb96fc4c2e493f62c0/settings.xml - mvn -fmain/pom.xml clean install -DskipTests dependency:go-offline -Pcoverage,release # "clean install" needed until we can exclude artifacts currently in the reactor, see https://maven.apache.org/plugins/maven-dependency-plugin/go-offline-mojo.html#excludeReactor and https://issues.apache.org/jira/browse/MDEP-568 script: - mvn --update-snapshots -fmain/pom.xml clean test jacoco:report verify -Pcoverage diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/NioAdapterImpl.java b/main/commons/src/main/java/org/cryptomator/common/settings/NioAdapterImpl.java new file mode 100644 index 000000000..b34ad340d --- /dev/null +++ b/main/commons/src/main/java/org/cryptomator/common/settings/NioAdapterImpl.java @@ -0,0 +1,8 @@ +package org.cryptomator.common.settings; + +public enum NioAdapterImpl { + + WEBDAV, + FUSE + +} diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java index a2721f4d3..88543cc01 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java @@ -2,7 +2,7 @@ * Copyright (c) 2014, 2017 Sebastian Stenzel * All rights reserved. * This program and the accompanying materials are made available under the terms of the accompanying LICENSE file. - * + * * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ @@ -30,6 +30,7 @@ public class Settings { public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3; public static final String DEFAULT_GVFS_SCHEME = "dav"; public static final boolean DEFAULT_DEBUG_MODE = false; + public static final String DEFAULT_NIO_ADAPTER = NioAdapterImpl.WEBDAV.name(); private final ObservableList directories = FXCollections.observableArrayList(VaultSettings::observables); private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES); @@ -37,6 +38,8 @@ public class Settings { private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS); private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME); private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE); + private final StringProperty nioAdapterImpl = new SimpleStringProperty(DEFAULT_NIO_ADAPTER); + private Consumer saveCmd; /** @@ -49,6 +52,7 @@ public class Settings { numTrayNotifications.addListener(this::somethingChanged); preferredGvfsScheme.addListener(this::somethingChanged); debugMode.addListener(this::somethingChanged); + nioAdapterImpl.addListener(this::somethingChanged); } void setSaveCmd(Consumer saveCmd) { @@ -91,4 +95,8 @@ public class Settings { return debugMode; } + public StringProperty usedNioAdapterImpl() { + return nioAdapterImpl; + } + } diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java index e474fcf7c..5f601cd28 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java @@ -33,6 +33,7 @@ public class SettingsJsonAdapter extends TypeAdapter { out.name("numTrayNotifications").value(value.numTrayNotifications().get()); out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get()); out.name("debugMode").value(value.debugMode().get()); + out.name("nioAdapterImpl").value(value.usedNioAdapterImpl().get()); out.endObject(); } @@ -52,27 +53,30 @@ public class SettingsJsonAdapter extends TypeAdapter { while (in.hasNext()) { String name = in.nextName(); switch (name) { - case "directories": - settings.getDirectories().addAll(readVaultSettingsArray(in)); - break; - case "checkForUpdatesEnabled": - settings.checkForUpdates().set(in.nextBoolean()); - break; - case "port": - settings.port().set(in.nextInt()); - break; - case "numTrayNotifications": - settings.numTrayNotifications().set(in.nextInt()); - break; - case "preferredGvfsScheme": - settings.preferredGvfsScheme().set(in.nextString()); - break; - case "debugMode": - settings.debugMode().set(in.nextBoolean()); - break; - default: - LOG.warn("Unsupported vault setting found in JSON: " + name); - in.skipValue(); + case "directories": + settings.getDirectories().addAll(readVaultSettingsArray(in)); + break; + case "checkForUpdatesEnabled": + settings.checkForUpdates().set(in.nextBoolean()); + break; + case "port": + settings.port().set(in.nextInt()); + break; + case "numTrayNotifications": + settings.numTrayNotifications().set(in.nextInt()); + break; + case "preferredGvfsScheme": + settings.preferredGvfsScheme().set(in.nextString()); + break; + case "debugMode": + settings.debugMode().set(in.nextBoolean()); + break; + case "nioAdapterImpl": + settings.usedNioAdapterImpl().set(in.nextString()); + break; + default: + LOG.warn("Unsupported vault setting found in JSON: " + name); + in.skipValue(); } } in.endObject(); diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java index 8ee82146f..3b24a4c8b 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java @@ -36,6 +36,7 @@ public class VaultSettings { private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP); private final BooleanProperty mountAfterUnlock = new SimpleBooleanProperty(DEFAULT_MOUNT_AFTER_UNLOCK); private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT); + private final StringProperty individualMountPath = new SimpleStringProperty(); public VaultSettings(String id) { this.id = Objects.requireNonNull(id); @@ -44,7 +45,7 @@ public class VaultSettings { } Observable[] observables() { - return new Observable[] {path, mountName, winDriveLetter, unlockAfterStartup, mountAfterUnlock, revealAfterMount}; + return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, mountAfterUnlock, revealAfterMount, individualMountPath}; } private void deriveMountNameFromPath(Path path) { @@ -123,6 +124,10 @@ public class VaultSettings { return revealAfterMount; } + public StringProperty individualMountPath() { + return individualMountPath; + } + /* Hashcode/Equals */ @Override diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java index d1de6231e..dde47dcd2 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java @@ -27,6 +27,9 @@ class VaultSettingsJsonAdapter { out.name("unlockAfterStartup").value(value.unlockAfterStartup().get()); out.name("mountAfterUnlock").value(value.mountAfterUnlock().get()); out.name("revealAfterMount").value(value.revealAfterMount().get()); + if(value.individualMountPath().isNotEmpty().get()){ + out.name("individualMountPath").value(value.individualMountPath().get()); + } out.endObject(); } @@ -34,6 +37,7 @@ class VaultSettingsJsonAdapter { String id = null; String path = null; String mountName = null; + String individualMountPath = null; String winDriveLetter = null; boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP; boolean mountAfterUnlock = VaultSettings.DEFAULT_MOUNT_AFTER_UNLOCK; @@ -43,30 +47,33 @@ class VaultSettingsJsonAdapter { while (in.hasNext()) { String name = in.nextName(); switch (name) { - case "id": - id = in.nextString(); - break; - case "path": - path = in.nextString(); - break; - case "mountName": - mountName = in.nextString(); - break; - case "winDriveLetter": - winDriveLetter = in.nextString(); - break; - case "unlockAfterStartup": - unlockAfterStartup = in.nextBoolean(); - break; - case "mountAfterUnlock": - mountAfterUnlock = in.nextBoolean(); - break; - case "revealAfterMount": - revealAfterMount = in.nextBoolean(); - break; - default: - LOG.warn("Unsupported vault setting found in JSON: " + name); - in.skipValue(); + case "id": + id = in.nextString(); + break; + case "path": + path = in.nextString(); + break; + case "mountName": + mountName = in.nextString(); + break; + case "winDriveLetter": + winDriveLetter = in.nextString(); + break; + case "unlockAfterStartup": + unlockAfterStartup = in.nextBoolean(); + break; + case "mountAfterUnlock": + mountAfterUnlock = in.nextBoolean(); + break; + case "revealAfterMount": + revealAfterMount = in.nextBoolean(); + break; + case "individualMountPath": + individualMountPath = in.nextString(); + break; + default: + LOG.warn("Unsupported vault setting found in JSON: " + name); + in.skipValue(); } } in.endObject(); @@ -78,6 +85,7 @@ class VaultSettingsJsonAdapter { vaultSettings.unlockAfterStartup().set(unlockAfterStartup); vaultSettings.mountAfterUnlock().set(mountAfterUnlock); vaultSettings.revealAfterMount().set(revealAfterMount); + vaultSettings.individualMountPath().set(individualMountPath); return vaultSettings; } diff --git a/main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java index 23ddaab1d..95bebf3bb 100644 --- a/main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java +++ b/main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java @@ -22,7 +22,9 @@ public class SettingsJsonAdapterTest { + "\"checkForUpdatesEnabled\": true,"// + "\"port\": 8080,"// + "\"useIpv6\": true,"// - + "\"numTrayNotifications\": 42}"; + + "\"numTrayNotifications\": 42,"// + + "\"nioAdapterImpl\": \"webdav\","// + + "\"defaultMountDir\": \"/home/test/crypto\"}"; Settings settings = adapter.fromJson(json); @@ -32,6 +34,7 @@ public class SettingsJsonAdapterTest { // Assert.assertTrue(settings.useIpv6().get()); temporarily ignored Assert.assertEquals(42, settings.numTrayNotifications().get()); Assert.assertEquals("dav", settings.preferredGvfsScheme().get()); + Assert.assertEquals("webdav", settings.usedNioAdapterImpl().get()); } } diff --git a/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java index 7320b6768..e3f31251e 100644 --- a/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java +++ b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java @@ -20,7 +20,7 @@ public class VaultSettingsJsonAdapterTest { @Test public void testDeserialize() throws IOException { - String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true}"; + String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\"}"; JsonReader jsonReader = new JsonReader(new StringReader(json)); VaultSettings vaultSettings = adapter.read(jsonReader); @@ -28,6 +28,7 @@ public class VaultSettingsJsonAdapterTest { Assert.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get()); Assert.assertEquals("test", vaultSettings.mountName().get()); Assert.assertEquals("X", vaultSettings.winDriveLetter().get()); + Assert.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get()); } } diff --git a/main/pom.xml b/main/pom.xml index 146aa28c4..a31f000ba 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -28,7 +28,8 @@ 1.5.0-SNAPSHOT 1.0.4 2.0.0 - + 0.1.1 + 2.5 3.6 @@ -95,6 +96,11 @@ cryptofs ${cryptomator.cryptofs.version} + + org.cryptomator + fuse-nio-adapter + ${cryptomator.fuse.version} + org.cryptomator webdav-nio-adapter diff --git a/main/ui/pom.xml b/main/ui/pom.xml index edbaecfc1..6c26bc1a0 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -30,7 +30,11 @@ org.cryptomator keychain - + + org.cryptomator + fuse-nio-adapter + + org.cryptomator diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java index 20ac7cac6..40f94e3eb 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java @@ -2,7 +2,7 @@ * Copyright (c) 2014, 2017 Sebastian Stenzel * All rights reserved. * This program and the accompanying materials are made available under the terms of the accompanying LICENSE file. - * + * * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ @@ -14,9 +14,12 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; +import javafx.beans.value.ObservableValue; +import javafx.scene.layout.GridPane; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.settings.Settings; import org.cryptomator.ui.l10n.Localization; +import org.cryptomator.common.settings.NioAdapterImpl; import com.google.common.base.CharMatcher; import com.google.common.base.Strings; @@ -52,6 +55,15 @@ public class SettingsController implements ViewController { @FXML private CheckBox checkForUpdatesCheckbox; + @FXML + private GridPane webdavVolume; + + @FXML + private GridPane fuseVolume; + + @FXML + private Label portFieldLabel; + @FXML private TextField portField; @@ -67,6 +79,12 @@ public class SettingsController implements ViewController { @FXML private ChoiceBox prefGvfsScheme; + @FXML + private Label volumeLabel; + + @FXML + private ChoiceBox volume; + @FXML private CheckBox debugModeCheckbox; @@ -75,25 +93,57 @@ public class SettingsController implements ViewController { @Override public void initialize() { + versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion.orElse("SNAPSHOT"))); checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally()); checkForUpdatesCheckbox.setSelected(settings.checkForUpdates().get() && !areUpdatesManagedExternally()); + + //NIOADAPTER + volume.getItems().addAll(getSupportedAdapters()); + volume.setValue(settings.usedNioAdapterImpl().get()); + volume.setVisible(true); + volume.getSelectionModel().selectedItemProperty().addListener((ObservableValue observable, String oldVal, String newVal) -> changeNioView(newVal)); + + + //WEBDAV + webdavVolume.setVisible(settings.usedNioAdapterImpl().getValue().equals(NioAdapterImpl.WEBDAV.name())); + webdavVolume.managedProperty().bind(webdavVolume.visibleProperty()); + prefGvfsScheme.managedProperty().bind(webdavVolume.visibleProperty()); + prefGvfsSchemeLabel.managedProperty().bind(webdavVolume.visibleProperty()); + portFieldLabel.managedProperty().bind(webdavVolume.visibleProperty()); + changePortButton.managedProperty().bind(webdavVolume.visibleProperty()); + portField.managedProperty().bind(webdavVolume.visibleProperty()); portField.setText(String.valueOf(settings.port().intValue())); portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents); changePortButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(portField.textProperty())); changePortButton.disableProperty().bind(Bindings.createBooleanBinding(this::isPortValid, portField.textProperty()).not()); - 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.preferredGvfsScheme().get()); + prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX); + prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX); + + //FUSE + fuseVolume.setVisible(settings.usedNioAdapterImpl().getValue().equals(NioAdapterImpl.FUSE.name())); + fuseVolume.managedProperty().bind(fuseVolume.visibleProperty()); + debugModeCheckbox.setSelected(settings.debugMode().get()); settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty()); settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty()); + settings.usedNioAdapterImpl().bind(volume.valueProperty()); settings.debugMode().bind(debugModeCheckbox.selectedProperty()); } + //TODO: how to implement this? + private String[] getSupportedAdapters() { + return new String[]{NioAdapterImpl.FUSE.name(), NioAdapterImpl.WEBDAV.name()}; + } + + private void changeNioView(String newVal) { + fuseVolume.setVisible(newVal.equalsIgnoreCase(NioAdapterImpl.FUSE.name())); + webdavVolume.setVisible(newVal.equalsIgnoreCase(NioAdapterImpl.WEBDAV.name())); + } + @Override public Parent getRoot() { return root; diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java index 6d30a7e2f..38bd40670 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java @@ -2,13 +2,14 @@ * Copyright (c) 2014, 2017 Sebastian Stenzel * All rights reserved. * This program and the accompanying materials are made available under the terms of the accompanying LICENSE file. - * + * * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ package org.cryptomator.ui.controllers; import java.io.IOException; +import java.nio.file.*; import java.util.Arrays; import java.util.Comparator; import java.util.Objects; @@ -16,8 +17,12 @@ import java.util.Optional; import javax.inject.Inject; +import javafx.beans.binding.Bindings; +import javafx.scene.layout.HBox; import org.apache.commons.lang3.CharUtils; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.NioAdapterImpl; +import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; @@ -72,17 +77,19 @@ public class UnlockController implements ViewController { private final WindowsDriveLetters driveLetters; private final ChangeListener driveLetterChangeListener = this::winDriveLetterDidChange; private final Optional keychainAccess; + private final Settings settings; private Vault vault; private Optional listener = Optional.empty(); private Subscription vaultSubs = Subscription.EMPTY; @Inject - public UnlockController(Application app, Localization localization, AsyncTaskService asyncTaskService, WindowsDriveLetters driveLetters, Optional keychainAccess) { + public UnlockController(Application app, Localization localization, AsyncTaskService asyncTaskService, WindowsDriveLetters driveLetters, Optional keychainAccess, Settings settings) { this.app = app; this.localization = localization; this.asyncTaskService = asyncTaskService; this.driveLetters = driveLetters; this.keychainAccess = keychainAccess; + this.settings = settings; } @FXML @@ -112,6 +119,18 @@ public class UnlockController implements ViewController { @FXML private ChoiceBox winDriveLetter; + @FXML + private HBox mountPathBox; + + @FXML + private Label mountPathLabel; + + @FXML + private TextField mountPath; + + @FXML + private Button changeMountPathButton; + @FXML private ProgressIndicator progressIndicator; @@ -140,14 +159,29 @@ public class UnlockController implements ViewController { mountName.textProperty().addListener(this::mountNameDidChange); savePassword.setDisable(!keychainAccess.isPresent()); unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not())); + + mountPathBox.managedProperty().bind(mountPathLabel.visibleProperty()); + mountPath.managedProperty().bind(mountPathLabel.visibleProperty()); + changeMountPathButton.managedProperty().bind(mountPathLabel.visibleProperty()); + if (SystemUtils.IS_OS_WINDOWS) { winDriveLetter.setConverter(new WinDriveLetterLabelConverter()); + mountPathLabel.setVisible(false); + mountPathLabel.setManaged(false); + //dirty cheat + mountPathBox.setMouseTransparent(true); } else { winDriveLetterLabel.setVisible(false); winDriveLetterLabel.setManaged(false); winDriveLetter.setVisible(false); winDriveLetter.setManaged(false); + if(settings.usedNioAdapterImpl().isEqualTo(NioAdapterImpl.WEBDAV.name()).get()){ + mountPathLabel.setVisible(false); + mountPathLabel.setManaged(false); + } } + changeMountPathButton.disableProperty().bind(Bindings.createBooleanBinding(this::isDirVaild, mountPath.textProperty()).not()); + } @Override @@ -200,14 +234,20 @@ public class UnlockController implements ViewController { Arrays.fill(storedPw, ' '); } } - VaultSettings settings = vault.getVaultSettings(); - unlockAfterStartup.setSelected(savePassword.isSelected() && settings.unlockAfterStartup().get()); - mountAfterUnlock.setSelected(settings.mountAfterUnlock().get()); - revealAfterMount.setSelected(settings.revealAfterMount().get()); + VaultSettings vaultSettings = vault.getVaultSettings(); + unlockAfterStartup.setSelected(savePassword.isSelected() && vaultSettings.unlockAfterStartup().get()); + mountAfterUnlock.setSelected(vaultSettings.mountAfterUnlock().get()); + revealAfterMount.setSelected(vaultSettings.revealAfterMount().get()); + + vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), vaultSettings.unlockAfterStartup()::set)); + vaultSubs = vaultSubs.and(EasyBind.subscribe(mountAfterUnlock.selectedProperty(), vaultSettings.mountAfterUnlock()::set)); + vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), vaultSettings.revealAfterMount()::set)); + + changeMountPathButton.visibleProperty().bind( + vaultSettings.individualMountPath().isNotEqualTo(mountPath.textProperty()) + ); + mountPath.textProperty().setValue(vaultSettings.individualMountPath().getValueSafe()); - vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), settings.unlockAfterStartup()::set)); - vaultSubs = vaultSubs.and(EasyBind.subscribe(mountAfterUnlock.selectedProperty(), settings.mountAfterUnlock()::set)); - vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), settings.revealAfterMount()::set)); } // **************************************** @@ -233,6 +273,28 @@ public class UnlockController implements ViewController { } } + @FXML + private void didClickchangeMountPathButton(ActionEvent event) { + assert isDirVaild(); + vault.setMountPath(mountPath.getText()); + } + + private boolean isDirVaild() { + try { + if (!mountPath.textProperty().isEmpty().get()) { + Path p = Paths.get(mountPath.textProperty().get()); + return Files.isDirectory(p) && Files.isReadable(p) && Files.isWritable(p) && Files.isExecutable(p); + } else { + return false; + } + + } catch (InvalidPathException e) { + LOG.info("Invalid path"); + return false; + } + } + + private void filterAlphanumericKeyEvents(KeyEvent t) { if (!Strings.isNullOrEmpty(t.getCharacter()) && !ALPHA_NUMERIC_MATCHER.matchesAllOf(t.getCharacter())) { t.consume(); @@ -397,6 +459,7 @@ public class UnlockController implements ViewController { @FunctionalInterface interface UnlockListener { + void didUnlock(Vault vault); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java index fdafb99c4..d1f0e0417 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java @@ -244,8 +244,8 @@ public class UnlockedController implements ViewController { @FXML private void didClickCopyUrl(ActionEvent event) { ClipboardContent clipboardContent = new ClipboardContent(); - clipboardContent.putUrl(vault.get().getWebDavUrl()); - clipboardContent.putString(vault.get().getWebDavUrl()); + clipboardContent.putUrl(vault.get().getFilesystemRootUrl()); + clipboardContent.putString(vault.get().getFilesystemRootUrl()); Clipboard.getSystemClipboard().setContent(clipboardContent); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java b/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java index 3d5ac4201..ba982c872 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java @@ -18,7 +18,6 @@ import javax.inject.Inject; import javax.inject.Singleton; import org.cryptomator.cryptolib.api.CryptoException; -import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException; import org.cryptomator.keychain.KeychainAccess; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/CommandFailedException.java b/main/ui/src/main/java/org/cryptomator/ui/model/CommandFailedException.java new file mode 100644 index 000000000..77d04f845 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/CommandFailedException.java @@ -0,0 +1,17 @@ +package org.cryptomator.ui.model; + +public class CommandFailedException extends Exception { + + public CommandFailedException(String message) { + super(message); + } + + public CommandFailedException(Throwable cause) { + super(cause); + } + + public CommandFailedException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java b/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java new file mode 100644 index 000000000..7751dadd0 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java @@ -0,0 +1,131 @@ +package org.cryptomator.ui.model; + +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.cryptofs.CryptoFileSystem; + +import org.cryptomator.frontend.fuse.mount.EnvironmentVariables; +import org.cryptomator.frontend.fuse.mount.FuseMount; +import org.cryptomator.frontend.fuse.mount.MountFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import java.nio.file.Paths; + +@VaultModule.PerVault +public class FuseVolume implements Volume { + + private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class); + private static final String DEFAULT_MOUNTROOTPATH_MAC = System.getProperty("user.home") + "/Library/Application Support/Cryptomator"; + private static final String DEFAULT_MOUNTROOTPATH_LINUX = System.getProperty("user.home") + "/.Cryptomator"; + + private final FuseMount fuseMnt; + private final VaultSettings vaultSettings; + private final WindowsDriveLetters windowsDriveLetters; + + private CryptoFileSystem cfs; + + @Inject + public FuseVolume(VaultSettings vaultSettings, WindowsDriveLetters windowsDriveLetters) { + this.vaultSettings = vaultSettings; + this.windowsDriveLetters = windowsDriveLetters; + this.fuseMnt = MountFactory.createMountObject(); + } + + @Override + public void prepare(CryptoFileSystem fs) { + this.cfs = fs; + if (!(vaultSettings.individualMountPath().isNotNull().get() || SystemUtils.IS_OS_WINDOWS)) { + fuseMnt.useExtraMountDir(); + } + } + + @Override + public void mount() throws CommandFailedException { + try { + EnvironmentVariables envVars = EnvironmentVariables.create() + .withMountName(vaultSettings.mountName().getValue() + "_ID-" + vaultSettings.getId()) + .withMountPath(chooseMountRootPath()) + .build(); + fuseMnt.mount(cfs.getPath("/"), envVars); + } catch (Exception e) { + throw new CommandFailedException("Unable to mount Filesystem", e); + } + } + + private String chooseMountRootPath() { + if (SystemUtils.IS_OS_WINDOWS) { + //windows case + if (vaultSettings.winDriveLetter().get() != null) { + // specific drive letter selected + return vaultSettings.winDriveLetter().getValue() + ":\\"; + } else { + // auto assign drive letter selected + return windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\"; + } + } else if (vaultSettings.individualMountPath().isNotNull().get()) { + //specific path given + return vaultSettings.individualMountPath().getValue(); + } + //choose default path + return SystemUtils.IS_OS_MAC ? DEFAULT_MOUNTROOTPATH_MAC : DEFAULT_MOUNTROOTPATH_LINUX; + } + + @Override + public void reveal() throws CommandFailedException { + //fuseMnt.reveal(); + } + + @Override + public synchronized void unmount() throws CommandFailedException { + if (cfs.getStats().pollBytesRead() == 0 && cfs.getStats().pollBytesWritten() == 0) { + unmountRaw(); + } else { + throw new CommandFailedException("Pending read or write operations."); + } + } + + @Override + public synchronized void unmountForced() throws CommandFailedException { + this.unmountRaw(); + } + + private synchronized void unmountRaw() throws CommandFailedException { + try { + fuseMnt.unmount(); + } catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) { + throw new CommandFailedException(e); + } + } + + @Override + public void stop() { + try { + fuseMnt.cleanUp(); + } catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) { + LOG.warn(e.getMessage()); + } + } + + @Override + public String getMountUri() { + return Paths.get(fuseMnt.getMountPath()).toUri().toString(); + } + + /** + * TODO: chang this to a real implementation + * + * @return + */ + @Override + public boolean isSupported() { + return true; + } + + @Override + public boolean supportsForcedUnmount() { + return true; + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java index 2b57d14ab..f1040a56e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java @@ -9,25 +9,18 @@ package org.cryptomator.ui.model; import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.nio.file.FileAlreadyExistsException; -import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; import java.util.function.Predicate; import javax.inject.Inject; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.LazyInitializer; -import org.cryptomator.common.Optionals; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.cryptofs.CryptoFileSystem; @@ -35,17 +28,7 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties; import org.cryptomator.cryptofs.CryptoFileSystemProvider; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.InvalidPassphraseException; -import org.cryptomator.frontend.webdav.ServerLifecycleException; -import org.cryptomator.frontend.webdav.WebDavServer; -import org.cryptomator.frontend.webdav.mount.MountParams; -import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException; -import org.cryptomator.frontend.webdav.mount.Mounter.Mount; -import org.cryptomator.frontend.webdav.mount.Mounter.UnmountOperation; -import org.cryptomator.frontend.webdav.servlet.WebDavServletController; import org.cryptomator.ui.model.VaultModule.PerVault; -import org.fxmisc.easybind.EasyBind; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javafx.application.Platform; import javafx.beans.Observable; @@ -53,6 +36,12 @@ import javafx.beans.binding.Binding; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.StringProperty; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.SystemUtils; +import org.fxmisc.easybind.EasyBind; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @PerVault public class Vault { @@ -64,22 +53,20 @@ public class Vault { private final Settings settings; private final VaultSettings vaultSettings; - private final WebDavServer server; private final AtomicReference cryptoFileSystem = new AtomicReference<>(); private final ObjectProperty state = new SimpleObjectProperty(State.LOCKED); - private WebDavServletController servlet; - private Mount mount; + private Volume volume; public enum State { LOCKED, UNLOCKED, MOUNTING, MOUNTED, UNMOUNTING - }; + } @Inject - Vault(Settings settings, VaultSettings vaultSettings, WebDavServer server) { + Vault(Settings settings, VaultSettings vaultSettings, Volume volume) { this.settings = settings; this.vaultSettings = vaultSettings; - this.server = server; + this.volume = volume; } // ****************************************************************************** @@ -111,80 +98,48 @@ public class Vault { CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase); } - public synchronized void unlock(CharSequence passphrase) throws ServerLifecycleException, CryptoException, IOException { - FileSystem fs = getCryptoFileSystem(passphrase); - if (!server.isRunning()) { - server.start(); - } - servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get()); - servlet.start(); + public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException { + CryptoFileSystem fs = getCryptoFileSystem(passphrase); + volume.prepare(fs); Platform.runLater(() -> { state.set(State.UNLOCKED); }); } public synchronized void mount() throws CommandFailedException { - if (servlet == null) { - throw new IllegalStateException("Mounting requires unlocked WebDAV servlet."); - } - - MountParams mountParams = MountParams.create() // - .withWindowsDriveLetter(vaultSettings.winDriveLetter().get()) // - .withPreferredGvfsScheme(settings.preferredGvfsScheme().get()) // - .withWebdavHostname(getLocalhostAliasOrNull()) // - .build(); - Platform.runLater(() -> { state.set(State.MOUNTING); }); - mount = servlet.mount(mountParams); // might block this thread for a while + volume.mount(); Platform.runLater(() -> { state.set(State.MOUNTED); }); } - private String getLocalhostAliasOrNull() { - try { - InetAddress alias = InetAddress.getByName(LOCALHOST_ALIAS); - if (alias.getHostAddress().equals("127.0.0.1")) { - return LOCALHOST_ALIAS; - } else { - return null; - } - } catch (UnknownHostException e) { - return null; - } + public synchronized void unmountForced() throws CommandFailedException { + unmount(true); } public synchronized void unmount() throws CommandFailedException { - unmount(Function.identity()); + unmount(false); } - public synchronized void unmountForced() throws CommandFailedException { - unmount(Optionals.unwrap(Mount::forced)); - } - - private synchronized void unmount(Function unmountOperationChooser) throws CommandFailedException { + private synchronized void unmount(boolean forced) throws CommandFailedException { Platform.runLater(() -> { state.set(State.UNMOUNTING); }); - if (mount != null) { - unmountOperationChooser.apply(mount).unmount(); - mount = null; + if (forced && volume.supportsForcedUnmount()) { + volume.unmountForced(); + } else { + volume.unmount(); } Platform.runLater(() -> { state.set(State.UNLOCKED); }); } - public boolean supportsForcedUnmount() { - return mount != null && mount.forced().isPresent(); - } - - public synchronized void lock() throws ServerLifecycleException, IOException { - if (servlet != null) { - servlet.stop(); - } + public synchronized void lock() throws IOException { + volume.stop(); CryptoFileSystem fs = cryptoFileSystem.getAndSet(null); if (fs != null) { fs.close(); @@ -201,7 +156,7 @@ public class Vault { try { unmount(); } catch (CommandFailedException e) { - if (supportsForcedUnmount()) { + if (volume.supportsForcedUnmount()) { try { unmountForced(); } catch (CommandFailedException e1) { @@ -219,9 +174,7 @@ public class Vault { } public void reveal() throws CommandFailedException { - if (mount != null) { - mount.reveal(); - } + volume.reveal(); } // ****************************************************************************** @@ -243,17 +196,13 @@ public class Vault { } public Observable[] observables() { - return new Observable[] {state}; + return new Observable[]{state}; } public VaultSettings getVaultSettings() { return vaultSettings; } - public synchronized String getWebDavUrl() { - return servlet.getServletRootUri().toString(); - } - public Path getPath() { return vaultSettings.path().getValue(); } @@ -308,6 +257,14 @@ public class Vault { return vaultSettings.mountName().get(); } + public StringProperty getMountPathProperty() { + return vaultSettings.individualMountPath(); + } + + public void setMountPath(String mountPath) { + vaultSettings.individualMountPath().set(mountPath); + } + public void setMountName(String mountName) throws IllegalArgumentException { if (StringUtils.isBlank(mountName)) { throw new IllegalArgumentException("mount name is empty"); @@ -332,6 +289,10 @@ public class Vault { } } + public String getFilesystemRootUrl() { + return volume.getMountUri(); + } + public String getId() { return vaultSettings.getId(); } @@ -355,4 +316,7 @@ public class Vault { } } + public boolean supportsForcedUnmount() { + return volume.supportsForcedUnmount(); + } } \ No newline at end of file diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java index c8a622233..108b64660 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java @@ -12,6 +12,8 @@ import java.util.Objects; import javax.inject.Scope; +import org.cryptomator.common.settings.NioAdapterImpl; +import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.VaultSettings; import dagger.Module; @@ -36,6 +38,22 @@ public class VaultModule { @Documented @Retention(RetentionPolicy.RUNTIME) @interface PerVault { + + } + + @Provides + @PerVault + public Volume provideNioAdpater(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume) { + NioAdapterImpl impl = NioAdapterImpl.valueOf(settings.usedNioAdapterImpl().get()); + switch (impl) { + case WEBDAV: + return webDavVolume; + case FUSE: + return fuseVolume; + default: + //this should not happen! + throw new IllegalStateException("Unsupported NioAdapter: " + settings.usedNioAdapterImpl().get()); + } } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java b/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java new file mode 100644 index 000000000..24954a47c --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java @@ -0,0 +1,36 @@ +package org.cryptomator.ui.model; + +import org.cryptomator.cryptofs.CryptoFileSystem; + +/** + * Takes a Volume and usess it to mount an unlocked vault + */ +public interface Volume { + + void prepare(CryptoFileSystem fs); + + void mount() throws CommandFailedException; + + default void reveal() throws CommandFailedException { + throw new CommandFailedException("Not implemented."); + } + + void unmount() throws CommandFailedException; + + default void unmountForced() throws CommandFailedException { + throw new CommandFailedException("Operation not supported."); + } + + void stop(); + + String getMountUri(); + + default boolean isSupported() { + return false; + } + + default boolean supportsForcedUnmount() { + return false; + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java b/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java new file mode 100644 index 000000000..77ecabfce --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java @@ -0,0 +1,124 @@ +package org.cryptomator.ui.model; + + +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.cryptofs.CryptoFileSystem; +import org.cryptomator.frontend.webdav.WebDavServer; +import org.cryptomator.frontend.webdav.mount.MountParams; +import org.cryptomator.frontend.webdav.mount.Mounter; +import org.cryptomator.frontend.webdav.servlet.WebDavServletController; + +import javax.inject.Inject; +import java.net.InetAddress; +import java.net.UnknownHostException; + +@VaultModule.PerVault +public class WebDavVolume implements Volume { + + private static final String LOCALHOST_ALIAS = "cryptomator-vault"; + + private final WebDavServer server; + private final VaultSettings vaultSettings; + private final Settings settings; + + private WebDavServletController servlet; + private Mounter.Mount mount; + + @Inject + public WebDavVolume(WebDavServer server, VaultSettings vaultSettings, Settings settings) { + this.server = server; + this.vaultSettings = vaultSettings; + this.settings = settings; + } + + @Override + public void prepare(CryptoFileSystem fs) { + if (!server.isRunning()) { + server.start(); + } + servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get()); + servlet.start(); + } + + @Override + public void mount() throws CommandFailedException { + if (servlet == null) { + throw new IllegalStateException("Mounting requires unlocked WebDAV servlet."); + } + MountParams mountParams = MountParams.create() // + .withWindowsDriveLetter(vaultSettings.winDriveLetter().get()) // + .withPreferredGvfsScheme(settings.preferredGvfsScheme().get())// + .withWebdavHostname(getLocalhostAliasOrNull()) // + .build(); + try { + this.mount = servlet.mount(mountParams); // might block this thread for a while + } catch (Mounter.CommandFailedException e) { + e.printStackTrace(); + throw new CommandFailedException(e); + } + } + + @Override + public void reveal() throws CommandFailedException { + try { + mount.reveal(); + } catch (Mounter.CommandFailedException e) { + e.printStackTrace(); + throw new CommandFailedException(e); + } + } + + @Override + public synchronized void unmount() throws CommandFailedException { + try { + mount.unmount(); + } catch (Mounter.CommandFailedException e) { + throw new CommandFailedException(e); + } + } + + @Override + public synchronized void unmountForced() { + mount.forced(); + } + + private String getLocalhostAliasOrNull() { + try { + InetAddress alias = InetAddress.getByName(LOCALHOST_ALIAS); + if (alias.getHostAddress().equals("127.0.0.1")) { + return LOCALHOST_ALIAS; + } else { + return null; + } + } catch (UnknownHostException e) { + return null; + } + } + + @Override + public void stop() { + if (servlet != null) { + servlet.stop(); + } + + } + + public synchronized String getMountUri() { + return servlet.getServletRootUri().toString(); + } + + /** + * TODO: what to check wether it is implemented? + * + * @return + */ + @Override + public boolean isSupported() { + return true; + } + + public boolean supportsForcedUnmount() { + return mount != null && mount.forced().isPresent(); + } +} diff --git a/main/ui/src/main/resources/fxml/settings.fxml b/main/ui/src/main/resources/fxml/settings.fxml index d3f9c7c12..794181cf5 100644 --- a/main/ui/src/main/resources/fxml/settings.fxml +++ b/main/ui/src/main/resources/fxml/settings.fxml @@ -35,22 +35,32 @@