From f53f2d2ca4b7eb0bde642eb82cca0a88041b0a22 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 17 Jun 2019 13:55:31 +0200 Subject: [PATCH 1/9] allow to store custom mount flags in the vault settings (required for #802) [ci skip] --- .../common/settings/VaultSettings.java | 6 ++++ .../settings/VaultSettingsJsonAdapter.java | 30 ++++++++++++++++++- .../VaultSettingsJsonAdapterTest.java | 28 ++++++++++++++++- main/pom.xml | 2 +- .../org/cryptomator/ui/model/FuseVolume.java | 18 +++++++++-- 5 files changed, 78 insertions(+), 6 deletions(-) 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 41c91efcf..8f30fd70f 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 @@ -20,6 +20,7 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Base64; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -44,6 +45,7 @@ public class VaultSettings { private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH); private final StringProperty individualMountPath = new SimpleStringProperty(); private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE); + private final ObjectProperty> mountFlags = new SimpleObjectProperty<>(List.of()); public VaultSettings(String id) { this.id = Objects.requireNonNull(id); @@ -147,6 +149,10 @@ public class VaultSettings { return usesReadOnlyMode; } + public ObjectProperty> mountFlags() { + return mountFlags; + } + /* 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 7a3f03b39..484e91e1c 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 @@ -6,12 +6,15 @@ package org.cryptomator.common.settings; import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; class VaultSettingsJsonAdapter { @@ -26,8 +29,10 @@ class VaultSettingsJsonAdapter { out.name("unlockAfterStartup").value(value.unlockAfterStartup().get()); out.name("revealAfterMount").value(value.revealAfterMount().get()); out.name("usesIndividualMountPath").value(value.usesIndividualMountPath().get()); - out.name("individualMountPath").value(value.individualMountPath().get()); //TODO: should this always be written? ( because it could contain metadata, which the user may not want to save!) + out.name("individualMountPath").value(value.individualMountPath().get()); out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get()); + out.name("mountFlags"); + writeMountFlags(out, value.mountFlags().get()); out.endObject(); } @@ -41,6 +46,7 @@ class VaultSettingsJsonAdapter { boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT; boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH; boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE; + List mountFlags = null; in.beginObject(); while (in.hasNext()) { @@ -73,6 +79,9 @@ class VaultSettingsJsonAdapter { case "usesReadOnlyMode": usesReadOnlyMode = in.nextBoolean(); break; + case "mountFlags": + mountFlags = readMountFlags(in); + break; default: LOG.warn("Unsupported vault setting found in JSON: " + name); in.skipValue(); @@ -90,7 +99,26 @@ class VaultSettingsJsonAdapter { vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath); vaultSettings.individualMountPath().set(individualMountPath); vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode); + vaultSettings.mountFlags().set(mountFlags); return vaultSettings; } + private List readMountFlags(JsonReader in) throws IOException { + List result = new ArrayList<>(); + in.beginArray(); + while (!JsonToken.END_ARRAY.equals(in.peek())) { + result.add(in.nextString()); + } + in.endArray(); + return result; + } + + private void writeMountFlags(JsonWriter out, List flags) throws IOException { + out.beginArray(); + for (String flag : flags) { + out.value(flag); + } + out.endArray(); + } + } 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 32f393842..eb3bb1582 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 @@ -6,12 +6,17 @@ package org.cryptomator.common.settings; import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.StringReader; +import java.io.StringWriter; import java.nio.file.Paths; +import java.util.Arrays; public class VaultSettingsJsonAdapterTest { @@ -19,7 +24,7 @@ public class VaultSettingsJsonAdapterTest { @Test public void testDeserialize() throws IOException { - String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\"}"; + String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":[\"--foo\", \"--bar\"]}"; JsonReader jsonReader = new JsonReader(new StringReader(json)); VaultSettings vaultSettings = adapter.read(jsonReader); @@ -28,6 +33,27 @@ public class VaultSettingsJsonAdapterTest { Assertions.assertEquals("test", vaultSettings.mountName().get()); Assertions.assertEquals("X", vaultSettings.winDriveLetter().get()); Assertions.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get()); + MatcherAssert.assertThat(vaultSettings.mountFlags().get(), CoreMatchers.hasItems("--foo", "--bar")); + + + } + + @Test + public void testSerialize() throws IOException { + VaultSettings vaultSettings = new VaultSettings("test"); + vaultSettings.path().set(Paths.get("/foo/bar")); + vaultSettings.mountName().set("mountyMcMountFace"); + vaultSettings.mountFlags().set(Arrays.asList("--foo", "--bar")); + + StringWriter buf = new StringWriter(); + JsonWriter jsonWriter = new JsonWriter(buf); + adapter.write(jsonWriter, vaultSettings); + String result = buf.toString(); + + MatcherAssert.assertThat(result, CoreMatchers.containsString("\"id\":\"test\"")); + MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"/foo/bar\"")); + MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountName\":\"mountyMcMountFace\"")); + MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountFlags\":[\"--foo\",\"--bar\"]")); } } diff --git a/main/pom.xml b/main/pom.xml index ffe4cb6c5..6a4dd6e33 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -27,7 +27,7 @@ 1.2.1 1.8.5 2.0.0 - 1.1.3 + 1.2.0-SNAPSHOT 1.1.8 1.0.10 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 index fcd69493d..dbce4eccb 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java @@ -9,6 +9,7 @@ import org.cryptomator.frontend.fuse.mount.EnvironmentVariables; import org.cryptomator.frontend.fuse.mount.FuseMountFactory; import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException; import org.cryptomator.frontend.fuse.mount.Mount; +import org.cryptomator.frontend.fuse.mount.Mounter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,6 +21,7 @@ import java.nio.file.Files; import java.nio.file.NotDirectoryException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import java.util.Optional; public class FuseVolume implements Volume { @@ -96,16 +98,26 @@ public class FuseVolume implements Volume { private void mount(Path root) throws VolumeException { try { + Mounter mounter = FuseMountFactory.getMounter(); EnvironmentVariables envVars = EnvironmentVariables.create() // - .withMountName(vaultSettings.mountName().getValue()) // - .withMountPath(mountPoint) // + .withFlags(mountFlags(mounter)) + .withMountPoint(mountPoint) // .build(); - this.fuseMnt = FuseMountFactory.getMounter().mount(root, envVars); + this.fuseMnt = mounter.mount(root, envVars); } catch (CommandFailedException e) { throw new VolumeException("Unable to mount Filesystem", e); } } + private String[] mountFlags(Mounter mounter) { + List mountFlags = vaultSettings.mountFlags().get(); + if (mountFlags.isEmpty()) { + return mounter.defaultMountFlags(); + } else { + return mountFlags.toArray(String[]::new); + } + } + @Override public void reveal() throws VolumeException { try { From 367281fccb3a8a1bfbf8e27ece6a1fd6f1804123 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 18 Jun 2019 14:45:43 +0200 Subject: [PATCH 2/9] overwrite settings atomically --- .../cryptomator/common/settings/SettingsProvider.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java index 732b66fef..92efe5e1f 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java @@ -29,6 +29,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.Optional; import java.util.concurrent.Executors; @@ -109,11 +110,13 @@ public class SettingsProvider implements Provider { LOG.debug("Attempting to save settings to {}", settingsPath); try { Files.createDirectories(settingsPath.getParent()); - try (OutputStream out = Files.newOutputStream(settingsPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); // - Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) { + Path tmpPath = settingsPath.resolveSibling(settingsPath.getFileName().toString() + ".tmp"); + try (OutputStream out = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE_NEW); // + Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) { gson.toJson(settings, writer); - LOG.info("Settings saved to {}", settingsPath); } + Files.move(tmpPath, settingsPath, StandardCopyOption.REPLACE_EXISTING); + LOG.info("Settings saved to {}", settingsPath); } catch (IOException e) { LOG.error("Failed to save settings.", e); } From f909f384be8520816f9d5b25dc568781e95ed3cd Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 18 Jun 2019 15:00:25 +0200 Subject: [PATCH 3/9] improved error logging --- .../java/org/cryptomator/common/settings/SettingsProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java index 92efe5e1f..0aae9a541 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java @@ -10,6 +10,7 @@ package org.cryptomator.common.settings; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; import org.cryptomator.common.Environment; import org.cryptomator.common.LazyInitializer; import org.slf4j.Logger; @@ -117,7 +118,7 @@ public class SettingsProvider implements Provider { } Files.move(tmpPath, settingsPath, StandardCopyOption.REPLACE_EXISTING); LOG.info("Settings saved to {}", settingsPath); - } catch (IOException e) { + } catch (IOException | JsonParseException e) { LOG.error("Failed to save settings.", e); } } From 06abbf2dd1ccc854be10ba84c3c0d541807d2865 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 18 Jun 2019 15:00:54 +0200 Subject: [PATCH 4/9] custom mount flags default to empty flags --- .../java/org/cryptomator/common/settings/VaultSettings.java | 5 +++-- .../common/settings/VaultSettingsJsonAdapter.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) 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 8f30fd70f..67b010556 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 @@ -35,9 +35,10 @@ public class VaultSettings { public static final boolean DEFAULT_REAVEAL_AFTER_MOUNT = true; public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false; public static final boolean DEFAULT_USES_READONLY_MODE = false; + public static final List DEFAULT_MOUNT_FLAGS = List.of(); private final String id; - private final ObjectProperty path = new SimpleObjectProperty<>(); + private final ObjectProperty path = new SimpleObjectProperty(); private final StringProperty mountName = new SimpleStringProperty(); private final StringProperty winDriveLetter = new SimpleStringProperty(); private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP); @@ -45,7 +46,7 @@ public class VaultSettings { private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH); private final StringProperty individualMountPath = new SimpleStringProperty(); private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE); - private final ObjectProperty> mountFlags = new SimpleObjectProperty<>(List.of()); + private final ObjectProperty> mountFlags = new SimpleObjectProperty(DEFAULT_MOUNT_FLAGS); public VaultSettings(String id) { this.id = Objects.requireNonNull(id); 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 484e91e1c..11d94dd44 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 @@ -46,7 +46,7 @@ class VaultSettingsJsonAdapter { boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT; boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH; boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE; - List mountFlags = null; + List mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS; in.beginObject(); while (in.hasNext()) { From ed9adab9b437117de4baeb20a5164faaeb3af366 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 18 Jun 2019 16:31:10 +0200 Subject: [PATCH 5/9] store custom mount flags in settings.json as string instead of string array --- .../common/settings/VaultSettings.java | 8 +++--- .../settings/VaultSettingsJsonAdapter.java | 28 ++----------------- .../VaultSettingsJsonAdapterTest.java | 8 +++--- 3 files changed, 11 insertions(+), 33 deletions(-) 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 67b010556..0a0301266 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 @@ -35,7 +35,7 @@ public class VaultSettings { public static final boolean DEFAULT_REAVEAL_AFTER_MOUNT = true; public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false; public static final boolean DEFAULT_USES_READONLY_MODE = false; - public static final List DEFAULT_MOUNT_FLAGS = List.of(); + public static final String DEFAULT_MOUNT_FLAGS = ""; private final String id; private final ObjectProperty path = new SimpleObjectProperty(); @@ -46,7 +46,7 @@ public class VaultSettings { private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH); private final StringProperty individualMountPath = new SimpleStringProperty(); private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE); - private final ObjectProperty> mountFlags = new SimpleObjectProperty(DEFAULT_MOUNT_FLAGS); + private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS); public VaultSettings(String id) { this.id = Objects.requireNonNull(id); @@ -55,7 +55,7 @@ public class VaultSettings { } Observable[] observables() { - return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, usesIndividualMountPath, individualMountPath, usesReadOnlyMode}; + return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, usesIndividualMountPath, individualMountPath, usesReadOnlyMode, mountFlags}; } private void deriveMountNameFromPath(Path path) { @@ -150,7 +150,7 @@ public class VaultSettings { return usesReadOnlyMode; } - public ObjectProperty> mountFlags() { + public StringProperty mountFlags() { return mountFlags; } 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 11d94dd44..e5130cdb0 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 @@ -6,15 +6,12 @@ package org.cryptomator.common.settings; import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; class VaultSettingsJsonAdapter { @@ -31,8 +28,7 @@ class VaultSettingsJsonAdapter { out.name("usesIndividualMountPath").value(value.usesIndividualMountPath().get()); out.name("individualMountPath").value(value.individualMountPath().get()); out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get()); - out.name("mountFlags"); - writeMountFlags(out, value.mountFlags().get()); + out.name("mountFlags").value(value.mountFlags().get()); out.endObject(); } @@ -46,7 +42,7 @@ class VaultSettingsJsonAdapter { boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT; boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH; boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE; - List mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS; + String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS; in.beginObject(); while (in.hasNext()) { @@ -80,7 +76,7 @@ class VaultSettingsJsonAdapter { usesReadOnlyMode = in.nextBoolean(); break; case "mountFlags": - mountFlags = readMountFlags(in); + mountFlags = in.nextString(); break; default: LOG.warn("Unsupported vault setting found in JSON: " + name); @@ -103,22 +99,4 @@ class VaultSettingsJsonAdapter { return vaultSettings; } - private List readMountFlags(JsonReader in) throws IOException { - List result = new ArrayList<>(); - in.beginArray(); - while (!JsonToken.END_ARRAY.equals(in.peek())) { - result.add(in.nextString()); - } - in.endArray(); - return result; - } - - private void writeMountFlags(JsonWriter out, List flags) throws IOException { - out.beginArray(); - for (String flag : flags) { - out.value(flag); - } - out.endArray(); - } - } 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 eb3bb1582..62a37a5c2 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 @@ -24,7 +24,7 @@ public class VaultSettingsJsonAdapterTest { @Test public void testDeserialize() throws IOException { - String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":[\"--foo\", \"--bar\"]}"; + String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":\"--foo --bar\"}"; JsonReader jsonReader = new JsonReader(new StringReader(json)); VaultSettings vaultSettings = adapter.read(jsonReader); @@ -33,7 +33,7 @@ public class VaultSettingsJsonAdapterTest { Assertions.assertEquals("test", vaultSettings.mountName().get()); Assertions.assertEquals("X", vaultSettings.winDriveLetter().get()); Assertions.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get()); - MatcherAssert.assertThat(vaultSettings.mountFlags().get(), CoreMatchers.hasItems("--foo", "--bar")); + Assertions.assertEquals("--foo --bar", vaultSettings.mountFlags().get()); } @@ -43,7 +43,7 @@ public class VaultSettingsJsonAdapterTest { VaultSettings vaultSettings = new VaultSettings("test"); vaultSettings.path().set(Paths.get("/foo/bar")); vaultSettings.mountName().set("mountyMcMountFace"); - vaultSettings.mountFlags().set(Arrays.asList("--foo", "--bar")); + vaultSettings.mountFlags().set("--foo --bar"); StringWriter buf = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(buf); @@ -53,7 +53,7 @@ public class VaultSettingsJsonAdapterTest { MatcherAssert.assertThat(result, CoreMatchers.containsString("\"id\":\"test\"")); MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"/foo/bar\"")); MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountName\":\"mountyMcMountFace\"")); - MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountFlags\":[\"--foo\",\"--bar\"]")); + MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountFlags\":\"--foo --bar\"")); } } From b15d410378db19d06be0e1bfe8e9cabff0455cea Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 18 Jun 2019 16:39:56 +0200 Subject: [PATCH 6/9] Re-layouted unlock UI and added textfield for custom mount flags (atm only supported for FUSE - see #802) --- .../ui/controllers/UnlockController.java | 41 +++-- .../ui/model/DefaultMountFlags.java | 13 ++ .../cryptomator/ui/model/DokanyVolume.java | 2 +- .../org/cryptomator/ui/model/FuseVolume.java | 19 +-- .../org/cryptomator/ui/model/PerVault.java | 13 ++ .../java/org/cryptomator/ui/model/Vault.java | 33 +++- .../cryptomator/ui/model/VaultComponent.java | 1 - .../org/cryptomator/ui/model/VaultModule.java | 79 ++++++++-- .../java/org/cryptomator/ui/model/Volume.java | 2 +- .../cryptomator/ui/model/WebDavVolume.java | 3 +- main/ui/src/main/resources/fxml/unlock.fxml | 145 ++++++++---------- .../ui/src/main/resources/localization/en.txt | 5 +- 12 files changed, 226 insertions(+), 130 deletions(-) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/model/DefaultMountFlags.java create mode 100644 main/ui/src/main/java/org/cryptomator/ui/model/PerVault.java 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 3d90c689f..dd9808070 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 @@ -11,6 +11,7 @@ package org.cryptomator.ui.controllers; import com.google.common.base.CharMatcher; import com.google.common.base.Strings; import javafx.application.Application; +import javafx.beans.Observable; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; @@ -27,6 +28,7 @@ import javafx.scene.control.TextField; import javafx.scene.input.KeyEvent; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; import javafx.scene.text.Text; import javafx.stage.DirectoryChooser; import javafx.stage.Stage; @@ -113,6 +115,12 @@ public class UnlockController implements ViewController { @FXML private TextField mountName; + @FXML + private CheckBox useCustomMountFlags; + + @FXML + private TextField mountFlags; + @FXML private CheckBox revealAfterMount; @@ -134,17 +142,14 @@ public class UnlockController implements ViewController { @FXML private ProgressIndicator progressIndicator; - @FXML - private Text progressText; - @FXML private Hyperlink downloadsPageLink; @FXML - private GridPane advancedOptions; + private VBox advancedOptions; @FXML - private GridPane root; + private VBox root; @FXML private CheckBox unlockAfterStartup; @@ -158,6 +163,9 @@ public class UnlockController implements ViewController { unlockButton.disableProperty().bind(passwordField.textProperty().isEmpty()); mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents); mountName.textProperty().addListener(this::mountNameDidChange); + useCustomMountFlags.selectedProperty().addListener(this::useCustomMountFlagsDidChange); + mountFlags.disableProperty().bind(useCustomMountFlags.selectedProperty().not()); + mountFlags.textProperty().addListener(this::mountFlagsDidChange); savePassword.setDisable(!keychainAccess.isPresent()); unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not())); @@ -173,7 +181,6 @@ public class UnlockController implements ViewController { } } - @Override public Parent getRoot() { return root; @@ -199,7 +206,6 @@ public class UnlockController implements ViewController { advancedOptions.setVisible(false); advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show")); progressIndicator.setVisible(false); - progressText.setText(null); state.successMessage().map(localization::getString).ifPresent(messageText::setText); if (SystemUtils.IS_OS_WINDOWS) { winDriveLetter.valueProperty().removeListener(driveLetterChangeListener); @@ -212,6 +218,8 @@ public class UnlockController implements ViewController { } downloadsPageLink.setVisible(false); mountName.setText(vault.getMountName()); + useCustomMountFlags.setSelected(vault.isHavingCustomMountFlags()); + mountFlags.setText(vault.getMountFlags()); savePassword.setSelected(false); // auto-fill pw from keychain: if (keychainAccess.isPresent()) { @@ -318,6 +326,23 @@ public class UnlockController implements ViewController { } else { vault.setMountName(newValue); } + if (!useCustomMountFlags.isSelected()) { + mountFlags.setText(vault.getMountFlags()); // flags might depend on the volume name + } + } + + + private void useCustomMountFlagsDidChange(@SuppressWarnings("unused") ObservableValue property, @SuppressWarnings("unused")Boolean oldValue, Boolean newValue) { + if (!newValue) { + vault.setMountFlags(VaultSettings.DEFAULT_MOUNT_FLAGS); + mountFlags.setText(vault.getMountFlags()); + } + } + + private void mountFlagsDidChange(@SuppressWarnings("unused") ObservableValue property, @SuppressWarnings("unused")String oldValue, String newValue) { + if (useCustomMountFlags.isSelected()) { + vault.setMountFlags(newValue); + } } @FXML @@ -435,7 +460,6 @@ public class UnlockController implements ViewController { CharSequence password = passwordField.getCharacters(); Tasks.create(() -> { - progressText.setText(localization.getString("unlock.pendingMessage.unlocking")); vault.unlock(password); if (keychainAccess.isPresent() && savePassword.isSelected()) { keychainAccess.get().storePassphrase(vault.getId(), password); @@ -476,7 +500,6 @@ public class UnlockController implements ViewController { }).andFinally(() -> { advancedOptions.setDisable(false); progressIndicator.setVisible(false); - progressText.setText(null); }).runOnce(executor); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/DefaultMountFlags.java b/main/ui/src/main/java/org/cryptomator/ui/model/DefaultMountFlags.java new file mode 100644 index 000000000..50943a34f --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/DefaultMountFlags.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.model; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface DefaultMountFlags { +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java b/main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java index b9200a981..1c419830e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java @@ -44,7 +44,7 @@ public class DokanyVolume implements Volume { } @Override - public void mount(CryptoFileSystem fs) throws VolumeException, IOException { + public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException, IOException { Path mountPath = getMountPoint(); String mountName = vaultSettings.mountName().get(); try { 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 index dbce4eccb..b740dfcbd 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java @@ -1,5 +1,6 @@ package org.cryptomator.ui.model; +import com.google.common.base.Splitter; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Environment; import org.cryptomator.common.settings.VaultSettings; @@ -21,7 +22,6 @@ import java.nio.file.Files; import java.nio.file.NotDirectoryException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; import java.util.Optional; public class FuseVolume implements Volume { @@ -44,7 +44,7 @@ public class FuseVolume implements Volume { } @Override - public void mount(CryptoFileSystem fs) throws IOException, FuseNotSupportedException, VolumeException { + public void mount(CryptoFileSystem fs, String mountFlags) throws IOException, FuseNotSupportedException, VolumeException { Optional optionalCustomMountPoint = vaultSettings.getIndividualMountPath(); if (optionalCustomMountPoint.isPresent()) { Path customMountPoint = Paths.get(optionalCustomMountPoint.get()); @@ -55,7 +55,7 @@ public class FuseVolume implements Volume { this.mountPoint = prepareTemporaryMountPoint(); LOG.debug("Successfully created mount point: {}", mountPoint); } - mount(fs.getPath("/")); + mount(fs.getPath("/"), mountFlags); } private void checkProvidedMountPoint(Path mountPoint) throws IOException { @@ -96,11 +96,11 @@ public class FuseVolume implements Volume { throw new VolumeException("Did not find feasible mount point."); } - private void mount(Path root) throws VolumeException { + private void mount(Path root, String mountFlags) throws VolumeException { try { Mounter mounter = FuseMountFactory.getMounter(); EnvironmentVariables envVars = EnvironmentVariables.create() // - .withFlags(mountFlags(mounter)) + .withFlags(splitFlags(mountFlags)) .withMountPoint(mountPoint) // .build(); this.fuseMnt = mounter.mount(root, envVars); @@ -109,13 +109,8 @@ public class FuseVolume implements Volume { } } - private String[] mountFlags(Mounter mounter) { - List mountFlags = vaultSettings.mountFlags().get(); - if (mountFlags.isEmpty()) { - return mounter.defaultMountFlags(); - } else { - return mountFlags.toArray(String[]::new); - } + private String[] splitFlags(String str) { + return Splitter.on(' ').splitToList(str).toArray(String[]::new); } @Override diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/PerVault.java b/main/ui/src/main/java/org/cryptomator/ui/model/PerVault.java new file mode 100644 index 000000000..3172beec0 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/PerVault.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.model; + +import javax.inject.Scope; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Scope +@Documented +@Retention(RetentionPolicy.RUNTIME) +@interface PerVault { + +} 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 bd48150fc..325f6ba97 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 @@ -25,7 +25,6 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags; import org.cryptomator.cryptofs.CryptoFileSystemProvider; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.InvalidPassphraseException; -import org.cryptomator.ui.model.VaultModule.PerVault; import org.fxmisc.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +43,7 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; +import java.util.function.Supplier; @PerVault public class Vault { @@ -54,6 +54,7 @@ public class Vault { private final VaultSettings vaultSettings; private final Provider volumeProvider; + private final Supplier defaultMountFlags; private final AtomicReference cryptoFileSystem = new AtomicReference<>(); private final ObjectProperty state = new SimpleObjectProperty(State.LOCKED); @@ -64,9 +65,10 @@ public class Vault { } @Inject - Vault(VaultSettings vaultSettings, Provider volumeProvider) { + Vault(VaultSettings vaultSettings, Provider volumeProvider, @DefaultMountFlags Supplier defaultMountFlags) { this.vaultSettings = vaultSettings; this.volumeProvider = volumeProvider; + this.defaultMountFlags = defaultMountFlags; } // ****************************************************************************** @@ -110,7 +112,7 @@ public class Vault { } CryptoFileSystem fs = getCryptoFileSystem(passphrase); volume = volumeProvider.get(); - volume.mount(fs); + volume.mount(fs, getMountFlags()); Platform.runLater(() -> { state.set(State.UNLOCKED); }); @@ -241,10 +243,6 @@ public class Vault { } } - public String getMountName() { - return vaultSettings.mountName().get(); - } - public String getCustomMountPath() { return vaultSettings.individualMountPath().getValueSafe(); } @@ -253,6 +251,10 @@ public class Vault { vaultSettings.individualMountPath().set(mountPath); } + public String getMountName() { + return vaultSettings.mountName().get(); + } + public void setMountName(String mountName) throws IllegalArgumentException { if (StringUtils.isBlank(mountName)) { throw new IllegalArgumentException("mount name is empty"); @@ -261,6 +263,23 @@ public class Vault { } } + public boolean isHavingCustomMountFlags() { + return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get()); + } + + public String getMountFlags() { + String mountFlags = vaultSettings.mountFlags().get(); + if (Strings.isNullOrEmpty(mountFlags)) { + return defaultMountFlags.get(); + } else { + return mountFlags; + } + } + + public void setMountFlags(String mountFlags) { + vaultSettings.mountFlags().set(mountFlags); + } + public Character getWinDriveLetter() { if (vaultSettings.winDriveLetter().get() == null) { return null; diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java index 6fafed661..bca3dab63 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java @@ -7,7 +7,6 @@ package org.cryptomator.ui.model; import dagger.BindsInstance; import org.cryptomator.common.settings.VaultSettings; -import org.cryptomator.ui.model.VaultModule.PerVault; import dagger.Subcomponent; 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 100137f6f..8ca71d692 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 @@ -7,28 +7,24 @@ package org.cryptomator.ui.model; import dagger.Module; import dagger.Provides; +import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.settings.VolumeImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.inject.Scope; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +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 { private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class); - @Scope - @Documented - @Retention(RetentionPolicy.RUNTIME) - @interface PerVault { - - } - @Provides public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) { VolumeImpl preferredImpl = settings.preferredVolumeImpl().get(); @@ -45,4 +41,65 @@ public class VaultModule { } } + @Provides + @PerVault + @DefaultMountFlags + public Supplier provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { + VolumeImpl preferredImpl = settings.preferredVolumeImpl().get(); + switch (preferredImpl) { + case FUSE: + if (SystemUtils.IS_OS_MAC_OSX) { + return () -> getMacFuseDefaultMountFlags(settings, vaultSettings); + } else if (SystemUtils.IS_OS_LINUX) { + return () -> getLinuxFuseDefaultMountFlags(settings, vaultSettings); + } + case DOKANY: + return () -> getDokanyDefaultMountFlags(settings, vaultSettings); + default: + return () -> "--flags-supported-on-FUSE-or-DOKANY-only"; + } + } + + private String getMacFuseDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { + assert SystemUtils.IS_OS_MAC_OSX; + // see: https://github.com/osxfuse/osxfuse/wiki/Mount-options + 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"); + return "-ovolname=" + vaultSettings.mountName().get() // volume name + + " -ouid=" + uid // + + " -ogid=" + gid // + + " -oatomic_o_trunc" // + + " -oauto_xattr" // + + " -oauto_cache" // + + " -omodules=iconv,from_code=UTF-8,to_code=UTF-8-MAC" // show files names in Unicode NFD encoding + + " -onoappledouble" // vastly impacts performance for some reason... + + " -odefault_permissions"; // let the kernel assume permissions based on file attributes etc + } catch (IOException e) { + LOG.error("Could not read uid/gid from USER_HOME", e); + return ""; + } + } + + private String getLinuxFuseDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { + assert SystemUtils.IS_OS_LINUX; + 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"); + return "-oauto_unmount" // + + " -ouid=" + uid // + + " -ogid=" + gid; + } catch (IOException e) { + LOG.error("Could not read uid/gid from USER_HOME", e); + return ""; + } + } + + private String getDokanyDefaultMountFlags(Settings settings, VaultSettings vaultSettings) { + // TODO + return "--not-yet-supported"; + } + } 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 index 73f8d666b..dc1aae0cd 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java @@ -22,7 +22,7 @@ public interface Volume { * @param fs * @throws IOException */ - void mount(CryptoFileSystem fs) throws IOException, VolumeException; + void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException; void reveal() throws VolumeException; 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 index 2161e388d..118e3a368 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java @@ -11,7 +11,6 @@ import org.cryptomator.frontend.webdav.servlet.WebDavServletController; import javax.inject.Inject; import javax.inject.Provider; - import java.net.InetAddress; import java.net.UnknownHostException; @@ -35,7 +34,7 @@ public class WebDavVolume implements Volume { } @Override - public void mount(CryptoFileSystem fs) throws VolumeException { + public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException { if (server == null) { server = serverProvider.get(); } diff --git a/main/ui/src/main/resources/fxml/unlock.fxml b/main/ui/src/main/resources/fxml/unlock.fxml index 585b60b50..29a9b82e3 100644 --- a/main/ui/src/main/resources/fxml/unlock.fxml +++ b/main/ui/src/main/resources/fxml/unlock.fxml @@ -17,102 +17,81 @@ - + + - + + - + - - - - + + + - - -