diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java index 6258552da..227ed1884 100644 --- a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java +++ b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java @@ -11,8 +11,10 @@ import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.StringBinding; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.StringProperty; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.settings.Settings; @@ -25,7 +27,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.function.Supplier; @Module public class VaultModule { @@ -52,94 +53,87 @@ public class VaultModule { @PerVault @DefaultMountFlags public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { - BooleanBinding isMacFuse = new ReadOnlyBooleanWrapper(SystemUtils.IS_OS_MAC_OSX).and(settings.preferredVolumeImpl().isEqualTo(VolumeImpl.FUSE)); - BooleanBinding isLinuxFuse = new ReadOnlyBooleanWrapper(SystemUtils.IS_OS_LINUX).and(settings.preferredVolumeImpl().isEqualTo(VolumeImpl.FUSE)); - BooleanBinding isWinDokany = new ReadOnlyBooleanWrapper(SystemUtils.IS_OS_WINDOWS).and(settings.preferredVolumeImpl().isEqualTo(VolumeImpl.DOKANY)); + ObjectProperty preferredVolumeImpl = settings.preferredVolumeImpl(); + StringProperty mountName = vaultSettings.mountName(); + BooleanProperty readOnly = vaultSettings.usesReadOnlyMode(); - return Bindings.when(isMacFuse) // IF isMacFuse - .then(getMacFuseDefaultMountFlags(vaultSettings)) // - .otherwise(Bindings.when(isLinuxFuse) // ELSE IF isLinuxFuse - .then(getLinuxFuseDefaultMountFlags(vaultSettings)) // - .otherwise(Bindings.when(isWinDokany) // ELSE IF isWinDokany - .then(getDokanyDefaultMountFlags(vaultSettings)) // - .otherwise("--flags-supported-on-FUSE-or-DOKANY-only") // ELSE - ) // - ); + return Bindings.createStringBinding(() -> { + VolumeImpl v = preferredVolumeImpl.get(); + if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_MAC) { + return getMacFuseDefaultMountFlags(mountName, readOnly); + } else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_LINUX) { + return getLinuxFuseDefaultMountFlags(readOnly); + } else if (v == VolumeImpl.DOKANY && SystemUtils.IS_OS_WINDOWS) { + return getDokanyDefaultMountFlags(readOnly); + } else { + return "--flags-supported-on-FUSE-or-DOKANY-only"; + } + }, mountName, readOnly, preferredVolumeImpl); } // see: https://github.com/osxfuse/osxfuse/wiki/Mount-options - private StringBinding getMacFuseDefaultMountFlags(VaultSettings vaultSettings) { + private String getMacFuseDefaultMountFlags(ReadOnlyStringProperty mountName, ReadOnlyBooleanProperty readOnly) { assert SystemUtils.IS_OS_MAC_OSX; - StringProperty mountName = vaultSettings.mountName(); - BooleanProperty readOnly = vaultSettings.usesReadOnlyMode(); - return Bindings.createStringBinding(() -> { - StringBuilder flags = new StringBuilder(); - if (readOnly.get()) { - flags.append(" -ordonly"); - } - flags.append(" -ovolname=").append(mountName.get()); - flags.append(" -oatomic_o_trunc"); - flags.append(" -oauto_xattr"); - flags.append(" -oauto_cache"); - flags.append(" -omodules=iconv,from_code=UTF-8,to_code=UTF-8-MAC"); // show files names in Unicode NFD encoding - flags.append(" -onoappledouble"); // vastly impacts performance for some reason... - flags.append(" -odefault_permissions"); // let the kernel assume permissions based on file attributes etc + StringBuilder flags = new StringBuilder(); + if (readOnly.get()) { + flags.append(" -ordonly"); + } + flags.append(" -ovolname=").append(mountName.get()); + flags.append(" -oatomic_o_trunc"); + flags.append(" -oauto_xattr"); + flags.append(" -oauto_cache"); + flags.append(" -omodules=iconv,from_code=UTF-8,to_code=UTF-8-MAC"); // show files names in Unicode NFD encoding + flags.append(" -onoappledouble"); // vastly impacts performance for some reason... + flags.append(" -odefault_permissions"); // let the kernel assume permissions based on file attributes etc - try { - Path userHome = Paths.get(System.getProperty("user.home")); - int uid = (int) Files.getAttribute(userHome, "unix:uid"); - int gid = (int) Files.getAttribute(userHome, "unix:gid"); - flags.append(" -ouid=").append(uid); - flags.append(" -ogid=").append(gid); - } catch (IOException e) { - LOG.error("Could not read uid/gid from USER_HOME", e); - } + try { + Path userHome = Paths.get(System.getProperty("user.home")); + int uid = (int) Files.getAttribute(userHome, "unix:uid"); + int gid = (int) Files.getAttribute(userHome, "unix:gid"); + flags.append(" -ouid=").append(uid); + flags.append(" -ogid=").append(gid); + } catch (IOException e) { + LOG.error("Could not read uid/gid from USER_HOME", e); + } - return flags.toString().strip(); - }, mountName, readOnly); + return flags.toString().strip(); } // see https://manpages.debian.org/testing/fuse/mount.fuse.8.en.html - private StringBinding getLinuxFuseDefaultMountFlags(VaultSettings vaultSettings) { + private String getLinuxFuseDefaultMountFlags(ReadOnlyBooleanProperty readOnly) { assert SystemUtils.IS_OS_LINUX; - BooleanProperty readOnly = vaultSettings.usesReadOnlyMode(); - return Bindings.createStringBinding(() -> { - StringBuilder flags = new StringBuilder(); - if (readOnly.get()) { - flags.append(" -oro"); - } - flags.append(" -oauto_unmount"); + StringBuilder flags = new StringBuilder(); + if (readOnly.get()) { + flags.append(" -oro"); + } + flags.append(" -oauto_unmount"); - try { - Path userHome = Paths.get(System.getProperty("user.home")); - int uid = (int) Files.getAttribute(userHome, "unix:uid"); - int gid = (int) Files.getAttribute(userHome, "unix:gid"); - flags.append(" -ouid=").append(uid); - flags.append(" -ogid=").append(gid); - } catch (IOException e) { - LOG.error("Could not read uid/gid from USER_HOME", e); - } + try { + Path userHome = Paths.get(System.getProperty("user.home")); + int uid = (int) Files.getAttribute(userHome, "unix:uid"); + int gid = (int) Files.getAttribute(userHome, "unix:gid"); + flags.append(" -ouid=").append(uid); + flags.append(" -ogid=").append(gid); + } catch (IOException e) { + LOG.error("Could not read uid/gid from USER_HOME", e); + } - return flags.toString().strip(); - }, readOnly); + return flags.toString().strip(); } // see https://github.com/cryptomator/dokany-nio-adapter/blob/develop/src/main/java/org/cryptomator/frontend/dokany/MountUtil.java#L30-L34 - private StringBinding getDokanyDefaultMountFlags(VaultSettings vaultSettings) { + private String getDokanyDefaultMountFlags(ReadOnlyBooleanProperty readOnly) { assert SystemUtils.IS_OS_WINDOWS; - BooleanProperty readOnly = vaultSettings.usesReadOnlyMode(); - return Bindings.createStringBinding(() -> { - StringBuilder flags = new StringBuilder(); - flags.append(" --options CURRENT_SESSION"); - if (readOnly.get()) { - flags.append(",WRITE_PROTECTION"); - } - flags.append(" --thread-count 5"); - flags.append(" --timeout 10000"); - flags.append(" --allocation-unit-size 4096"); - flags.append(" --sector-size 4096"); - return flags.toString().strip(); - }, readOnly); + StringBuilder flags = new StringBuilder(); + flags.append(" --options CURRENT_SESSION"); + if (readOnly.get()) { + flags.append(",WRITE_PROTECTION"); + } + flags.append(" --thread-count 5"); + flags.append(" --timeout 10000"); + flags.append(" --allocation-unit-size 4096"); + flags.append(" --sector-size 4096"); + return flags.toString().strip(); } } diff --git a/main/commons/src/test/java/org/cryptomator/common/vaults/VaultModuleTest.java b/main/commons/src/test/java/org/cryptomator/common/vaults/VaultModuleTest.java new file mode 100644 index 000000000..651d1a232 --- /dev/null +++ b/main/commons/src/test/java/org/cryptomator/common/vaults/VaultModuleTest.java @@ -0,0 +1,66 @@ +package org.cryptomator.common.vaults; + +import javafx.beans.binding.StringBinding; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.common.settings.VolumeImpl; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.mockito.Mockito; + +public class VaultModuleTest { + + private final Settings settings = Mockito.mock(Settings.class); + private final VaultSettings vaultSettings = Mockito.mock(VaultSettings.class); + + private final VaultModule module = new VaultModule(); + + @BeforeEach + public void setup() { + Mockito.when(vaultSettings.mountName()).thenReturn(new SimpleStringProperty("TEST")); + Mockito.when(vaultSettings.usesReadOnlyMode()).thenReturn(new SimpleBooleanProperty(true)); + } + + @Test + @DisplayName("provideDefaultMountFlags on Mac/FUSE") + @EnabledOnOs(OS.MAC) + public void testMacFuseDefaultMountFlags() { + Mockito.when(settings.preferredVolumeImpl()).thenReturn(new SimpleObjectProperty<>(VolumeImpl.FUSE)); + + StringBinding result = module.provideDefaultMountFlags(settings, vaultSettings); + + MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ovolname=TEST")); + MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ordonly")); + } + + @Test + @DisplayName("provideDefaultMountFlags on Linux/FUSE") + @EnabledOnOs(OS.LINUX) + public void testLinuxFuseDefaultMountFlags() { + Mockito.when(settings.preferredVolumeImpl()).thenReturn(new SimpleObjectProperty<>(VolumeImpl.FUSE)); + + StringBinding result = module.provideDefaultMountFlags(settings, vaultSettings); + + MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-oro")); + } + + @Test + @DisplayName("provideDefaultMountFlags on Windows/Dokany") + @EnabledOnOs(OS.WINDOWS) + public void testWinDokanyDefaultMountFlags() { + Mockito.when(settings.preferredVolumeImpl()).thenReturn(new SimpleObjectProperty<>(VolumeImpl.DOKANY)); + + StringBinding result = module.provideDefaultMountFlags(settings, vaultSettings); + + MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("--options CURRENT_SESSION,WRITE_PROTECTION")); + } + +}