diff --git a/.idea/compiler.xml b/.idea/compiler.xml index eb6e630bd..90cd3c60e 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -29,5 +29,8 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 74a53dde5..0e7df1ed1 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -1,7 +1,8 @@ - + + diff --git a/.idea/misc.xml b/.idea/misc.xml index fd8e48573..c20d5a34e 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..bca3878d6 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Cryptomator_macOS.xml b/.idea/runConfigurations/Cryptomator_macOS.xml new file mode 100644 index 000000000..372287203 --- /dev/null +++ b/.idea/runConfigurations/Cryptomator_macOS.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/.travis-deploy-release.tmpl.json b/.travis-deploy-release.tmpl.json new file mode 100644 index 000000000..435e3dd82 --- /dev/null +++ b/.travis-deploy-release.tmpl.json @@ -0,0 +1,19 @@ +{ + "package": { + "name": "buildkit", + "repo": "cryptomator", + "subject": "cryptomator" + }, + "version": { + "name": "$TRAVIS_TAG", + "desc": "Cryptomator version $TRAVIS_TAG", + "released": "$TODAY", + "vcs_tag": "$TRAVIS_TAG", + "gpgSign": true + }, + "files": + [ + {"includePattern": "main/buildkit/target/(buildkit-[a-z]+\\.zip)", "uploadPattern": "/$TRAVIS_TAG/$1"} + ], + "publish": true +} diff --git a/.travis-deploy-snapshot.json b/.travis-deploy-snapshot.json new file mode 100644 index 000000000..fa3897a4d --- /dev/null +++ b/.travis-deploy-snapshot.json @@ -0,0 +1,15 @@ +{ + "package": { + "name": "buildkit", + "repo": "cryptomator", + "subject": "cryptomator" + }, + "version": { + "name": "snapshot" + }, + "files": + [ + {"includePattern": "main/buildkit/target/(buildkit-[a-z]+\\.zip)", "uploadPattern": "/snapshot/$1", "matrixParams": {"override": 1}} + ], + "publish": true +} diff --git a/.travis.yml b/.travis.yml index 00a1351d5..05b105162 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: java sudo: false jdk: -- oraclejdk9 +- openjdk11 cache: directories: - $HOME/.m2 @@ -34,40 +34,21 @@ before_deploy: mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=SNAPSHOT-$(echo $TRAVIS_COMMIT | head -c7) fi - mvn -fmain/pom.xml clean package -Prelease -DskipTests +- export TODAY=`date +'%Y-%m-%d'`; envsubst '$TRAVIS_TAG $TODAY' < .travis-deploy-release.tmpl.json > .travis-deploy-release.json deploy: -- provider: script # SNAPSHOTS +- provider: bintray # SNAPSHOTS + file: .travis-deploy-snapshot.json + user: cryptobot + key: $BINTRAY_API_KEY skip_cleanup: true - script: >- - curl -T main/ant-kit/target/antkit.zip - -u cryptobot:${BINTRAY_API_KEY} - -H "X-Bintray-Package:ant-kit" - -H "X-Bintray-Version:continuous" - -H "X-Bintray-Override:1" - -H "X-Bintray-Publish:1" - https://api.bintray.com/content/cryptomator/cryptomator/antkit-continuous.zip on: repo: cryptomator/cryptomator branch: develop - condition: $TRAVIS_TAG = '' -- provider: releases # RELEASE - prerelease: false - api_key: $GITHUB_API_KEY - file: - - "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar" +- provider: bintray # RELEASES + file: .travis-deploy-release.json + user: cryptobot + key: $BINTRAY_API_KEY skip_cleanup: true on: repo: cryptomator/cryptomator - tags: true -- provider: script - skip_cleanup: true - script: >- - curl -T main/ant-kit/target/antkit.zip - -u cryptobot:${BINTRAY_API_KEY} - -H "X-Bintray-Package:ant-kit" - -H "X-Bintray-Version:${TRAVIS_TAG}" - -H "X-Bintray-Override:1" - -H "X-Bintray-Publish:1" - https://api.bintray.com/content/cryptomator/cryptomator/antkit-${TRAVIS_TAG}.zip - on: - repo: cryptomator/cryptomator - tags: true + tags: true \ No newline at end of file diff --git a/main/ant-kit/pom.xml b/main/ant-kit/pom.xml deleted file mode 100644 index 17798d90e..000000000 --- a/main/ant-kit/pom.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - 4.0.0 - - org.cryptomator - main - 1.4.5 - - ant-kit - pom - Cryptomator Ant Build Kit - Builds a package that can be built with Ant locally - - - - org.cryptomator - launcher - - - - - - - - maven-dependency-plugin - - - copy-libs - prepare-package - - copy-dependencies - - - ${project.build.directory}/libs - - - - - - - - maven-resources-plugin - 3.0.2 - - - copy-resources - prepare-package - - copy-resources - - - ${project.build.directory} - \ - UTF-8 - - - src/main/resources - true - - build.xml - - - - src/main/resources - false - - logback.xml - - - - - - - - - - - maven-assembly-plugin - 3.1.0 - - - make-assembly - package - - single - - - - - - assembly.xml - - false - antkit - - - - - \ No newline at end of file diff --git a/main/ant-kit/src/main/resources/build.xml b/main/ant-kit/src/main/resources/build.xml deleted file mode 100644 index 7ed445448..000000000 --- a/main/ant-kit/src/main/resources/build.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/main/ant-kit/assembly.xml b/main/buildkit/assembly-linux.xml similarity index 58% rename from main/ant-kit/assembly.xml rename to main/buildkit/assembly-linux.xml index 661711fe4..9784212b5 100644 --- a/main/ant-kit/assembly.xml +++ b/main/buildkit/assembly-linux.xml @@ -7,6 +7,13 @@ zip + + target/ + + version.txt + + libs + target/libs @@ -15,24 +22,11 @@ libs - target/fixed-binaries - false - fixed-binaries - 755 - - - target/package - false - package - - - target + target/linux-libs - build.xml - logback.xml + *.jar - false - . + libs \ No newline at end of file diff --git a/main/buildkit/assembly-mac.xml b/main/buildkit/assembly-mac.xml new file mode 100644 index 000000000..1b4e94ef9 --- /dev/null +++ b/main/buildkit/assembly-mac.xml @@ -0,0 +1,32 @@ + + + tarball + false + + zip + + + + target/ + + version.txt + + libs + + + target/libs + + *.jar + + libs + + + target/mac-libs + + *.jar + + libs + + + \ No newline at end of file diff --git a/main/buildkit/assembly-win.xml b/main/buildkit/assembly-win.xml new file mode 100644 index 000000000..982ff6037 --- /dev/null +++ b/main/buildkit/assembly-win.xml @@ -0,0 +1,32 @@ + + + tarball + false + + zip + + + + target/ + + version.txt + + libs + + + target/libs + + *.jar + + libs + + + target/win-libs + + *.jar + + libs + + + \ No newline at end of file diff --git a/main/buildkit/pom.xml b/main/buildkit/pom.xml new file mode 100644 index 000000000..aadcb0778 --- /dev/null +++ b/main/buildkit/pom.xml @@ -0,0 +1,157 @@ + + + 4.0.0 + + org.cryptomator + main + 1.4.6 + + buildkit + pom + Cryptomator Build Kit + Builds a package that can be built with Ant locally + + + + org.cryptomator + launcher + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.1.0 + + + copy-resources + prepare-package + + copy-resources + + + ${project.build.directory} + + + ${project.basedir}/src/main/resources + + version.txt + + true + + + + + + + + + + maven-dependency-plugin + 3.1.1 + + + copy-libs + prepare-package + + copy-dependencies + + + ${project.build.directory}/libs + linux,mac,win + + + + copy-linux-libs + prepare-package + + copy-dependencies + + + ${project.build.directory}/linux-libs + org.openjfx + linux + + + + copy-mac-libs + prepare-package + + copy-dependencies + + + ${project.build.directory}/mac-libs + org.openjfx + mac + + + + copy-win-libs + prepare-package + + copy-dependencies + + + ${project.build.directory}/win-libs + org.openjfx + win + + + + + + + + maven-assembly-plugin + 3.1.1 + + + assemble-linux + package + + single + + + + assembly-linux.xml + + false + buildkit-linux + + + + assemble-mac + package + + single + + + + assembly-mac.xml + + false + buildkit-mac + + + + assemble-win + package + + single + + + + assembly-win.xml + + false + buildkit-win + + + + + + + \ No newline at end of file diff --git a/main/buildkit/src/main/resources/version.txt b/main/buildkit/src/main/resources/version.txt new file mode 100644 index 000000000..f2ab45c3b --- /dev/null +++ b/main/buildkit/src/main/resources/version.txt @@ -0,0 +1 @@ +${project.version} \ No newline at end of file diff --git a/main/commons/pom.xml b/main/commons/pom.xml index 4799a4f36..93c86b51b 100644 --- a/main/commons/pom.xml +++ b/main/commons/pom.xml @@ -4,13 +4,19 @@ org.cryptomator main - 1.4.5 + 1.4.6 commons Cryptomator Commons Shared utilities + + + org.openjfx + javafx-base + + com.google.guava diff --git a/main/commons/src/main/java/org/cryptomator/common/Environment.java b/main/commons/src/main/java/org/cryptomator/common/Environment.java new file mode 100644 index 000000000..861cfbb31 --- /dev/null +++ b/main/commons/src/main/java/org/cryptomator/common/Environment.java @@ -0,0 +1,95 @@ +package org.cryptomator.common; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Predicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +@Singleton +public class Environment { + + private static final Logger LOG = LoggerFactory.getLogger(Environment.class); + private static final String USER_HOME = System.getProperty("user.home"); + private static final Path RELATIVE_HOME_DIR = Paths.get("~"); + private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME); + private static final char PATH_LIST_SEP = ':'; + + @Inject + public Environment() { + LOG.debug("user.language: {}", System.getProperty("user.language")); + LOG.debug("user.region: {}", System.getProperty("user.region")); + LOG.debug("logback.configurationFile: {}", System.getProperty("logback.configurationFile")); + LOG.debug("cryptomator.settingsPath: {}", System.getProperty("cryptomator.settingsPath")); + LOG.debug("cryptomator.ipcPortPath: {}", System.getProperty("cryptomator.ipcPortPath")); + LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath")); + LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir")); + LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir")); + } + + public boolean useCustomLogbackConfig() { + return getPath("logback.configurationFile").map(Files::exists).orElse(false); + } + + public Stream getSettingsPath() { + return getPaths("cryptomator.settingsPath"); + } + + public Stream getIpcPortPath() { + return getPaths("cryptomator.ipcPortPath"); + } + + public Stream getKeychainPath() { + return getPaths("cryptomator.keychainPath"); + } + + public Optional getLogDir() { + return getPath("cryptomator.logDir").map(this::replaceHomeDir); + } + + public Optional getMountPointsDir() { + return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir); + } + + private Optional getPath(String propertyName) { + String value = System.getProperty(propertyName); + return Optional.ofNullable(value).map(Paths::get); + } + + // visible for testing + Stream getPaths(String propertyName) { + Stream rawSettingsPaths = getRawList(propertyName, PATH_LIST_SEP); + return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Paths::get).map(this::replaceHomeDir); + } + + private Path replaceHomeDir(Path path) { + if (path.startsWith(RELATIVE_HOME_DIR)) { + return ABSOLUTE_HOME_DIR.resolve(RELATIVE_HOME_DIR.relativize(path)); + } else { + return path; + } + } + + private Stream getRawList(String propertyName, char separator) { + String value = System.getProperty(propertyName); + if (value == null) { + return Stream.empty(); + } else { + Iterable iter = Splitter.on(separator).split(value); + Spliterator spliter = Spliterators.spliteratorUnknownSize(iter.iterator(), Spliterator.ORDERED | Spliterator.IMMUTABLE); + return StreamSupport.stream(spliter, false); + } + } + +} diff --git a/main/commons/src/main/java/org/cryptomator/common/FxApplicationScoped.java b/main/commons/src/main/java/org/cryptomator/common/FxApplicationScoped.java new file mode 100644 index 000000000..dd90f4a38 --- /dev/null +++ b/main/commons/src/main/java/org/cryptomator/common/FxApplicationScoped.java @@ -0,0 +1,13 @@ +package org.cryptomator.common; + +import javax.inject.Scope; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Scope +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface FxApplicationScoped { + +} 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 48b85ef96..3f4da5c45 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 @@ -19,6 +19,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Optional; @@ -27,6 +28,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Provider; @@ -34,6 +36,7 @@ import javax.inject.Singleton; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.Environment; import org.cryptomator.common.LazyInitializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,92 +48,77 @@ import com.google.gson.GsonBuilder; public class SettingsProvider implements Provider { private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class); - private static final Path DEFAULT_SETTINGS_PATH; private static final long SAVE_DELAY_MS = 1000; - static { - final FileSystem fs = FileSystems.getDefault(); - if (SystemUtils.IS_OS_WINDOWS) { - DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, "AppData/Roaming/Cryptomator/settings.json"); - } else if (SystemUtils.IS_OS_MAC_OSX) { - DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, "Library/Application Support/Cryptomator/settings.json"); - } else { - DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, ".Cryptomator/settings.json"); - } - } - private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor(); private final AtomicReference> scheduledSaveCmd = new AtomicReference<>(); private final AtomicReference settings = new AtomicReference<>(); private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter(); + private final Environment env; private final Gson gson; @Inject - public SettingsProvider() { + public SettingsProvider(Environment env) { + this.env = env; this.gson = new GsonBuilder() // .setPrettyPrinting().setLenient().disableHtmlEscaping() // .registerTypeAdapter(Settings.class, settingsJsonAdapter) // .create(); } - private Path getSettingsPath() { - final String settingsPathProperty = System.getProperty("cryptomator.settingsPath"); - return Optional.ofNullable(settingsPathProperty).filter(StringUtils::isNotBlank).map(this::replaceHomeDir).map(FileSystems.getDefault()::getPath).orElse(DEFAULT_SETTINGS_PATH); - } - - private String replaceHomeDir(String path) { - if (path.startsWith("~/")) { - return SystemUtils.USER_HOME + path.substring(1); - } else { - return path; - } - } - @Override public Settings get() { return LazyInitializer.initializeLazily(settings, this::load); } private Settings load() { - Settings settings; - final Path settingsPath = getSettingsPath(); - try (InputStream in = Files.newInputStream(settingsPath, StandardOpenOption.READ); // - Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { - settings = gson.fromJson(reader, Settings.class); + Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElse(new Settings()); + settings.setSaveCmd(this::scheduleSave); + return settings; + } + + private Stream tryLoad(Path path) { + LOG.debug("Attempting to load settings from {}", path); + try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ); // + Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) { + Settings settings = gson.fromJson(reader, Settings.class); if (settings == null) { throw new IOException("Unexpected EOF"); } - LOG.info("Settings loaded from " + settingsPath); + LOG.info("Settings loaded from {}", path); + return Stream.of(settings); + } catch (NoSuchFileException e) { + return Stream.empty(); } catch (IOException e) { - LOG.info("Failed to load settings, creating new one."); - settings = new Settings(); + LOG.warn("Exception while loading settings from " + path, e); + return Stream.empty(); } - settings.setSaveCmd(this::scheduleSave); - return settings; } private void scheduleSave(Settings settings) { if (settings == null) { return; } - ScheduledFuture saveCmd = saveScheduler.schedule(() -> { - this.save(settings); - }, SAVE_DELAY_MS, TimeUnit.MILLISECONDS); - ScheduledFuture previousSaveCmd = scheduledSaveCmd.getAndSet(saveCmd); - if (previousSaveCmd != null) { - previousSaveCmd.cancel(false); - } + final Optional settingsPath = env.getSettingsPath().findFirst(); // alway save to preferred (first) path + settingsPath.ifPresent(path -> { + Runnable saveCommand = () -> this.save(settings, path); + ScheduledFuture scheduledTask = saveScheduler.schedule(saveCommand, SAVE_DELAY_MS, TimeUnit.MILLISECONDS); + ScheduledFuture previouslyScheduledTask = scheduledSaveCmd.getAndSet(scheduledTask); + if (previouslyScheduledTask != null) { + previouslyScheduledTask.cancel(false); + } + }); } - private void save(Settings settings) { + private void save(Settings settings, Path settingsPath) { assert settings != null : "method should only be invoked by #scheduleSave, which checks for null"; - final Path settingsPath = getSettingsPath(); + 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)) { gson.toJson(settings, writer); - LOG.info("Settings saved to " + settingsPath); + LOG.info("Settings saved to {}", settingsPath); } } catch (IOException e) { LOG.error("Failed to save settings.", e); diff --git a/main/commons/src/test/java/org/cryptomator/common/EnvironmentTest.java b/main/commons/src/test/java/org/cryptomator/common/EnvironmentTest.java new file mode 100644 index 000000000..cd8514397 --- /dev/null +++ b/main/commons/src/test/java/org/cryptomator/common/EnvironmentTest.java @@ -0,0 +1,133 @@ +package org.cryptomator.common; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@DisplayName("Environment Variables Test") +class EnvironmentTest { + + private Environment env; + + @BeforeAll + static void init() { + System.setProperty("user.home", "/home/testuser"); + } + + @BeforeEach + void initEach() { + env = new Environment(); + } + + @Test + @DisplayName("cryptomator.settingsPath=~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json") + public void testSettingsPath() { + System.setProperty("cryptomator.settingsPath", "~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json"); + + List result = env.getSettingsPath().collect(Collectors.toList()); + MatcherAssert.assertThat(result, Matchers.hasSize(2)); + MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"), + Paths.get("/home/testuser/.Cryptomator/settings.json"))); + } + + @Test + @DisplayName("cryptomator.ipcPortPath=~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin") + public void testIpcPortPath() { + System.setProperty("cryptomator.ipcPortPath", "~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin"); + + List result = env.getIpcPortPath().collect(Collectors.toList()); + MatcherAssert.assertThat(result, Matchers.hasSize(2)); + MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"), + Paths.get("/home/testuser/.Cryptomator/ipcPort.bin"))); + } + + @Test + @DisplayName("cryptomator.keychainPath=~/AppData/Roaming/Cryptomator/keychain.json") + public void testKeychainPath() { + System.setProperty("cryptomator.keychainPath", "~/AppData/Roaming/Cryptomator/keychain.json"); + + List result = env.getKeychainPath().collect(Collectors.toList()); + MatcherAssert.assertThat(result, Matchers.hasSize(1)); + MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/AppData/Roaming/Cryptomator/keychain.json"))); + } + + @Test + @DisplayName("cryptomator.logDir=/foo/bar") + public void testAbsoluteLogDir() { + System.setProperty("cryptomator.logDir", "/foo/bar"); + + Optional logDir = env.getLogDir(); + + Assertions.assertTrue(logDir.isPresent()); + } + + @Test + @DisplayName("cryptomator.logDir=~/foo/bar") + public void testRelativeLogDir() { + System.setProperty("cryptomator.logDir", "~/foo/bar"); + + Optional logDir = env.getLogDir(); + + Assertions.assertTrue(logDir.isPresent()); + Assertions.assertEquals(Paths.get("/home/testuser/foo/bar"), logDir.get()); + } + + @Nested + @DisplayName("Path Lists") + class SettingsPath { + + @Test + @DisplayName("test.path.property=") + public void testEmptyList() { + System.setProperty("test.path.property", ""); + List result = env.getPaths("test.path.property").collect(Collectors.toList()); + + MatcherAssert.assertThat(result, Matchers.hasSize(0)); + } + + @Test + @DisplayName("test.path.property=/foo/bar/test") + public void testSingleAbsolutePath() { + System.setProperty("test.path.property", "/foo/bar/test"); + List result = env.getPaths("test.path.property").collect(Collectors.toList()); + + MatcherAssert.assertThat(result, Matchers.hasSize(1)); + MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/foo/bar/test"))); + } + + @Test + @DisplayName("test.path.property=~/test") + public void testSingleHomeRelativePath() { + System.setProperty("test.path.property", "~/test"); + List result = env.getPaths("test.path.property").collect(Collectors.toList()); + + MatcherAssert.assertThat(result, Matchers.hasSize(1)); + MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/home/testuser/test"))); + } + + @Test + @DisplayName("test.path.property=~/test:~/test2:/foo/bar/test") + public void testMultiplePaths() { + System.setProperty("test.path.property", "~/test:~/test2:/foo/bar/test"); + List result = env.getPaths("test.path.property").collect(Collectors.toList()); + + MatcherAssert.assertThat(result, Matchers.hasSize(3)); + MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"), + Paths.get("/home/testuser/test2"), + Paths.get("/foo/bar/test"))); + } + + } + +} diff --git a/main/commons/src/test/java/org/cryptomator/common/SemVerComparatorTest.java b/main/commons/src/test/java/org/cryptomator/common/SemVerComparatorTest.java index cf22dcecc..52a9366c9 100644 --- a/main/commons/src/test/java/org/cryptomator/common/SemVerComparatorTest.java +++ b/main/commons/src/test/java/org/cryptomator/common/SemVerComparatorTest.java @@ -8,10 +8,10 @@ *******************************************************************************/ package org.cryptomator.common; -import java.util.Comparator; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; -import org.junit.Assert; -import org.junit.Test; +import java.util.Comparator; public class SemVerComparatorTest { @@ -21,38 +21,38 @@ public class SemVerComparatorTest { @Test public void compareEqualVersions() { - Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4"))); - Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha"))); - Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4+20170101", "1.23.4+20171231"))); - Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha+20170101", "1.23.4-alpha+20171231"))); + Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4"))); + Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha"))); + Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4+20170101", "1.23.4+20171231"))); + Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha+20170101", "1.23.4-alpha+20171231"))); } // newer versions in first argument @Test public void compareHigherToLowerVersions() { - Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.5", "1.23.4"))); - Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.24.4", "1.23.4"))); - Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23"))); - Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-SNAPSHOT"))); - Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-56.78"))); - Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-beta", "1.23.4-alpha"))); - Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-alpha.1", "1.23.4-alpha"))); - Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-56.79", "1.23.4-56.78"))); + Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.5", "1.23.4"))); + Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.24.4", "1.23.4"))); + Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23"))); + Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-SNAPSHOT"))); + Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-56.78"))); + Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-beta", "1.23.4-alpha"))); + Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-alpha.1", "1.23.4-alpha"))); + Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-56.79", "1.23.4-56.78"))); } // newer versions in second argument @Test public void compareLowerToHigherVersions() { - Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.5"))); - Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.24.4"))); - Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23", "1.23.4"))); - Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-SNAPSHOT", "1.23.4"))); - Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4"))); - Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-beta"))); - Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha.1"))); - Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4-56.79"))); + Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.5"))); + Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.24.4"))); + Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23", "1.23.4"))); + Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-SNAPSHOT", "1.23.4"))); + Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4"))); + Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-beta"))); + Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha.1"))); + Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4-56.79"))); } } 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 cee09295a..2d0e31d2a 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 @@ -5,10 +5,10 @@ *******************************************************************************/ package org.cryptomator.common.settings; -import java.io.IOException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; -import org.junit.Assert; -import org.junit.Test; +import java.io.IOException; public class SettingsJsonAdapterTest { @@ -26,12 +26,12 @@ public class SettingsJsonAdapterTest { Settings settings = adapter.fromJson(json); - Assert.assertTrue(settings.checkForUpdates().get()); - Assert.assertEquals(2, settings.getDirectories().size()); - Assert.assertEquals(8080, settings.port().get()); - Assert.assertEquals(42, settings.numTrayNotifications().get()); - Assert.assertEquals("dav", settings.preferredGvfsScheme().get()); - Assert.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get()); + Assertions.assertTrue(settings.checkForUpdates().get()); + Assertions.assertEquals(2, settings.getDirectories().size()); + Assertions.assertEquals(8080, settings.port().get()); + Assertions.assertEquals(42, settings.numTrayNotifications().get()); + Assertions.assertEquals("dav", settings.preferredGvfsScheme().get()); + Assertions.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get()); } } diff --git a/main/commons/src/test/java/org/cryptomator/common/settings/SettingsTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/SettingsTest.java index 320d0504c..6aadb27b3 100644 --- a/main/commons/src/test/java/org/cryptomator/common/settings/SettingsTest.java +++ b/main/commons/src/test/java/org/cryptomator/common/settings/SettingsTest.java @@ -5,12 +5,12 @@ *******************************************************************************/ package org.cryptomator.common.settings; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + import java.io.IOException; import java.util.function.Consumer; -import org.junit.Test; -import org.mockito.Mockito; - public class SettingsTest { @Test 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 e3f31251e..32f393842 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 @@ -5,15 +5,14 @@ *******************************************************************************/ package org.cryptomator.common.settings; +import com.google.gson.stream.JsonReader; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + import java.io.IOException; import java.io.StringReader; import java.nio.file.Paths; -import org.junit.Assert; -import org.junit.Test; - -import com.google.gson.stream.JsonReader; - public class VaultSettingsJsonAdapterTest { private final VaultSettingsJsonAdapter adapter = new VaultSettingsJsonAdapter(); @@ -24,11 +23,11 @@ public class VaultSettingsJsonAdapterTest { JsonReader jsonReader = new JsonReader(new StringReader(json)); VaultSettings vaultSettings = adapter.read(jsonReader); - Assert.assertEquals("foo", vaultSettings.getId()); - 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()); + Assertions.assertEquals("foo", vaultSettings.getId()); + Assertions.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get()); + Assertions.assertEquals("test", vaultSettings.mountName().get()); + Assertions.assertEquals("X", vaultSettings.winDriveLetter().get()); + Assertions.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get()); } } diff --git a/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsTest.java index 9aeac9b7a..042ff9896 100644 --- a/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsTest.java +++ b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsTest.java @@ -8,9 +8,9 @@ *******************************************************************************/ package org.cryptomator.common.settings; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; public class VaultSettingsTest { diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml index add7cb113..97e710b80 100644 --- a/main/keychain/pom.xml +++ b/main/keychain/pom.xml @@ -4,12 +4,17 @@ org.cryptomator main - 1.4.5 + 1.4.6 keychain System Keychain Access + + org.cryptomator + commons + + org.apache.commons commons-lang3 diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java index 57d9cdfa7..ce8c5744d 100644 --- a/main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java +++ b/main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java @@ -5,30 +5,6 @@ *******************************************************************************/ package org.cryptomator.keychain; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.UncheckedIOException; -import java.io.Writer; -import java.lang.reflect.Type; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; - -import javax.inject.Inject; - import com.google.common.io.BaseEncoding; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -42,11 +18,36 @@ import com.google.gson.JsonSerializer; import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.Environment; import org.cryptomator.jni.WinDataProtection; import org.cryptomator.jni.WinFunctions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.inject.Inject; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.lang.reflect.Type; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + import static java.nio.charset.StandardCharsets.UTF_8; class WindowsProtectedKeychainAccess implements KeychainAccessStrategy { @@ -57,24 +58,13 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy { .disableHtmlEscaping().create(); private final Optional winFunctions; - private final Path keychainPath; + private final List keychainPaths; private Map keychainEntries; @Inject - public WindowsProtectedKeychainAccess(Optional winFunctions) { + public WindowsProtectedKeychainAccess(Optional winFunctions, Environment environment) { this.winFunctions = winFunctions; - String keychainPathProperty = System.getProperty("cryptomator.keychainPath"); - if (keychainPathProperty == null) { - LOG.warn("Windows DataProtection module loaded, but no cryptomator.keychainPath property found."); - } - if (keychainPathProperty != null) { - if (keychainPathProperty.startsWith("~/")) { - keychainPathProperty = SystemUtils.USER_HOME + keychainPathProperty.substring(1); - } - this.keychainPath = FileSystems.getDefault().getPath(keychainPathProperty); - } else { - this.keychainPath = null; - } + this.keychainPaths = environment.getKeychainPath().collect(Collectors.toList()); } private WinDataProtection dataProtection() { @@ -124,7 +114,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy { @Override public boolean isSupported() { - return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && keychainPath != null; + return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && !keychainPaths.isEmpty(); } private byte[] generateSalt() { @@ -138,30 +128,44 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy { private void loadKeychainEntriesIfNeeded() { if (keychainEntries == null) { - loadKeychainEntries(); - } - assert keychainEntries != null; - } - - private void loadKeychainEntries() { - Type type = new TypeToken>() { - }.getType(); - try (InputStream in = Files.newInputStream(keychainPath, StandardOpenOption.READ); // - Reader reader = new InputStreamReader(in, UTF_8)) { - keychainEntries = GSON.fromJson(reader, type); - } catch (JsonParseException | NoSuchFileException e) { - LOG.info("Creating new keychain at path {}", keychainPath); - } catch (IOException e) { - throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e); + for (Path keychainPath : keychainPaths) { + Optional> keychain = loadKeychainEntries(keychainPath); + if (keychain.isPresent()) { + keychainEntries = keychain.get(); + break; + } + } } if (keychainEntries == null) { + LOG.info("Unable to load existing keychain file, creating new keychain."); keychainEntries = new HashMap<>(); } } + private Optional> loadKeychainEntries(Path keychainPath) { + LOG.debug("Attempting to load keychain from {}", keychainPath); + Type type = new TypeToken>() { + }.getType(); + try (InputStream in = Files.newInputStream(keychainPath, StandardOpenOption.READ); // + Reader reader = new InputStreamReader(in, UTF_8)) { + return Optional.of(GSON.fromJson(reader, type)); + } catch (NoSuchFileException | JsonParseException e) { + return Optional.empty(); + } catch (IOException e) { + throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e); + } + } + private void saveKeychainEntries() { + if (keychainPaths.isEmpty()) { + throw new IllegalStateException("Can't save keychain if no keychain path is specified."); + } + saveKeychainEntries(keychainPaths.get(0)); + } + + private void saveKeychainEntries(Path keychainPath) { try (OutputStream out = Files.newOutputStream(keychainPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); // - Writer writer = new OutputStreamWriter(out, UTF_8)) { + Writer writer = new OutputStreamWriter(out, UTF_8)) { GSON.toJson(keychainEntries, writer); } catch (IOException e) { throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e); @@ -169,6 +173,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy { } private static class KeychainEntry { + @SerializedName("ciphertext") byte[] ciphertext; @SerializedName("salt") diff --git a/main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java b/main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java index 1bd556061..c177060a3 100644 --- a/main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java +++ b/main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java @@ -5,20 +5,20 @@ *******************************************************************************/ package org.cryptomator.keychain; -import java.util.Optional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; -import org.junit.Assert; -import org.junit.Test; +import java.util.Optional; public class KeychainModuleTest { @Test public void testGetKeychain() { Optional keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess(); - Assert.assertTrue(keychainAccess.isPresent()); - Assert.assertTrue(keychainAccess.get() instanceof MapKeychainAccess); + Assertions.assertTrue(keychainAccess.isPresent()); + Assertions.assertTrue(keychainAccess.get() instanceof MapKeychainAccess); keychainAccess.get().storePassphrase("test", "asd"); - Assert.assertArrayEquals("asd".toCharArray(), keychainAccess.get().loadPassphrase("test")); + Assertions.assertArrayEquals("asd".toCharArray(), keychainAccess.get().loadPassphrase("test")); } } diff --git a/main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainComponent.java b/main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainComponent.java index 5d954bad0..82c4b82ae 100644 --- a/main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainComponent.java +++ b/main/keychain/src/test/java/org/cryptomator/keychain/TestKeychainComponent.java @@ -5,11 +5,10 @@ *******************************************************************************/ package org.cryptomator.keychain; -import java.util.Optional; +import dagger.Component; import javax.inject.Singleton; - -import dagger.Component; +import java.util.Optional; @Singleton @Component(modules = KeychainModule.class) diff --git a/main/keychain/src/test/java/org/cryptomator/keychain/WindowsProtectedKeychainAccessTest.java b/main/keychain/src/test/java/org/cryptomator/keychain/WindowsProtectedKeychainAccessTest.java index c058baadc..796634f5e 100644 --- a/main/keychain/src/test/java/org/cryptomator/keychain/WindowsProtectedKeychainAccessTest.java +++ b/main/keychain/src/test/java/org/cryptomator/keychain/WindowsProtectedKeychainAccessTest.java @@ -5,61 +5,54 @@ *******************************************************************************/ package org.cryptomator.keychain; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; - +import org.cryptomator.common.Environment; import org.cryptomator.jni.WinDataProtection; import org.cryptomator.jni.WinFunctions; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.mockito.Mockito; import org.mockito.stubbing.Answer; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.stream.Stream; + public class WindowsProtectedKeychainAccessTest { - @Rule - public final ExpectedException thrown = ExpectedException.none(); - - private Path tmpFile; + private Path keychainPath; private WindowsProtectedKeychainAccess keychain; - @Before - public void setup() throws IOException, ReflectiveOperationException { - tmpFile = Files.createTempFile("unit-tests", ".tmp"); - System.setProperty("cryptomator.keychainPath", tmpFile.toAbsolutePath().normalize().toString()); + @BeforeEach + public void setup(@TempDir Path tempDir) throws IOException { + keychainPath = tempDir.resolve("keychainfile.tmp"); + Environment env = Mockito.mock(Environment.class); + Mockito.when(env.getKeychainPath()).thenReturn(Stream.of(keychainPath)); WinFunctions winFunctions = Mockito.mock(WinFunctions.class); WinDataProtection winDataProtection = Mockito.mock(WinDataProtection.class); Mockito.when(winFunctions.dataProtection()).thenReturn(winDataProtection); Answer answerReturningFirstArg = invocation -> ((byte[]) invocation.getArgument(0)).clone(); Mockito.when(winDataProtection.protect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg); Mockito.when(winDataProtection.unprotect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg); - keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions)); - } - - @After - public void teardown() throws IOException { - Files.deleteIfExists(tmpFile); + keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions), env); } @Test - public void testStoreAndLoad() { + public void testStoreAndLoad() throws IOException { String storedPw1 = "topSecret"; String storedPw2 = "bottomSecret"; keychain.storePassphrase("myPassword", storedPw1); keychain.storePassphrase("myOtherPassword", storedPw2); String loadedPw1 = new String(keychain.loadPassphrase("myPassword")); String loadedPw2 = new String(keychain.loadPassphrase("myOtherPassword")); - Assert.assertEquals(storedPw1, loadedPw1); - Assert.assertEquals(storedPw2, loadedPw2); + Assertions.assertEquals(storedPw1, loadedPw1); + Assertions.assertEquals(storedPw2, loadedPw2); keychain.deletePassphrase("myPassword"); - Assert.assertNull(keychain.loadPassphrase("myPassword")); - Assert.assertNull(keychain.loadPassphrase("nonExistingPassword")); + Assertions.assertNull(keychain.loadPassphrase("myPassword")); + Assertions.assertNotNull(keychain.loadPassphrase("myOtherPassword")); + Assertions.assertNull(keychain.loadPassphrase("nonExistingPassword")); } } diff --git a/main/launcher/pom.xml b/main/launcher/pom.xml index ee9308e00..aa501fa19 100644 --- a/main/launcher/pom.xml +++ b/main/launcher/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.4.5 + 1.4.6 launcher Cryptomator Launcher diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/ApplicationVersion.java b/main/launcher/src/main/java/org/cryptomator/launcher/ApplicationVersion.java deleted file mode 100644 index 6a60c5e86..000000000 --- a/main/launcher/src/main/java/org/cryptomator/launcher/ApplicationVersion.java +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the accompanying LICENSE file. - *******************************************************************************/ -package org.cryptomator.launcher; - -import java.util.Optional; - -public class ApplicationVersion { - - public static String orElse(String other) { - return get().orElse(other); - } - - public static Optional get() { - return Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion()); - } - -} diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java b/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java index 8a97e336d..5309a80bc 100644 --- a/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java +++ b/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java @@ -5,57 +5,127 @@ *******************************************************************************/ package org.cryptomator.launcher; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; - +import javafx.application.Application; +import javafx.application.Platform; +import javafx.stage.Stage; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.logging.DebugMode; +import org.cryptomator.logging.LoggerConfiguration; +import org.cryptomator.ui.controllers.MainController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javafx.application.Application; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.CountDownLatch; +@Singleton public class Cryptomator { + // DaggerCryptomatorComponent gets generated by Dagger. + // Run Maven and include target/generated-sources/annotations in your IDE. + private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.create(); private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class); - static final BlockingQueue FILE_OPEN_REQUESTS = new ArrayBlockingQueue<>(10); - public static void main(String[] args) { - LOG.info("Starting Cryptomator {} on {} {} ({})", ApplicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); + private final LoggerConfiguration logConfig; + private final DebugMode debugMode; + private final IpcFactory ipcFactory; + private final Optional applicationVersion; - FileOpenRequestHandler fileOpenRequestHandler = new FileOpenRequestHandler(FILE_OPEN_REQUESTS); - try (InterProcessCommunicator communicator = InterProcessCommunicator.start(new IpcProtocolImpl(fileOpenRequestHandler))) { - if (communicator.isServer()) { - fileOpenRequestHandler.handleLaunchArgs(args); - CleanShutdownPerformer.registerShutdownHook(); - Application.launch(MainApplication.class, args); - } else { - communicator.handleLaunchArgs(args); - LOG.info("Found running application instance. Shutting down."); - } - } catch (IOException e) { - LOG.error("Failed to initiate inter-process communication.", e); - } catch (Throwable e) { - LOG.error("Error during startup", e); - } - System.exit(0); // end remaining non-daemon threads. + @Inject + Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional applicationVersion) { + this.logConfig = logConfig; + this.debugMode = debugMode; + this.ipcFactory = ipcFactory; + this.applicationVersion = applicationVersion; } - private static class IpcProtocolImpl implements InterProcessCommunicationProtocol { + public static void main(String[] args) { + int exitCode = CRYPTOMATOR_COMPONENT.application().run(args); + System.exit(exitCode); // end remaining non-daemon threads. + } - private final FileOpenRequestHandler fileOpenRequestHandler; + /** + * Main entry point of the application launcher. + * @param args The arguments passed to this program via {@link #main(String[])}. + * @return Nonzero exit code in case of an error. + */ + private int run(String[] args) { + logConfig.init(); + LOG.info("Starting Cryptomator {} on {} {} ({})", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); - // TODO: inject? - public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler) { - this.fileOpenRequestHandler = fileOpenRequestHandler; + if (sendArgsToRunningInstance(args)) { + LOG.info("Found running application instance. Shutting down..."); + return 0; + } + + try { + runGuiApplication(); + LOG.info("Shutting down..."); + return 0; + } catch (Throwable e) { + LOG.error("Terminating due to error", e); + return 1; + } + } + + /** + * Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args. + * If no external process could be reached, the args will be handled by the loopback IPC endpoint. + * + * @param args Arguments to send to the instance (if possible) + * @return true if a different process could be reached, false otherwise. + */ + private boolean sendArgsToRunningInstance(String[] args) { + try (IpcFactory.IpcEndpoint endpoint = ipcFactory.create()) { + endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self. + return endpoint.isConnectedToRemote(); + } catch (IOException e) { + LOG.error("Failed to initiate inter-process communication.", e); + return false; + } + } + + /** + * Launches the JavaFX application and waits until shutdown is requested. + */ + private void runGuiApplication() { + debugMode.initialize(); + CleanShutdownPerformer.registerShutdownHook(); + Application.launch(MainApp.class); +// Platform.startup(() -> { +// assert Platform.isFxApplicationThread(); +// FxApplication app = CRYPTOMATOR_COMPONENT.fxApplicationComponent().application(); +// app.start(); +// }); + } + + + // We need a separate FX Application class, until we can use the module system. See https://stackoverflow.com/q/54756176/4014509 + public static class MainApp extends Application { + + @Override + public void start(Stage primaryStage) { + LOG.info("JavaFX application started."); + primaryStage.setMinWidth(652.0); + primaryStage.setMinHeight(440.0); + + FxApplicationComponent fxApplicationComponent = CRYPTOMATOR_COMPONENT.fxApplicationComponent() // + .fxApplication(this) // + .mainWindow(primaryStage) // + .build(); + + MainController mainCtrl = fxApplicationComponent.fxmlLoader().load("/fxml/main.fxml"); + mainCtrl.initStage(primaryStage); + primaryStage.show(); } @Override - public void handleLaunchArgs(String[] args) { - LOG.info("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse("")); - fileOpenRequestHandler.handleLaunchArgs(args); + public void stop() { + LOG.info("JavaFX application stopped."); } } diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java b/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java new file mode 100644 index 000000000..e22a85f9e --- /dev/null +++ b/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java @@ -0,0 +1,21 @@ +package org.cryptomator.launcher; + +import dagger.Component; +import org.cryptomator.common.CommonsModule; +import org.cryptomator.common.Environment; +import org.cryptomator.logging.DebugMode; +import org.cryptomator.logging.LoggerModule; + +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.Optional; + +@Singleton +@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class}) +public interface CryptomatorComponent { + + Cryptomator application(); + + FxApplicationComponent.Builder fxApplicationComponent(); + +} diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorModule.java b/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorModule.java new file mode 100644 index 000000000..2fe1f393a --- /dev/null +++ b/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorModule.java @@ -0,0 +1,39 @@ +package org.cryptomator.launcher; + +import dagger.Module; +import dagger.Provides; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.SettingsProvider; +import org.cryptomator.ui.model.AppLaunchEvent; + +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.Optional; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; + +@Module +class CryptomatorModule { + + @Provides + @Singleton + static Settings provideSettings(SettingsProvider settingsProvider) { + return settingsProvider.get(); + } + + @Provides + @Singleton + @Named("launchEventQueue") + static BlockingQueue provideFileOpenRequests() { + return new ArrayBlockingQueue<>(10); + } + + @Provides + @Singleton + @Named("applicationVersion") + static Optional provideApplicationVersion() { + return Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion()); + } + +} diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java b/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java index 8f10c6aa2..c422b8b51 100644 --- a/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java +++ b/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java @@ -7,51 +7,71 @@ package org.cryptomator.launcher; import java.awt.Desktop; +import java.awt.desktop.OpenFilesEvent; +import java.awt.desktop.QuitStrategy; import java.io.File; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.InvalidPathException; import java.nio.file.Path; +import java.util.Arrays; +import java.util.Objects; import java.util.concurrent.BlockingQueue; +import java.util.function.Function; +import java.util.stream.Stream; +import org.cryptomator.ui.model.AppLaunchEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +@Singleton class FileOpenRequestHandler { private static final Logger LOG = LoggerFactory.getLogger(FileOpenRequestHandler.class); - private final BlockingQueue fileOpenRequests; + private final BlockingQueue launchEventQueue; - public FileOpenRequestHandler(BlockingQueue fileOpenRequests) { - this.fileOpenRequests = fileOpenRequests; + @Inject + public FileOpenRequestHandler(@Named("launchEventQueue") BlockingQueue launchEventQueue) { + this.launchEventQueue = launchEventQueue; try { - Desktop.getDesktop().setOpenFileHandler(e -> { - e.getFiles().stream().map(File::toPath).forEach(fileOpenRequests::add); - }); + Desktop.getDesktop().setOpenFileHandler(this::openFiles); } catch (UnsupportedOperationException e) { LOG.info("Unable to setOpenFileHandler, probably not supported on this OS."); } } + private void openFiles(final OpenFilesEvent evt) { + Stream pathsToOpen = evt.getFiles().stream().map(File::toPath); + AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen); + tryToEnqueueFileOpenRequest(launchEvent); + } + public void handleLaunchArgs(String[] args) { handleLaunchArgs(FileSystems.getDefault(), args); } // visible for testing void handleLaunchArgs(FileSystem fs, String[] args) { - for (String arg : args) { + Stream pathsToOpen = Arrays.stream(args).map(str -> { try { - Path path = fs.getPath(arg); - tryToEnqueueFileOpenRequest(path); + return fs.getPath(str); } catch (InvalidPathException e) { - LOG.trace("{} not a valid path", arg); + LOG.trace("Argument not a valid path: {}", str); + return null; } - } + }).filter(Objects::nonNull); + AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen); + tryToEnqueueFileOpenRequest(launchEvent); } - private void tryToEnqueueFileOpenRequest(Path path) { - if (!fileOpenRequests.offer(path)) { - LOG.warn("{} could not be enqueued for opening.", path); + + private void tryToEnqueueFileOpenRequest(AppLaunchEvent launchEvent) { + if (!launchEventQueue.offer(launchEvent)) { + LOG.warn("Could not enqueue application launch event.", launchEvent); } } diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/LauncherComponent.java b/main/launcher/src/main/java/org/cryptomator/launcher/FxApplicationComponent.java similarity index 50% rename from main/launcher/src/main/java/org/cryptomator/launcher/LauncherComponent.java rename to main/launcher/src/main/java/org/cryptomator/launcher/FxApplicationComponent.java index bf70359fb..701e3e546 100644 --- a/main/launcher/src/main/java/org/cryptomator/launcher/LauncherComponent.java +++ b/main/launcher/src/main/java/org/cryptomator/launcher/FxApplicationComponent.java @@ -5,19 +5,33 @@ *******************************************************************************/ package org.cryptomator.launcher; -import javax.inject.Singleton; - +import dagger.BindsInstance; +import dagger.Subcomponent; +import javafx.application.Application; +import javafx.stage.Stage; +import org.cryptomator.common.FxApplicationScoped; import org.cryptomator.logging.DebugMode; import org.cryptomator.ui.controllers.ViewControllerLoader; -import dagger.Component; +import javax.inject.Named; -@Singleton -@Component(modules = LauncherModule.class) -interface LauncherComponent { +@FxApplicationScoped +@Subcomponent(modules = FxApplicationModule.class) +interface FxApplicationComponent { ViewControllerLoader fxmlLoader(); - DebugMode debugMode(); + @Subcomponent.Builder + interface Builder { + + @BindsInstance + Builder fxApplication(Application application); + + @BindsInstance + Builder mainWindow(@Named("mainWindow") Stage mainWindow); + + FxApplicationComponent build(); + + } } diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/FxApplicationModule.java b/main/launcher/src/main/java/org/cryptomator/launcher/FxApplicationModule.java new file mode 100644 index 000000000..1d9988d06 --- /dev/null +++ b/main/launcher/src/main/java/org/cryptomator/launcher/FxApplicationModule.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the accompanying LICENSE file. + *******************************************************************************/ +package org.cryptomator.launcher; + +import dagger.Module; +import dagger.Provides; +import org.cryptomator.common.FxApplicationScoped; +import org.cryptomator.ui.UiModule; + +import javax.inject.Named; +import java.util.function.Consumer; + +@Module(includes = {UiModule.class}) +class FxApplicationModule { + + @Provides + @FxApplicationScoped + @Named("shutdownTaskScheduler") + Consumer provideShutdownTaskScheduler() { + return CleanShutdownPerformer::scheduleShutdownTask; + } + +} diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicator.java b/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicator.java deleted file mode 100644 index e9108cb75..000000000 --- a/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicator.java +++ /dev/null @@ -1,274 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the accompanying LICENSE file. - *******************************************************************************/ -package org.cryptomator.launcher; - -import java.io.Closeable; -import java.io.IOException; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.WritableByteChannel; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.rmi.ConnectException; -import java.rmi.ConnectIOException; -import java.rmi.NotBoundException; -import java.rmi.Remote; -import java.rmi.RemoteException; -import java.rmi.registry.LocateRegistry; -import java.rmi.registry.Registry; -import java.rmi.server.RMIClientSocketFactory; -import java.rmi.server.RMIServerSocketFactory; -import java.rmi.server.RMISocketFactory; -import java.rmi.server.UnicastRemoteObject; - -import org.apache.commons.lang3.SystemUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.io.MoreFiles; - -/** - * First running application on a machine opens a server socket. Further processes will connect as clients. - */ -abstract class InterProcessCommunicator implements InterProcessCommunicationProtocol, Closeable { - - private static final Logger LOG = LoggerFactory.getLogger(InterProcessCommunicator.class); - private static final String RMI_NAME = "Cryptomator"; - - public abstract boolean isServer(); - - /** - * @param endpoint The server-side communication endpoint. - * @return Either a client or a server communicator. - * @throws IOException In case of communication errors. - */ - public static InterProcessCommunicator start(InterProcessCommunicationProtocol endpoint) throws IOException { - return start(getIpcPortPath(), endpoint); - } - - // visible for testing - static InterProcessCommunicator start(Path portFilePath, InterProcessCommunicationProtocol endpoint) throws IOException { - System.setProperty("java.rmi.server.hostname", "localhost"); - try { - // try to connect to existing server: - ClientCommunicator client = new ClientCommunicator(portFilePath); - LOG.trace("Connected to running process."); - return client; - } catch (ConnectException | ConnectIOException | NotBoundException e) { - LOG.debug("Could not connect to running process."); - // continue - } - - // spawn a new server: - LOG.trace("Spawning new server..."); - ServerCommunicator server = new ServerCommunicator(endpoint, portFilePath); - LOG.debug("Server listening on port {}.", server.getPort()); - return server; - } - - private static Path getIpcPortPath() { - final String settingsPathProperty = System.getProperty("cryptomator.ipcPortPath"); - if (settingsPathProperty == null) { - LOG.warn("System property cryptomator.ipcPortPath not set."); - return Paths.get(".ipcPort.tmp"); - } else { - return Paths.get(replaceHomeDir(settingsPathProperty)); - } - } - - private static String replaceHomeDir(String path) { - if (path.startsWith("~/")) { - return SystemUtils.USER_HOME + path.substring(1); - } else { - return path; - } - } - - public static class ClientCommunicator extends InterProcessCommunicator { - - private final IpcProtocolRemote remote; - - private ClientCommunicator(Path portFilePath) throws ConnectException, NotBoundException, RemoteException { - if (Files.notExists(portFilePath)) { - throw new ConnectException("No IPC port file."); - } - try { - int port = ClientCommunicator.readPort(portFilePath); - LOG.debug("Connecting to port {}...", port); - Registry registry = LocateRegistry.getRegistry("localhost", port, new ClientSocketFactory()); - this.remote = (IpcProtocolRemote) registry.lookup(RMI_NAME); - } catch (IOException e) { - throw new ConnectException("Error reading IPC port file."); - } - } - - private static int readPort(Path portFilePath) throws IOException { - ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES); - try (ReadableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.READ)) { - if (ch.read(buf) == Integer.BYTES) { - buf.flip(); - return buf.getInt(); - } else { - throw new IOException("Invalid IPC port file."); - } - } - } - - @Override - public void handleLaunchArgs(String[] args) { - try { - remote.handleLaunchArgs(args); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - - @Override - public boolean isServer() { - return false; - } - - @Override - public void close() { - // no-op - } - - } - - public static class ServerCommunicator extends InterProcessCommunicator { - - private final ServerSocket socket; - private final Registry registry; - private final IpcProtocolRemoteImpl remote; - private final Path portFilePath; - - private ServerCommunicator(InterProcessCommunicationProtocol delegate, Path portFilePath) throws IOException { - this.socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost")); - RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory(); - SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket); - this.registry = LocateRegistry.createRegistry(0, csf, ssf); - this.remote = new IpcProtocolRemoteImpl(delegate); - UnicastRemoteObject.exportObject(remote, 0); - registry.rebind(RMI_NAME, remote); - this.portFilePath = portFilePath; - ServerCommunicator.writePort(portFilePath, socket.getLocalPort()); - } - - private static void writePort(Path portFilePath, int port) throws IOException { - ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES); - buf.putInt(port); - buf.flip(); - MoreFiles.createParentDirectories(portFilePath); - try (WritableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { - if (ch.write(buf) != Integer.BYTES) { - throw new IOException("Did not write expected number of bytes."); - } - } - } - - @Override - public void handleLaunchArgs(String[] args) { - throw new UnsupportedOperationException("Server doesn't invoke methods."); - } - - @Override - public boolean isServer() { - return true; - } - - private int getPort() { - return socket.getLocalPort(); - } - - @Override - public void close() { - try { - registry.unbind(RMI_NAME); - UnicastRemoteObject.unexportObject(remote, true); - socket.close(); - Files.deleteIfExists(portFilePath); - LOG.debug("Server shut down."); - } catch (NotBoundException | IOException e) { - LOG.warn("Failed to close IPC Server.", e); - } - } - - } - - private static interface IpcProtocolRemote extends Remote { - void handleLaunchArgs(String[] args) throws RemoteException; - } - - private static class IpcProtocolRemoteImpl implements IpcProtocolRemote { - - private final InterProcessCommunicationProtocol delegate; - - protected IpcProtocolRemoteImpl(InterProcessCommunicationProtocol delegate) throws RemoteException { - this.delegate = delegate; - } - - @Override - public void handleLaunchArgs(String[] args) { - delegate.handleLaunchArgs(args); - } - - } - - /** - * Always returns the same pre-constructed server socket. - */ - private static class SingletonServerSocketFactory implements RMIServerSocketFactory { - - private final ServerSocket socket; - - public SingletonServerSocketFactory(ServerSocket socket) { - this.socket = socket; - } - - @Override - public synchronized ServerSocket createServerSocket(int port) throws IOException { - if (port != 0) { - throw new IllegalArgumentException("This factory doesn't support specific ports."); - } - return this.socket; - } - - } - - /** - * Creates client sockets with short timeouts. - */ - private static class ClientSocketFactory implements RMIClientSocketFactory { - - @Override - public Socket createSocket(String host, int port) throws IOException { - return new SocketWithFixedTimeout(host, port, 1000); - } - - } - - private static class SocketWithFixedTimeout extends Socket { - - public SocketWithFixedTimeout(String host, int port, int timeoutInMs) throws UnknownHostException, IOException { - super(host, port); - super.setSoTimeout(timeoutInMs); - } - - @Override - public synchronized void setSoTimeout(int timeout) throws SocketException { - // do nothing, timeout is fixed - } - - } - -} diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/IpcFactory.java b/main/launcher/src/main/java/org/cryptomator/launcher/IpcFactory.java new file mode 100644 index 000000000..112050d26 --- /dev/null +++ b/main/launcher/src/main/java/org/cryptomator/launcher/IpcFactory.java @@ -0,0 +1,258 @@ +/******************************************************************************* + * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the accompanying LICENSE file. + *******************************************************************************/ +package org.cryptomator.launcher; + +import com.google.common.io.MoreFiles; +import org.cryptomator.common.Environment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.Closeable; +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.rmi.NotBoundException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; +import java.rmi.server.RMISocketFactory; +import java.rmi.server.UnicastRemoteObject; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * First running application on a machine opens a server socket. Further processes will connect as clients. + */ +@Singleton +class IpcFactory { + + private static final Logger LOG = LoggerFactory.getLogger(IpcFactory.class); + private static final String RMI_NAME = "Cryptomator"; + + private final List portFilePaths; + private final IpcProtocolImpl ipcHandler; + + @Inject + public IpcFactory(Environment env, IpcProtocolImpl ipcHandler) { + this.portFilePaths = env.getIpcPortPath().collect(Collectors.toUnmodifiableList()); + this.ipcHandler = ipcHandler; + } + + public IpcEndpoint create() { + if (portFilePaths.isEmpty()) { + LOG.warn("No IPC port file path specified."); + return new SelfEndpoint(ipcHandler); + } else { + System.setProperty("java.rmi.server.hostname", "localhost"); + return attemptClientConnection().or(this::createServerEndpoint).orElseGet(() -> new SelfEndpoint(ipcHandler)); + } + } + + private Optional attemptClientConnection() { + for (Path portFilePath : portFilePaths) { + try { + int port = readPort(portFilePath); + LOG.debug("[Client] Connecting to port {}...", port); + Registry registry = LocateRegistry.getRegistry("localhost", port, new ClientSocketFactory()); + IpcProtocol remoteInterface = (IpcProtocol) registry.lookup(RMI_NAME); + return Optional.of(new ClientEndpoint(remoteInterface)); + } catch (NotBoundException | IOException e) { + LOG.debug("[Client] Failed to connect."); + // continue with next portFilePath... + } + } + return Optional.empty(); + } + + private int readPort(Path portFilePath) throws IOException { + try (ReadableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.READ)) { + LOG.debug("[Client] Reading IPC port from {}", portFilePath); + ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES); + if (ch.read(buf) == Integer.BYTES) { + buf.flip(); + return buf.getInt(); + } else { + throw new IOException("Invalid IPC port file."); + } + } + } + + private Optional createServerEndpoint() { + assert !portFilePaths.isEmpty(); + Path portFilePath = portFilePaths.get(0); + try { + ServerSocket socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost")); + RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory(); + SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket); + Registry registry = LocateRegistry.createRegistry(0, csf, ssf); + UnicastRemoteObject.exportObject(ipcHandler, 0); + registry.rebind(RMI_NAME, ipcHandler); + writePort(portFilePath, socket.getLocalPort()); + return Optional.of(new ServerEndpoint(ipcHandler, socket, registry, portFilePath)); + } catch (IOException e) { + LOG.warn("[Server] Failed to create IPC server.", e); + return Optional.empty(); + } + } + + private void writePort(Path portFilePath, int port) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES); + buf.putInt(port); + buf.flip(); + MoreFiles.createParentDirectories(portFilePath); + try (WritableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + if (ch.write(buf) != Integer.BYTES) { + throw new IOException("Did not write expected number of bytes."); + } + } + LOG.debug("[Server] Wrote IPC port {} to {}", port, portFilePath); + } + + interface IpcEndpoint extends Closeable { + + boolean isConnectedToRemote(); + + IpcProtocol getRemote(); + + } + + static class SelfEndpoint implements IpcEndpoint { + + protected final IpcProtocol remoteObject; + + SelfEndpoint(IpcProtocol remoteObject) { + this.remoteObject = remoteObject; + } + + @Override + public boolean isConnectedToRemote() { + return false; + } + + @Override + public IpcProtocol getRemote() { + return remoteObject; + } + + @Override + public void close() { + // no-op + } + } + + static class ClientEndpoint implements IpcEndpoint { + + private final IpcProtocol remoteInterface; + + public ClientEndpoint(IpcProtocol remoteInterface) { + this.remoteInterface = remoteInterface; + } + + public IpcProtocol getRemote() { + return remoteInterface; + } + + @Override + public boolean isConnectedToRemote() { + return true; + } + + @Override + public void close() { + // no-op + } + + } + + class ServerEndpoint extends SelfEndpoint { + + private final ServerSocket socket; + private final Registry registry; + private final Path portFilePath; + + private ServerEndpoint(IpcProtocol remoteObject, ServerSocket socket, Registry registry, Path portFilePath) { + super(remoteObject); + this.socket = socket; + this.registry = registry; + this.portFilePath = portFilePath; + } + + @Override + public void close() { + try { + registry.unbind(RMI_NAME); + UnicastRemoteObject.unexportObject(remoteObject, true); + socket.close(); + Files.deleteIfExists(portFilePath); + LOG.debug("[Server] Shut down"); + } catch (NotBoundException | IOException e) { + LOG.warn("[Server] Error shutting down:", e); + } + } + + } + + /** + * Always returns the same pre-constructed server socket. + */ + private static class SingletonServerSocketFactory implements RMIServerSocketFactory { + + private final ServerSocket socket; + + public SingletonServerSocketFactory(ServerSocket socket) { + this.socket = socket; + } + + @Override + public synchronized ServerSocket createServerSocket(int port) throws IOException { + if (port != 0) { + throw new IllegalArgumentException("This factory doesn't support specific ports."); + } + return this.socket; + } + + } + + /** + * Creates client sockets with short timeouts. + */ + private static class ClientSocketFactory implements RMIClientSocketFactory { + + @Override + public Socket createSocket(String host, int port) throws IOException { + return new SocketWithFixedTimeout(host, port, 1000); + } + + } + + private static class SocketWithFixedTimeout extends Socket { + + public SocketWithFixedTimeout(String host, int port, int timeoutInMs) throws UnknownHostException, IOException { + super(host, port); + super.setSoTimeout(timeoutInMs); + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException { + // do nothing, timeout is fixed + } + + } + +} diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicationProtocol.java b/main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocol.java similarity index 71% rename from main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicationProtocol.java rename to main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocol.java index 1b3cc1fc8..40b4ded51 100644 --- a/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicationProtocol.java +++ b/main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocol.java @@ -5,6 +5,11 @@ *******************************************************************************/ package org.cryptomator.launcher; -public interface InterProcessCommunicationProtocol { - void handleLaunchArgs(String[] args); +import java.rmi.Remote; +import java.rmi.RemoteException; + +interface IpcProtocol extends Remote { + + void handleLaunchArgs(String[] args) throws RemoteException; + } \ No newline at end of file diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocolImpl.java b/main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocolImpl.java new file mode 100644 index 000000000..158ec290d --- /dev/null +++ b/main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocolImpl.java @@ -0,0 +1,28 @@ +package org.cryptomator.launcher; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Arrays; + +@Singleton +class IpcProtocolImpl implements IpcProtocol { + + private static final Logger LOG = LoggerFactory.getLogger(IpcProtocolImpl.class); + + private final FileOpenRequestHandler fileOpenRequestHandler; + + @Inject + public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler) { + this.fileOpenRequestHandler = fileOpenRequestHandler; + } + + @Override + public void handleLaunchArgs(String[] args) { + LOG.info("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse("")); + fileOpenRequestHandler.handleLaunchArgs(args); + } + +} diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/LauncherModule.java b/main/launcher/src/main/java/org/cryptomator/launcher/LauncherModule.java deleted file mode 100644 index 2e1cd6c53..000000000 --- a/main/launcher/src/main/java/org/cryptomator/launcher/LauncherModule.java +++ /dev/null @@ -1,68 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the accompanying LICENSE file. - *******************************************************************************/ -package org.cryptomator.launcher; - -import java.nio.file.Path; -import java.util.Optional; -import java.util.concurrent.BlockingQueue; -import java.util.function.Consumer; - -import javax.inject.Named; -import javax.inject.Singleton; - -import org.cryptomator.ui.UiModule; - -import dagger.Module; -import dagger.Provides; -import javafx.application.Application; -import javafx.stage.Stage; - -@Module(includes = {UiModule.class}) -class LauncherModule { - - private final Application application; - private final Stage mainWindow; - - public LauncherModule(Application application, Stage mainWindow) { - this.application = application; - this.mainWindow = mainWindow; - } - - @Provides - @Singleton - Application provideApplication() { - return application; - } - - @Provides - @Singleton - @Named("applicationVersion") - Optional provideApplicationVersion() { - return ApplicationVersion.get(); - } - - @Provides - @Singleton - @Named("mainWindow") - Stage provideMainWindow() { - return mainWindow; - } - - @Provides - @Singleton - @Named("fileOpenRequests") - BlockingQueue provideFileOpenRequests() { - return Cryptomator.FILE_OPEN_REQUESTS; - } - - @Provides - @Singleton - @Named("shutdownTaskScheduler") - Consumer provideShutdownTaskScheduler() { - return CleanShutdownPerformer::scheduleShutdownTask; - } - -} diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/MainApplication.java b/main/launcher/src/main/java/org/cryptomator/launcher/MainApplication.java deleted file mode 100644 index c62f624c4..000000000 --- a/main/launcher/src/main/java/org/cryptomator/launcher/MainApplication.java +++ /dev/null @@ -1,45 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the accompanying LICENSE file. - *******************************************************************************/ -package org.cryptomator.launcher; - -import javafx.application.Application; -import javafx.stage.Stage; -import org.cryptomator.ui.controllers.MainController; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MainApplication extends Application { - - private static final Logger LOG = LoggerFactory.getLogger(MainApplication.class); - private Stage primaryStage; - - @Override - public void start(Stage primaryStage) { - LOG.info("JavaFX application started."); - this.primaryStage = primaryStage; - primaryStage.setMinWidth(652.0); - primaryStage.setMinHeight(440.0); - - LauncherModule launcherModule = new LauncherModule(this, primaryStage); - LauncherComponent launcherComponent = DaggerLauncherComponent.builder() // - .launcherModule(launcherModule) // - .build(); - - launcherComponent.debugMode().initialize(); - - MainController mainCtrl = launcherComponent.fxmlLoader().load("/fxml/main.fxml"); - mainCtrl.initStage(primaryStage); - primaryStage.show(); - } - - @Override - public void stop() { - assert primaryStage != null; - primaryStage.hide(); - LOG.info("JavaFX application stopped."); - } - -} diff --git a/main/launcher/src/main/java/org/cryptomator/logging/DebugMode.java b/main/launcher/src/main/java/org/cryptomator/logging/DebugMode.java index 911e3a05d..de9e3110b 100644 --- a/main/launcher/src/main/java/org/cryptomator/logging/DebugMode.java +++ b/main/launcher/src/main/java/org/cryptomator/logging/DebugMode.java @@ -5,77 +5,55 @@ *******************************************************************************/ package org.cryptomator.logging; -import static java.util.Arrays.asList; - -import java.util.Collection; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.cryptomator.common.settings.Settings; -import org.slf4j.ILoggerFactory; -import org.slf4j.LoggerFactory; - import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; +import javafx.beans.value.ObservableValue; +import org.cryptomator.common.settings.Settings; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.Map; @Singleton public class DebugMode { private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(DebugMode.class); - private static final Collection LOGGER_UPGRADES = asList( // - loggerUpgrade(org.slf4j.Logger.ROOT_LOGGER_NAME, Level.INFO), // - loggerUpgrade("org.cryptomator", Level.TRACE), // - loggerUpgrade("org.eclipse.jetty.server.HttpChannel", Level.DEBUG) // - ); - private final Settings settings; + private final LoggerContext context; @Inject - public DebugMode(Settings settings) { + public DebugMode(Settings settings, LoggerContext context) { this.settings = settings; + this.context = context; } public void initialize() { - if (settings.debugMode().get()) { - enable(); - LOG.debug("Debug mode initialized"); - } + setLogLevels(settings.debugMode().get()); + settings.debugMode().addListener(this::logLevelChanged); } - private void enable() { - ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); - if (loggerFactory instanceof LoggerContext) { - LoggerContext context = (LoggerContext) loggerFactory; - LOGGER_UPGRADES.forEach(loggerUpgrade -> loggerUpgrade.execute(context)); + private void logLevelChanged(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) { + setLogLevels(newValue); + } + + private void setLogLevels(boolean debugMode) { + if (debugMode) { + setLogLevels(LoggerModule.DEBUG_LOG_LEVELS); + LOG.debug("Debug mode enabled"); } else { - LOG.warn("SLF4J not bound to Logback."); + LOG.debug("Debug mode disabled"); + setLogLevels(LoggerModule.DEFAULT_LOG_LEVELS); } } - private static LoggerUpgrade loggerUpgrade(String loggerName, Level minLevel) { - return new LoggerUpgrade(loggerName, minLevel); - } - - private static class LoggerUpgrade { - - private final Level level; - private final String loggerName; - - public LoggerUpgrade(String loggerName, Level minLevel) { - this.loggerName = loggerName; - this.level = minLevel; + private void setLogLevels(Map logLevels) { + for (Map.Entry loglevel : logLevels.entrySet()) { + Logger logger = context.getLogger(loglevel.getKey()); + logger.setLevel(loglevel.getValue()); } - - public void execute(LoggerContext context) { - Logger logger = context.getLogger(loggerName); - if (logger != null && logger.getEffectiveLevel().isGreaterOrEqual(level)) { - logger.setLevel(level); - } - } - } } \ No newline at end of file diff --git a/main/launcher/src/main/java/org/cryptomator/logging/LoggerConfiguration.java b/main/launcher/src/main/java/org/cryptomator/logging/LoggerConfiguration.java new file mode 100644 index 000000000..d1916abee --- /dev/null +++ b/main/launcher/src/main/java/org/cryptomator/logging/LoggerConfiguration.java @@ -0,0 +1,71 @@ +package org.cryptomator.logging; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.hook.DelayingShutdownHook; +import ch.qos.logback.core.util.Duration; +import org.cryptomator.common.Environment; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.Map; + +@Singleton +public class LoggerConfiguration { + + private static final double SHUTDOWN_DELAY_MS = 100; + + private final LoggerContext context; + private final Environment environment; + private final Appender stdout; + private final Appender upgrade; + private final Appender file; + + @Inject + LoggerConfiguration(LoggerContext context, // + Environment environment, // + @Named("stdoutAppender") Appender stdout, // + @Named("upgradeAppender") Appender upgrade, // + @Named("fileAppender") Appender file) { + this.context = context; + this.environment = environment; + this.stdout = stdout; + this.upgrade = upgrade; + this.file = file; + } + + public void init() { + if (environment.useCustomLogbackConfig()) { + Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME); + root.info("Using external logback configuration file."); + } else { + context.reset(); + + // configure loggers: + for (Map.Entry loglevel : LoggerModule.DEFAULT_LOG_LEVELS.entrySet()) { + Logger logger = context.getLogger(loglevel.getKey()); + logger.setLevel(loglevel.getValue()); + logger.setAdditive(false); + logger.addAppender(stdout); + logger.addAppender(file); + } + + // configure upgrade logger: + Logger upgrades = context.getLogger("org.cryptomator.ui.model.upgrade"); + upgrades.setLevel(Level.DEBUG); + upgrades.addAppender(stdout); + upgrades.addAppender(upgrade); + upgrades.setAdditive(false); + + // add shutdown hook + DelayingShutdownHook shutdownHook = new DelayingShutdownHook(); + shutdownHook.setContext(context); + shutdownHook.setDelay(Duration.buildByMilliseconds(SHUTDOWN_DELAY_MS)); + } + } + +} diff --git a/main/launcher/src/main/java/org/cryptomator/logging/LoggerModule.java b/main/launcher/src/main/java/org/cryptomator/logging/LoggerModule.java new file mode 100644 index 000000000..dab3a3476 --- /dev/null +++ b/main/launcher/src/main/java/org/cryptomator/logging/LoggerModule.java @@ -0,0 +1,127 @@ +package org.cryptomator.logging; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.ConsoleAppender; +import ch.qos.logback.core.FileAppender; +import ch.qos.logback.core.helpers.NOPAppender; +import ch.qos.logback.core.hook.DelayingShutdownHook; +import ch.qos.logback.core.rolling.FixedWindowRollingPolicy; +import ch.qos.logback.core.rolling.RollingFileAppender; +import ch.qos.logback.core.util.Duration; +import dagger.Module; +import dagger.Provides; +import org.cryptomator.common.Environment; +import org.slf4j.ILoggerFactory; +import org.slf4j.LoggerFactory; + +import javax.inject.Named; +import javax.inject.Singleton; +import java.nio.file.Path; +import java.util.Map; + +@Module +public class LoggerModule { + + private static final String UPGRADE_FILENAME = "upgrade.log"; + private static final String LOGFILE_NAME = "cryptomator0.log"; + private static final String LOGFILE_ROLLING_PATTERN = "cryptomator%i.log"; + private static final int LOGFILE_ROLLING_MIN = 1; + private static final int LOGFILE_ROLLING_MAX = 9; + private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"; + static final Map DEFAULT_LOG_LEVELS = Map.of( // + Logger.ROOT_LOGGER_NAME, Level.INFO, // + "org.cryptomator", Level.INFO // + ); + static final Map DEBUG_LOG_LEVELS = Map.of( // + Logger.ROOT_LOGGER_NAME, Level.INFO, // + "org.cryptomator", Level.TRACE // + ); + + @Provides + @Singleton + static LoggerContext provideLoggerContext() { + ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); + if (loggerFactory instanceof LoggerContext) { + return (LoggerContext) loggerFactory; + } else { + throw new IllegalStateException("SLF4J not bound to Logback."); + } + } + + @Provides + @Singleton + static PatternLayoutEncoder provideLayoutEncoder(LoggerContext context) { + PatternLayoutEncoder ple = new PatternLayoutEncoder(); + ple.setPattern(LOG_PATTERN); + ple.setContext(context); + ple.start(); + return ple; + } + + @Provides + @Singleton + @Named("stdoutAppender") + static Appender provideStdoutAppender(LoggerContext context, PatternLayoutEncoder encoder) { + ConsoleAppender appender = new ConsoleAppender<>(); + appender.setContext(context); + appender.setEncoder(encoder); + appender.start(); + return appender; + } + + @Provides + @Singleton + @Named("fileAppender") + static Appender provideFileAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) { + if (environment.getLogDir().isPresent()) { + Path logDir = environment.getLogDir().get(); + RollingFileAppender appender = new RollingFileAppender<>(); + appender.setContext(context); + appender.setFile(logDir.resolve(LOGFILE_NAME).toString()); + appender.setEncoder(encoder); + LaunchBasedTriggeringPolicy triggeringPolicy = new LaunchBasedTriggeringPolicy(); + triggeringPolicy.setContext(context); + triggeringPolicy.start(); + appender.setTriggeringPolicy(triggeringPolicy); + FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy(); + rollingPolicy.setContext(context); + rollingPolicy.setFileNamePattern(logDir.resolve(LOGFILE_ROLLING_PATTERN).toString()); + rollingPolicy.setMinIndex(LOGFILE_ROLLING_MIN); + rollingPolicy.setMaxIndex(LOGFILE_ROLLING_MAX); + rollingPolicy.setParent(appender); + rollingPolicy.start(); + appender.setRollingPolicy(rollingPolicy); + appender.start(); + return appender; + } else { + NOPAppender appender = new NOPAppender<>(); + appender.setContext(context); + return appender; + } + } + + @Provides + @Singleton + @Named("upgradeAppender") + static Appender provideUpgradeAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) { + if (environment.getLogDir().isPresent()) { + FileAppender appender = new FileAppender<>(); + appender.setFile(environment.getLogDir().get().resolve(UPGRADE_FILENAME).toString()); + appender.setContext(context); + appender.setEncoder(encoder); + appender.start(); + return appender; + } else { + NOPAppender appender = new NOPAppender<>(); + appender.setContext(context); + return appender; + } + } + + +} diff --git a/main/launcher/src/test/java/org/cryptomator/launcher/FileOpenRequestHandlerTest.java b/main/launcher/src/test/java/org/cryptomator/launcher/FileOpenRequestHandlerTest.java index 54acb065c..673f23c6d 100644 --- a/main/launcher/src/test/java/org/cryptomator/launcher/FileOpenRequestHandlerTest.java +++ b/main/launcher/src/test/java/org/cryptomator/launcher/FileOpenRequestHandlerTest.java @@ -5,72 +5,69 @@ *******************************************************************************/ package org.cryptomator.launcher; +import org.cryptomator.ui.model.AppLaunchEvent; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.InvalidPathException; import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.spi.FileSystemProvider; +import java.nio.file.Paths; +import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; - -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class FileOpenRequestHandlerTest { - @Test - public void testOpenArgsWithCorrectPaths() throws IOException { - Path p1 = Mockito.mock(Path.class); - Path p2 = Mockito.mock(Path.class); - FileSystem fs = Mockito.mock(FileSystem.class); - FileSystemProvider provider = Mockito.mock(FileSystemProvider.class); - BasicFileAttributes attrs = Mockito.mock(BasicFileAttributes.class); - Mockito.when(p1.getFileSystem()).thenReturn(fs); - Mockito.when(p2.getFileSystem()).thenReturn(fs); - Mockito.when(fs.provider()).thenReturn(provider); - Mockito.when(fs.getPath(Mockito.anyString())).thenReturn(p1, p2); - Mockito.when(provider.readAttributes(Mockito.any(), Mockito.eq(BasicFileAttributes.class))).thenReturn(attrs); + private FileOpenRequestHandler inTest; + private BlockingQueue queue; - BlockingQueue queue = new ArrayBlockingQueue<>(10); - FileOpenRequestHandler handler = new FileOpenRequestHandler(queue); - handler.handleLaunchArgs(fs, new String[] {"foo", "bar"}); - - Assert.assertEquals(p1, queue.poll()); - Assert.assertEquals(p2, queue.poll()); + @BeforeEach + public void setup() { + queue = new ArrayBlockingQueue<>(1); + inTest = new FileOpenRequestHandler(queue); } @Test + @DisplayName("./cryptomator.exe foo bar") + public void testOpenArgsWithCorrectPaths() throws IOException { + inTest.handleLaunchArgs(new String[]{"foo", "bar"}); + + AppLaunchEvent evt = queue.poll(); + Assertions.assertNotNull(evt); + List paths = evt.getPathsToOpen().collect(Collectors.toList()); + MatcherAssert.assertThat(paths, CoreMatchers.hasItems(Paths.get("foo"), Paths.get("bar"))); + } + + @Test + @DisplayName("./cryptomator.exe foo (with 'foo' being an invalid path)") public void testOpenArgsWithIncorrectPaths() throws IOException { FileSystem fs = Mockito.mock(FileSystem.class); - Mockito.when(fs.getPath(Mockito.anyString())).thenThrow(new InvalidPathException("foo", "foo is not a path")); + Mockito.when(fs.getPath("foo")).thenThrow(new InvalidPathException("foo", "foo is not a path")); + inTest.handleLaunchArgs(fs, new String[]{"foo"}); - @SuppressWarnings("unchecked") - BlockingQueue queue = Mockito.mock(BlockingQueue.class); - FileOpenRequestHandler handler = new FileOpenRequestHandler(queue); - handler.handleLaunchArgs(fs, new String[] {"foo"}); - - Mockito.verifyNoMoreInteractions(queue); + AppLaunchEvent evt = queue.poll(); + Assertions.assertNotNull(evt); + List paths = evt.getPathsToOpen().collect(Collectors.toList()); + Assertions.assertTrue(paths.isEmpty()); } @Test + @DisplayName("./cryptomator.exe foo (with full event queue)") public void testOpenArgsWithFullQueue() throws IOException { - Path p = Mockito.mock(Path.class); - FileSystem fs = Mockito.mock(FileSystem.class); - FileSystemProvider provider = Mockito.mock(FileSystemProvider.class); - BasicFileAttributes attrs = Mockito.mock(BasicFileAttributes.class); - Mockito.when(p.getFileSystem()).thenReturn(fs); - Mockito.when(fs.provider()).thenReturn(provider); - Mockito.when(fs.getPath(Mockito.anyString())).thenReturn(p); - Mockito.when(provider.readAttributes(Mockito.eq(p), Mockito.eq(BasicFileAttributes.class))).thenReturn(attrs); - Mockito.when(attrs.isRegularFile()).thenReturn(true); + queue.add(new AppLaunchEvent(Stream.empty())); + Assumptions.assumeTrue(queue.remainingCapacity() == 0); - @SuppressWarnings("unchecked") - BlockingQueue queue = Mockito.mock(BlockingQueue.class); - Mockito.when(queue.offer(Mockito.any())).thenReturn(false); - FileOpenRequestHandler handler = new FileOpenRequestHandler(queue); - handler.handleLaunchArgs(fs, new String[] {"foo"}); + inTest.handleLaunchArgs(new String[]{"foo"}); } } diff --git a/main/launcher/src/test/java/org/cryptomator/launcher/InterProcessCommunicatorTest.java b/main/launcher/src/test/java/org/cryptomator/launcher/InterProcessCommunicatorTest.java deleted file mode 100644 index 81c62aca3..000000000 --- a/main/launcher/src/test/java/org/cryptomator/launcher/InterProcessCommunicatorTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the accompanying LICENSE file. - *******************************************************************************/ -package org.cryptomator.launcher; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.SeekableByteChannel; -import java.nio.file.FileSystem; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.spi.FileSystemProvider; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; - -public class InterProcessCommunicatorTest { - - Path portFilePath = Mockito.mock(Path.class); - Path portFileParentPath = Mockito.mock(Path.class); - BasicFileAttributes portFileParentPathAttrs = Mockito.mock(BasicFileAttributes.class); - FileSystem fs = Mockito.mock(FileSystem.class); - FileSystemProvider provider = Mockito.mock(FileSystemProvider.class); - SeekableByteChannel portFileChannel = Mockito.mock(SeekableByteChannel.class); - AtomicInteger port = new AtomicInteger(-1); - - @Before - public void setup() throws IOException { - Mockito.when(portFilePath.getFileSystem()).thenReturn(fs); - Mockito.when(portFilePath.toAbsolutePath()).thenReturn(portFilePath); - Mockito.when(portFilePath.normalize()).thenReturn(portFilePath); - Mockito.when(portFilePath.getParent()).thenReturn(portFileParentPath); - Mockito.when(portFileParentPath.getFileSystem()).thenReturn(fs); - Mockito.when(fs.provider()).thenReturn(provider); - Mockito.when(provider.readAttributes(portFileParentPath, BasicFileAttributes.class)).thenReturn(portFileParentPathAttrs); - Mockito.when(portFileParentPathAttrs.isDirectory()).thenReturn(false, true); // Guava's MoreFiles will check if dir exists before attempting to create them. - Mockito.when(provider.newByteChannel(Mockito.eq(portFilePath), Mockito.any(), Mockito.any())).thenReturn(portFileChannel); - Mockito.when(portFileChannel.read(Mockito.any())).then(invocation -> { - ByteBuffer buf = invocation.getArgument(0); - buf.putInt(port.get()); - return Integer.BYTES; - }); - Mockito.when(portFileChannel.write(Mockito.any())).then(invocation -> { - ByteBuffer buf = invocation.getArgument(0); - port.set(buf.getInt()); - return Integer.BYTES; - }); - } - - @Test(expected = UnsupportedOperationException.class) - public void testStartWithDummyPort1() throws IOException { - port.set(0); - InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class); - try (InterProcessCommunicator result = InterProcessCommunicator.start(portFilePath, protocol)) { - Assert.assertTrue(result.isServer()); - Mockito.verify(provider).createDirectory(portFileParentPath); - Mockito.verifyZeroInteractions(protocol); - result.handleLaunchArgs(new String[] {"foo"}); - } - } - - @Test - public void testStartWithDummyPort2() throws IOException { - Mockito.doThrow(new NoSuchFileException("port file")).when(provider).checkAccess(portFilePath); - - InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class); - try (InterProcessCommunicator result = InterProcessCommunicator.start(portFilePath, protocol)) { - Assert.assertTrue(result.isServer()); - Mockito.verify(provider).createDirectory(portFileParentPath); - Mockito.verifyZeroInteractions(protocol); - } - } - - @Test - public void testInterProcessCommunication() throws IOException, InterruptedException { - port.set(-1); - InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class); - try (InterProcessCommunicator result1 = InterProcessCommunicator.start(portFilePath, protocol)) { - Assert.assertTrue(result1.isServer()); - Mockito.verify(provider, Mockito.times(1)).createDirectory(portFileParentPath); - Mockito.verifyZeroInteractions(protocol); - - try (InterProcessCommunicator result2 = InterProcessCommunicator.start(portFilePath, null)) { - Assert.assertFalse(result2.isServer()); - Mockito.verify(provider, Mockito.times(1)).createDirectory(portFileParentPath); - Assert.assertNotSame(result1, result2); - - result2.handleLaunchArgs(new String[] {"foo"}); - Mockito.verify(protocol).handleLaunchArgs(new String[] {"foo"}); - } - } - } - -} diff --git a/main/launcher/src/test/java/org/cryptomator/launcher/IpcFactoryTest.java b/main/launcher/src/test/java/org/cryptomator/launcher/IpcFactoryTest.java new file mode 100644 index 000000000..e4fef5556 --- /dev/null +++ b/main/launcher/src/test/java/org/cryptomator/launcher/IpcFactoryTest.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the accompanying LICENSE file. + *******************************************************************************/ +package org.cryptomator.launcher; + +import org.cryptomator.common.Environment; +import org.cryptomator.launcher.IpcFactory.IpcEndpoint; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +public class IpcFactoryTest { + + private Environment environment = Mockito.mock(Environment.class); + private IpcProtocolImpl protocolHandler = Mockito.mock(IpcProtocolImpl.class); + + @Test + @DisplayName("Wihout IPC port files") + public void testNoIpcWithoutPortFile() throws IOException { + IpcFactory inTest = new IpcFactory(environment, protocolHandler); + + Mockito.when(environment.getIpcPortPath()).thenReturn(Stream.empty()); + try (IpcEndpoint endpoint1 = inTest.create()) { + Assertions.assertEquals(IpcFactory.SelfEndpoint.class, endpoint1.getClass()); + Assertions.assertFalse(endpoint1.isConnectedToRemote()); + Assertions.assertSame(protocolHandler, endpoint1.getRemote()); + try (IpcEndpoint endpoint2 = inTest.create()) { + Assertions.assertEquals(IpcFactory.SelfEndpoint.class, endpoint2.getClass()); + Assertions.assertNotSame(endpoint1, endpoint2); + Assertions.assertFalse(endpoint2.isConnectedToRemote()); + Assertions.assertSame(protocolHandler, endpoint2.getRemote()); + } + } + } + + @Test + @DisplayName("Start server and client with port shared via file") + public void testInterProcessCommunication(@TempDir Path tmpDir) throws IOException { + Path portFile = tmpDir.resolve("testPortFile"); + Mockito.when(environment.getIpcPortPath()).thenReturn(Stream.of(portFile)); + IpcFactory inTest = new IpcFactory(environment, protocolHandler); + + Assertions.assertFalse(Files.exists(portFile)); + try (IpcEndpoint endpoint1 = inTest.create()) { + Assertions.assertEquals(IpcFactory.ServerEndpoint.class, endpoint1.getClass()); + Assertions.assertFalse(endpoint1.isConnectedToRemote()); + Assertions.assertTrue(Files.exists(portFile)); + Assertions.assertSame(protocolHandler, endpoint1.getRemote()); + Mockito.verifyZeroInteractions(protocolHandler); + try (IpcEndpoint endpoint2 = inTest.create()) { + Assertions.assertEquals(IpcFactory.ClientEndpoint.class, endpoint2.getClass()); + Assertions.assertNotSame(endpoint1, endpoint2); + Assertions.assertTrue(endpoint2.isConnectedToRemote()); + Assertions.assertNotSame(protocolHandler, endpoint2.getRemote()); + Mockito.verifyZeroInteractions(protocolHandler); + endpoint2.getRemote().handleLaunchArgs(new String[] {"foo"}); + Mockito.verify(protocolHandler).handleLaunchArgs(new String[] {"foo"}); + } + } + } + +} diff --git a/main/launcher/src/test/java/org/cryptomator/logging/LaunchBasedTriggeringPolicyTest.java b/main/launcher/src/test/java/org/cryptomator/logging/LaunchBasedTriggeringPolicyTest.java index 2896398ab..16385d949 100644 --- a/main/launcher/src/test/java/org/cryptomator/logging/LaunchBasedTriggeringPolicyTest.java +++ b/main/launcher/src/test/java/org/cryptomator/logging/LaunchBasedTriggeringPolicyTest.java @@ -5,12 +5,12 @@ *******************************************************************************/ package org.cryptomator.logging; -import java.io.File; - -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import java.io.File; + public class LaunchBasedTriggeringPolicyTest { @Test @@ -21,15 +21,15 @@ public class LaunchBasedTriggeringPolicyTest { // 1st invocation boolean triggered = policy.isTriggeringEvent(activeFile, event); - Assert.assertTrue(triggered); + Assertions.assertTrue(triggered); // 2nd invocation triggered = policy.isTriggeringEvent(activeFile, event); - Assert.assertFalse(triggered); + Assertions.assertFalse(triggered); // 3rd invocation triggered = policy.isTriggeringEvent(activeFile, event); - Assert.assertFalse(triggered); + Assertions.assertFalse(triggered); Mockito.verifyZeroInteractions(activeFile); Mockito.verifyZeroInteractions(event); diff --git a/main/launcher/src/test/resources/logback-test.xml b/main/launcher/src/test/resources/logback-test.xml deleted file mode 100644 index 043b6d20a..000000000 --- a/main/launcher/src/test/resources/logback-test.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - diff --git a/main/pom.xml b/main/pom.xml index ca8b1d55f..08ce7861b 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator main - 1.4.5 + 1.4.6 pom Cryptomator @@ -27,9 +27,11 @@ 1.2.1 1.7.0 2.0.0 - 1.1.0 + 1.1.1 1.1.3 - 1.0.7 + 1.0.9 + + 11.0.2 2.6 3.8.1 @@ -43,10 +45,9 @@ 1.7.25 1.2.3 - 4.12 - 4.12.1 - 2.23.0 - 1.3 + 5.4.0 + 2.24.0 + 1.3 @@ -122,6 +123,23 @@ ${cryptomator.jni.version} + + + org.openjfx + javafx-base + ${javafx.version} + + + org.openjfx + javafx-controls + ${javafx.version} + + + org.openjfx + javafx-fxml + ${javafx.version} + + org.slf4j @@ -182,20 +200,10 @@ - junit - junit - ${junit.version} - - - hamcrest-core - org.hamcrest - - - - - de.bechte.junit - junit-hierarchicalcontextrunner - ${junit.hierarchicalrunner.version} + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test org.mockito @@ -207,6 +215,12 @@ hamcrest-all ${hamcrest.version} + + org.openjfx + javafx-swing + ${javafx.version} + test + @@ -217,9 +231,8 @@ slf4j-api - junit - junit - test + org.junit.jupiter + junit-jupiter org.hamcrest @@ -231,11 +244,6 @@ mockito-core test - - de.bechte.junit - junit-hierarchicalcontextrunner - test - @@ -249,8 +257,7 @@ release - uber-jar - ant-kit + buildkit @@ -285,6 +292,18 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + + true + + + + org.jacoco jacoco-maven-plugin @@ -317,7 +336,7 @@ maven-compiler-plugin 3.8.0 - 9 + 11 com.google.dagger @@ -327,6 +346,11 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + diff --git a/main/uber-jar/pom.xml b/main/uber-jar/pom.xml deleted file mode 100644 index 817667972..000000000 --- a/main/uber-jar/pom.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - 4.0.0 - - org.cryptomator - main - 1.4.5 - - uber-jar - Single über jar with all dependencies - - - - org.cryptomator - launcher - - - - - - - maven-shade-plugin - 3.0.0 - - - make-assembly - package - - shade - - - - - Cryptomator-${project.version} - false - - - - org.cryptomator.launcher.Cryptomator - ${project.version} - - - - - - - - \ No newline at end of file diff --git a/main/uber-jar/src/main/resources/logback.xml b/main/uber-jar/src/main/resources/logback.xml deleted file mode 100644 index 721175d1c..000000000 --- a/main/uber-jar/src/main/resources/logback.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - diff --git a/main/ui/pom.xml b/main/ui/pom.xml index e3f763a21..8748d94d9 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.4.5 + 1.4.6 ui Cryptomator GUI @@ -46,6 +46,16 @@ cryptolib + + + org.openjfx + javafx-controls + + + org.openjfx + javafx-fxml + + org.fxmisc.easybind @@ -99,5 +109,10 @@ 1.1 test + + org.openjfx + javafx-swing + test + diff --git a/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java b/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java index eca371bbf..ba51b6622 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java +++ b/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java @@ -9,6 +9,24 @@ *******************************************************************************/ package org.cryptomator.ui; +import javafx.application.Platform; +import javafx.stage.Stage; +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.FxApplicationScoped; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.jni.JniException; +import org.cryptomator.jni.MacApplicationUiState; +import org.cryptomator.jni.MacFunctions; +import org.cryptomator.ui.l10n.Localization; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import javax.swing.SwingUtilities; import java.awt.AWTException; import java.awt.Image; import java.awt.MenuItem; @@ -24,27 +42,7 @@ import java.io.IOException; import java.util.Optional; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; -import javax.swing.SwingUtilities; - -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.common.settings.Settings; -import org.cryptomator.jni.JniException; -import org.cryptomator.jni.MacApplicationUiState; -import org.cryptomator.jni.MacFunctions; -import org.cryptomator.ui.l10n.Localization; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javafx.application.Platform; -import javafx.stage.Stage; - -@Singleton +@FxApplicationScoped public class ExitUtil { private static final Logger LOG = LoggerFactory.getLogger(ExitUtil.class); diff --git a/main/ui/src/main/java/org/cryptomator/ui/UiModule.java b/main/ui/src/main/java/org/cryptomator/ui/UiModule.java index 8b94936b0..f1cbc1c00 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/UiModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/UiModule.java @@ -8,21 +8,11 @@ *******************************************************************************/ package org.cryptomator.ui; -import java.net.InetSocketAddress; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; - -import javax.inject.Named; -import javax.inject.Singleton; - import dagger.Module; import dagger.Provides; import javafx.beans.binding.Binding; import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.common.CommonsModule; +import org.cryptomator.common.FxApplicationScoped; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.SettingsProvider; import org.cryptomator.frontend.webdav.WebDavServer; @@ -31,19 +21,21 @@ import org.cryptomator.ui.controllers.ViewControllerModule; import org.cryptomator.ui.model.VaultComponent; import org.fxmisc.easybind.EasyBind; -@Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class}) +import javax.inject.Named; +import java.net.InetSocketAddress; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +@Module(includes = {ViewControllerModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class}) public class UiModule { private static final int NUM_SCHEDULER_THREADS = 4; @Provides - @Singleton - Settings provideSettings(SettingsProvider settingsProvider) { - return settingsProvider.get(); - } - - @Provides - @Singleton + @FxApplicationScoped ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer shutdownTaskScheduler) { final AtomicInteger threadNumber = new AtomicInteger(1); ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> { @@ -57,7 +49,7 @@ public class UiModule { } @Provides - @Singleton + @FxApplicationScoped ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer shutdownTaskScheduler) { final AtomicInteger threadNumber = new AtomicInteger(1); ExecutorService executorService = Executors.newCachedThreadPool(r -> { @@ -71,7 +63,7 @@ public class UiModule { } @Provides - @Singleton + @FxApplicationScoped Binding provideServerSocketAddressBinding(Settings settings) { return EasyBind.map(settings.port(), (Number port) -> { String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost"; @@ -80,7 +72,7 @@ public class UiModule { } @Provides - @Singleton + @FxApplicationScoped WebDavServer provideWebDavServer(Binding serverSocketAddressBinding) { WebDavServer server = WebDavServer.create(); // no need to unsubscribe eventually, because server is a singleton diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java index b1a823178..f4f498938 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java @@ -9,24 +9,6 @@ ******************************************************************************/ package org.cryptomator.ui.controllers; -import java.awt.Desktop; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.stream.Stream; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - import javafx.application.Application; import javafx.application.Platform; import javafx.beans.binding.Binding; @@ -61,27 +43,47 @@ import javafx.scene.layout.Pane; import javafx.scene.text.Font; import javafx.stage.FileChooser; import javafx.stage.Stage; +import javafx.util.Duration; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.FxApplicationScoped; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.ui.ExitUtil; import org.cryptomator.ui.controls.DirectoryListCell; import org.cryptomator.ui.l10n.Localization; +import org.cryptomator.ui.model.AppLaunchEvent; import org.cryptomator.ui.model.AutoUnlocker; -import org.cryptomator.ui.model.UpgradeStrategies; -import org.cryptomator.ui.model.UpgradeStrategy; +import org.cryptomator.ui.model.upgrade.UpgradeStrategies; +import org.cryptomator.ui.model.upgrade.UpgradeStrategy; import org.cryptomator.ui.model.Vault; import org.cryptomator.ui.model.VaultFactory; import org.cryptomator.ui.model.VaultList; import org.cryptomator.ui.util.DialogBuilderUtil; +import org.cryptomator.ui.util.Tasks; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; import org.fxmisc.easybind.monadic.MonadicBinding; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.inject.Inject; +import javax.inject.Named; +import java.awt.Desktop; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.stream.Stream; + import static org.cryptomator.ui.util.DialogBuilderUtil.buildErrorDialog; -@Singleton +@FxApplicationScoped public class MainController implements ViewController { private static final Logger LOG = LoggerFactory.getLogger(MainController.class); @@ -92,7 +94,7 @@ public class MainController implements ViewController { private final ExitUtil exitUtil; private final Localization localization; private final ExecutorService executorService; - private final BlockingQueue fileOpenRequests; + private final BlockingQueue launchEventQueue; private final VaultFactory vaultFactoy; private final ViewControllerLoader viewControllerLoader; private final ObjectProperty activeController = new SimpleObjectProperty<>(); @@ -109,11 +111,11 @@ public class MainController implements ViewController { private Subscription subs = Subscription.EMPTY; @Inject - public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("fileOpenRequests") BlockingQueue fileOpenRequests, ExitUtil exitUtil, Localization localization, + public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("launchEventQueue") BlockingQueue launchEventQueue, ExitUtil exitUtil, Localization localization, VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker) { this.mainWindow = mainWindow; this.executorService = executorService; - this.fileOpenRequests = fileOpenRequests; + this.launchEventQueue = launchEventQueue; this.exitUtil = exitUtil; this.localization = localization; this.vaultFactoy = vaultFactoy; @@ -211,7 +213,7 @@ public class MainController implements ViewController { stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon_32.png"))); Application.setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString()); } - exitUtil.initExitHandler(this::gracefulShutdown); + exitUtil.initExitHandler(() -> Platform.runLater(this::gracefulShutdown)); listenToFileOpenRequests(stage); } @@ -248,22 +250,13 @@ public class MainController implements ViewController { } private void listenToFileOpenRequests(Stage stage) { - executorService.submit(() -> { - while (!Thread.interrupted()) { - try { - final Path path = fileOpenRequests.take(); - Platform.runLater(() -> { - addVault(path, true); - stage.setIconified(false); - stage.show(); - stage.toFront(); - stage.requestFocus(); - }); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - }); + Tasks.create(launchEventQueue::take).onSuccess(event -> { + stage.setIconified(false); + stage.show(); + stage.toFront(); + stage.requestFocus(); + event.getPathsToOpen().forEach(path -> addVault(path, true)); + }).schedulePeriodically(executorService, Duration.ZERO, Duration.ZERO); } private ListCell createDirecoryListCell(ListView param) { diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/NotFoundController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/NotFoundController.java index ef2e44a07..8d4408111 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/NotFoundController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/NotFoundController.java @@ -5,14 +5,14 @@ *******************************************************************************/ package org.cryptomator.ui.controllers; -import javax.inject.Inject; -import javax.inject.Singleton; - import javafx.fxml.FXML; import javafx.scene.Parent; import javafx.scene.layout.VBox; +import org.cryptomator.common.FxApplicationScoped; -@Singleton +import javax.inject.Inject; + +@FxApplicationScoped public class NotFoundController implements ViewController { @Inject 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 8c1454404..70a792f37 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 @@ -25,6 +25,7 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.VBox; import javafx.util.StringConverter; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.FxApplicationScoped; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.VolumeImpl; import org.cryptomator.ui.l10n.Localization; @@ -32,10 +33,9 @@ import org.cryptomator.ui.model.Volume; import javax.inject.Inject; import javax.inject.Named; -import javax.inject.Singleton; import java.util.Optional; -@Singleton +@FxApplicationScoped public class SettingsController implements ViewController { private static final CharMatcher DIGITS_MATCHER = CharMatcher.inRange('0', '9'); 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 cc6da7214..a9ab0049e 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 @@ -106,7 +106,7 @@ public class UnlockController implements ViewController { private Button unlockButton; @FXML - private Label successMessage; + private Text messageText; @FXML private CheckBox savePassword; @@ -136,7 +136,7 @@ public class UnlockController implements ViewController { private ProgressIndicator progressIndicator; @FXML - private Text messageText; + private Text progressText; @FXML private Hyperlink downloadsPageLink; @@ -200,8 +200,8 @@ public class UnlockController implements ViewController { advancedOptions.setVisible(false); advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show")); progressIndicator.setVisible(false); - successMessage.setVisible(state.successMessage().isPresent()); - state.successMessage().map(localization::getString).ifPresent(successMessage::setText); + progressText.setText(null); + state.successMessage().map(localization::getString).ifPresent(messageText::setText); if (SystemUtils.IS_OS_WINDOWS) { winDriveLetter.valueProperty().removeListener(driveLetterChangeListener); winDriveLetter.getItems().clear(); @@ -212,7 +212,6 @@ public class UnlockController implements ViewController { chooseSelectedDriveLetter(); } downloadsPageLink.setVisible(false); - messageText.setText(null); mountName.setText(vault.getMountName()); savePassword.setSelected(false); // auto-fill pw from keychain: @@ -298,7 +297,7 @@ public class UnlockController implements ViewController { @FXML private void didClickAdvancedOptionsButton(ActionEvent event) { - successMessage.setVisible(false); + messageText.setText(null); advancedOptions.setVisible(!advancedOptions.isVisible()); if (advancedOptions.isVisible()) { advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.hide")); @@ -431,11 +430,12 @@ public class UnlockController implements ViewController { private void didClickUnlockButton(ActionEvent event) { advancedOptions.setDisable(true); advancedOptions.setVisible(false); + advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show")); progressIndicator.setVisible(true); CharSequence password = passwordField.getCharacters(); Tasks.create(() -> { - messageText.setText(localization.getString("unlock.pendingMessage.unlocking")); + progressText.setText(localization.getString("unlock.pendingMessage.unlocking")); vault.unlock(password); if (keychainAccess.isPresent() && savePassword.isSelected()) { keychainAccess.get().storePassphrase(vault.getId(), password); @@ -478,6 +478,7 @@ public class UnlockController implements ViewController { } advancedOptions.setDisable(false); progressIndicator.setVisible(false); + progressText.setText(null); }).runOnce(executor); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java index 1eb0c7307..49e9295bf 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UpgradeController.java @@ -22,9 +22,9 @@ import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; import javafx.scene.layout.GridPane; import org.cryptomator.ui.controls.SecPasswordField; -import org.cryptomator.ui.model.UpgradeStrategies; -import org.cryptomator.ui.model.UpgradeStrategy; -import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException; +import org.cryptomator.ui.model.upgrade.UpgradeStrategies; +import org.cryptomator.ui.model.upgrade.UpgradeStrategy; +import org.cryptomator.ui.model.upgrade.UpgradeStrategy.UpgradeFailedException; import org.cryptomator.ui.model.Vault; import org.cryptomator.ui.util.Tasks; import org.fxmisc.easybind.EasyBind; diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/ViewControllerLoader.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/ViewControllerLoader.java index b345dafdc..55f51db83 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/ViewControllerLoader.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/ViewControllerLoader.java @@ -5,20 +5,18 @@ *******************************************************************************/ package org.cryptomator.ui.controllers; +import javafx.fxml.FXMLLoader; +import org.cryptomator.common.FxApplicationScoped; +import org.cryptomator.ui.l10n.Localization; + +import javax.inject.Inject; +import javax.inject.Provider; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; import java.util.Map; -import javax.inject.Inject; -import javax.inject.Provider; -import javax.inject.Singleton; - -import org.cryptomator.ui.l10n.Localization; - -import javafx.fxml.FXMLLoader; - -@Singleton +@FxApplicationScoped public class ViewControllerLoader { private final Map, Provider> controllerProviders; diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java index 353576862..30d09b972 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java @@ -8,22 +8,6 @@ ******************************************************************************/ package org.cryptomator.ui.controllers; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Comparator; -import java.util.Map; -import java.util.Optional; -import java.util.ResourceBundle; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; @@ -39,15 +23,31 @@ import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; import javafx.scene.layout.VBox; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.FxApplicationScoped; import org.cryptomator.common.settings.Settings; import org.cryptomator.ui.l10n.Localization; import org.cryptomator.ui.util.Tasks; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.inject.Inject; +import javax.inject.Named; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Comparator; +import java.util.Map; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + import static org.cryptomator.ui.util.DialogBuilderUtil.buildYesNoDialog; -@Singleton +@FxApplicationScoped public class WelcomeController implements ViewController { private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class); diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java b/main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java index 8a6d0539e..69124db4e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java @@ -9,12 +9,27 @@ package org.cryptomator.ui.controls; import com.google.common.base.Strings; +import javafx.beans.NamedArg; +import javafx.beans.Observable; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.OverrunStyle; import javafx.scene.control.PasswordField; +import javafx.scene.control.Tooltip; import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; import javafx.scene.input.TransferMode; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import java.awt.Toolkit; import java.nio.CharBuffer; +import java.text.Normalizer; +import java.text.Normalizer.Form; import java.util.Arrays; /** @@ -28,13 +43,44 @@ public class SecPasswordField extends PasswordField { private static final int INITIAL_BUFFER_SIZE = 50; private static final int GROW_BUFFER_SIZE = 50; private static final String PLACEHOLDER = "*"; + private static final double PADDING = 2.0; + private static final double INDICATOR_PADDING = 4.0; + private static final Color INDICATOR_COLOR = new Color(0.901, 0.494, 0.133, 1.0); + + private final Tooltip tooltip = new Tooltip(); + private final Label indicator = new Label(); + private final String nonPrintableCharsWarning; + private final String capslockWarning; private char[] content = new char[INITIAL_BUFFER_SIZE]; private int length = 0; public SecPasswordField() { - this.onDragOverProperty().set(this::handleDragOver); - this.onDragDroppedProperty().set(this::handleDragDropped); + this("", ""); + } + + public SecPasswordField(@NamedArg("nonPrintableCharsWarning") String nonPrintableCharsWarning, @NamedArg("capslockWarning") String capslockWarning) { + this.nonPrintableCharsWarning = nonPrintableCharsWarning; + this.capslockWarning = capslockWarning; + indicator.setPadding(new Insets(PADDING, INDICATOR_PADDING, PADDING, INDICATOR_PADDING)); + indicator.setAlignment(Pos.CENTER_RIGHT); + indicator.setMouseTransparent(true); + indicator.setTextOverrun(OverrunStyle.CLIP); + indicator.setTextFill(INDICATOR_COLOR); + indicator.setFont(Font.font(indicator.getFont().getFamily(), 15.0)); + this.getChildren().add(indicator); + this.setTooltip(tooltip); + this.addEventHandler(DragEvent.DRAG_OVER, this::handleDragOver); + this.addEventHandler(DragEvent.DRAG_DROPPED, this::handleDragDropped); + this.addEventHandler(KeyEvent.ANY, this::handleKeyEvent); + this.focusedProperty().addListener(this::focusedChanged); + } + + @Override + protected void layoutChildren() { + super.layoutChildren(); + indicator.relocate(0.0, 0.0); + indicator.resize(getWidth(), getHeight()); } private void handleDragOver(DragEvent event) { @@ -53,15 +99,93 @@ public class SecPasswordField extends PasswordField { event.consume(); } + private void handleKeyEvent(KeyEvent e) { + if (e.getCode() == KeyCode.CAPS) { + updateVisualHints(true); + } + } + + private void focusedChanged(@SuppressWarnings("unused") Observable observable) { + updateVisualHints(isFocused()); + } + + private void updateVisualHints(boolean focused) { + StringBuilder tooltipSb = new StringBuilder(); + StringBuilder indicatorSb = new StringBuilder(); + if (containsNonPrintableCharacters()) { + indicatorSb.append('⚠'); + tooltipSb.append("- ").append(nonPrintableCharsWarning).append('\n'); + } + // AWT code needed until https://bugs.openjdk.java.net/browse/JDK-8090882 is closed: + if (focused && Toolkit.getDefaultToolkit().getLockingKeyState(java.awt.event.KeyEvent.VK_CAPS_LOCK)) { + indicatorSb.append('⇪'); + tooltipSb.append("- ").append(capslockWarning).append('\n'); + } + indicator.setText(indicatorSb.toString()); + if (!indicator.getText().isEmpty()) { + setPadding(new Insets(PADDING, getIndicatorWidth(), PADDING, PADDING)); + } else { + setPadding(new Insets(PADDING)); + } + tooltip.setText(tooltipSb.toString()); + if (tooltip.getText().isEmpty()) { + setTooltip(null); + } else { + setTooltip(tooltip); + } + } + + private double getIndicatorWidth() { + return new Text(indicator.getText()).getLayoutBounds().getWidth() + INDICATOR_PADDING * 2.0; + } + + /** + * @return true if any {@link Character#isISOControl(char) control character} is present in the current value of this password field. + * @implNote runs in O(n) + */ + boolean containsNonPrintableCharacters() { + for (int i = 0; i < length; i++) { + if (Character.isISOControl(content[i])) { + return true; + } + } + return false; + } + + /** + * Replaces a range of characters with the given text. + * The text will be normalized to NFC. + * + * @param start The starting index in the range, inclusive. This must be >= 0 and < the end. + * @param end The ending index in the range, exclusive. This is one-past the last character to + * delete (consistent with the String manipulation methods). This must be > the start, + * and <= the length of the text. + * @param text The text that is to replace the range. This must not be null. + * @implNote Internally calls {@link PasswordField#replaceText(int, int, String)} with a dummy String for visual purposes. + */ @Override public void replaceText(int start, int end, String text) { + String normalizedText = Normalizer.normalize(text, Form.NFC); int removed = end - start; - int added = text.length(); - this.length += added - removed; - growContentIfNeeded(); - text.getChars(0, text.length(), content, start); + int added = normalizedText.length(); + int delta = added - removed; - String placeholderString = Strings.repeat(PLACEHOLDER, text.length()); + // ensure sufficient content buffer size + int oldLength = length; + this.length += delta; + growContentIfNeeded(); + + // shift existing content + if (delta != 0 && start < oldLength) { + System.arraycopy(content, end, content, end + delta, oldLength - end); + } + + // copy new text to content buffer + normalizedText.getChars(0, normalizedText.length(), content, start); + + // trigger visual hints + updateVisualHints(true); + String placeholderString = Strings.repeat(PLACEHOLDER, normalizedText.length()); super.replaceText(start, end, placeholderString); } @@ -69,7 +193,7 @@ public class SecPasswordField extends PasswordField { if (length > content.length) { char[] newContent = new char[length + GROW_BUFFER_SIZE]; System.arraycopy(content, 0, newContent, 0, content.length); - swipe(); + swipe(content); this.content = newContent; } } @@ -80,6 +204,7 @@ public class SecPasswordField extends PasswordField { * @return A character sequence backed by the SecPasswordField's buffer (not a copy). * @implNote The CharSequence will not copy the backing char[]. * Therefore any mutation to the SecPasswordField's content will mutate or eventually swipe the returned CharSequence. + * @implSpec The CharSequence is usually in NFC representation (unless NFD-encoded char[] is set via {@link #setPassword(char[])}). * @see #swipe() */ @Override @@ -87,6 +212,28 @@ public class SecPasswordField extends PasswordField { return CharBuffer.wrap(content, 0, length); } + /** + * Convenience method wrapper for {@link #setPassword(char[])}. + * + * @param password + * @see #setPassword(char[]) + */ + public void setPassword(CharSequence password) { + char[] buf = new char[password.length()]; + for (int i = 0; i < password.length(); i++) { + buf[i] = password.charAt(i); + } + setPassword(buf); + Arrays.fill(buf, SWIPE_CHAR); + } + + /** + * Directly sets the content of this password field to a copy of the given password. + * No conversion whatsoever happens. If you want to normalize the unicode representation of the password, + * do it before calling this method. + * + * @param password + */ public void setPassword(char[] password) { swipe(); content = Arrays.copyOf(password, password.length); @@ -100,7 +247,12 @@ public class SecPasswordField extends PasswordField { * Destroys the stored password by overriding each character with a different character. */ public void swipe() { - Arrays.fill(content, SWIPE_CHAR); + swipe(content); + length = 0; + } + + private void swipe(char[] buffer) { + Arrays.fill(buffer, SWIPE_CHAR); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/l10n/Localization.java b/main/ui/src/main/java/org/cryptomator/ui/l10n/Localization.java index 299392098..565c3d371 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/l10n/Localization.java +++ b/main/ui/src/main/java/org/cryptomator/ui/l10n/Localization.java @@ -5,6 +5,13 @@ *******************************************************************************/ package org.cryptomator.ui.l10n; +import com.google.common.collect.Sets; +import org.apache.commons.lang3.StringUtils; +import org.cryptomator.common.FxApplicationScoped; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -19,16 +26,7 @@ import java.util.Objects; import java.util.PropertyResourceBundle; import java.util.ResourceBundle; -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.Sets; - -@Singleton +@FxApplicationScoped public class Localization extends ResourceBundle { private static final Logger LOG = LoggerFactory.getLogger(Localization.class); diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/AppLaunchEvent.java b/main/ui/src/main/java/org/cryptomator/ui/model/AppLaunchEvent.java new file mode 100644 index 000000000..e01cf40da --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/AppLaunchEvent.java @@ -0,0 +1,15 @@ +package org.cryptomator.ui.model; + +import java.nio.file.Path; +import java.util.stream.Stream; + +public class AppLaunchEvent { + + private final Stream pathsToOpen; + + public AppLaunchEvent(Stream pathsToOpen) {this.pathsToOpen = pathsToOpen;} + + public Stream getPathsToOpen() { + return pathsToOpen; + } +} 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 78b39fc98..b2909cb84 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 @@ -5,6 +5,13 @@ *******************************************************************************/ package org.cryptomator.ui.model; +import org.cryptomator.common.FxApplicationScoped; +import org.cryptomator.cryptolib.api.CryptoException; +import org.cryptomator.keychain.KeychainAccess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; import java.io.IOException; import java.nio.CharBuffer; import java.util.Arrays; @@ -14,15 +21,7 @@ import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.cryptomator.cryptolib.api.CryptoException; -import org.cryptomator.keychain.KeychainAccess; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Singleton +@FxApplicationScoped public class AutoUnlocker { private static final Logger LOG = LoggerFactory.getLogger(AutoUnlocker.class); 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 b1d07894b..b2eb7cf0f 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,6 +1,7 @@ package org.cryptomator.ui.model; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.Environment; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.cryptofs.CryptoFileSystem; import org.cryptomator.frontend.fuse.mount.CommandFailedException; @@ -25,21 +26,20 @@ import java.util.Optional; public class FuseVolume implements Volume { private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class); - - // TODO: dont use fixed Strings and rather set them in some system environment variables in the cryptomator installer and load those! - 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 static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10; + private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase().contains("mac"); private final VaultSettings vaultSettings; + private final Environment environment; private Mount fuseMnt; private Path mountPoint; private boolean createdTemporaryMountPoint; @Inject - public FuseVolume(VaultSettings vaultSettings) { + public FuseVolume(VaultSettings vaultSettings, Environment environment) { this.vaultSettings = vaultSettings; + this.environment = environment; } @Override @@ -49,11 +49,9 @@ public class FuseVolume implements Volume { Path customMountPoint = Paths.get(optionalCustomMountPoint.get()); checkProvidedMountPoint(customMountPoint); this.mountPoint = customMountPoint; - this.createdTemporaryMountPoint = false; LOG.debug("Successfully checked custom mount point: {}", mountPoint); } else { - this.mountPoint = createTemporaryMountPoint(); - this.createdTemporaryMountPoint = true; + this.mountPoint = prepareTemporaryMountPoint(); LOG.debug("Successfully created mount point: {}", mountPoint); } mount(fs.getPath("/")); @@ -70,20 +68,31 @@ public class FuseVolume implements Volume { } } - private Path createTemporaryMountPoint() throws IOException { - Path parent = Paths.get(SystemUtils.IS_OS_MAC ? DEFAULT_MOUNTROOTPATH_MAC : DEFAULT_MOUNTROOTPATH_LINUX); + private Path prepareTemporaryMountPoint() throws IOException, VolumeException { + Path mountPoint = chooseNonExistingTemporaryMountPoint(); + // https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592: + // In order to allow non-admin users to mount FUSE volumes in `/Volumes`, + // starting with version 3.5.0, FUSE will create non-existent mount points automatically. + if (IS_MAC && mountPoint.getParent().equals(Paths.get("/Volumes"))) { + return mountPoint; + } else { + Files.createDirectories(mountPoint); + this.createdTemporaryMountPoint = true; + return mountPoint; + } + } + + private Path chooseNonExistingTemporaryMountPoint() throws VolumeException { + Path parent = environment.getMountPointsDir().orElseThrow(); String basename = vaultSettings.getId(); for (int i = 0; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) { - try { - Path mountPath = parent.resolve(basename + "_" + i); - Files.createDirectory(mountPath); - return mountPath; - } catch (FileAlreadyExistsException e) { - continue; + Path mountPoint = parent.resolve(basename + "_" + i); + if (Files.notExists(mountPoint)) { + return mountPoint; } } - LOG.error("Failed to create mount path at {}/{}_x. Giving up after {} attempts.", parent, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES); - throw new FileAlreadyExistsException(parent.toString() + "/" + basename); + LOG.error("Failed to find feasible mountpoint at {}/{}_x. Giving up after {} attempts.", parent, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES); + throw new VolumeException("Did not find feasible mount point."); } private void mount(Path root) throws VolumeException { @@ -121,7 +130,7 @@ public class FuseVolume implements Volume { } catch (CommandFailedException e) { throw new VolumeException(e); } - deleteTemporaryMountPoint(); + cleanupTemporaryMountPoint(); } @Override @@ -132,10 +141,10 @@ public class FuseVolume implements Volume { } catch (CommandFailedException e) { throw new VolumeException(e); } - deleteTemporaryMountPoint(); + cleanupTemporaryMountPoint(); } - private void deleteTemporaryMountPoint() { + private void cleanupTemporaryMountPoint() { if (createdTemporaryMountPoint) { try { Files.delete(mountPoint); 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 527aa4f85..6fafed661 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 @@ -5,6 +5,8 @@ *******************************************************************************/ package org.cryptomator.ui.model; +import dagger.BindsInstance; +import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.ui.model.VaultModule.PerVault; import dagger.Subcomponent; @@ -17,7 +19,9 @@ public interface VaultComponent { @Subcomponent.Builder interface Builder { - Builder vaultModule(VaultModule module); + + @BindsInstance + Builder vaultSettings(VaultSettings vaultSettings); VaultComponent build(); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java index 0cf0b8251..6ee402e88 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java @@ -8,15 +8,14 @@ *******************************************************************************/ package org.cryptomator.ui.model; +import org.cryptomator.common.FxApplicationScoped; +import org.cryptomator.common.settings.VaultSettings; + +import javax.inject.Inject; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.cryptomator.common.settings.VaultSettings; - -@Singleton +@FxApplicationScoped public class VaultFactory { private final VaultComponent.Builder vaultComponentBuilder; @@ -32,8 +31,7 @@ public class VaultFactory { } private Vault create(VaultSettings vaultSettings) { - VaultModule module = new VaultModule(vaultSettings); - VaultComponent comp = vaultComponentBuilder.vaultModule(module).build(); + VaultComponent comp = vaultComponentBuilder.vaultSettings(vaultSettings).build(); return comp.vault(); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java index 5ce2685fa..fb9c53afa 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java @@ -5,22 +5,19 @@ *******************************************************************************/ package org.cryptomator.ui.model; -import java.util.List; -import java.util.stream.IntStream; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.cryptomator.common.settings.Settings; -import org.cryptomator.common.settings.VaultSettings; - import com.google.common.collect.Lists; - import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.collections.transformation.TransformationList; +import org.cryptomator.common.FxApplicationScoped; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.VaultSettings; -@Singleton +import javax.inject.Inject; +import java.util.List; +import java.util.stream.IntStream; + +@FxApplicationScoped public class VaultList extends TransformationList { private final VaultFactory vaultFactory; 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 141ba8975..100137f6f 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 @@ -5,37 +5,22 @@ *******************************************************************************/ package org.cryptomator.ui.model; +import dagger.Module; +import dagger.Provides; +import org.cryptomator.common.settings.Settings; +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.util.Objects; - -import javax.inject.Scope; - -import org.cryptomator.common.settings.VolumeImpl; -import org.cryptomator.common.settings.Settings; -import org.cryptomator.common.settings.VaultSettings; - -import dagger.Module; -import dagger.Provides; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @Module public class VaultModule { private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class); - private final VaultSettings vaultSettings; - - public VaultModule(VaultSettings vaultSettings) { - this.vaultSettings = Objects.requireNonNull(vaultSettings); - } - - @Provides - @PerVault - public VaultSettings provideVaultSettings() { - return vaultSettings; - } @Scope @Documented diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/WindowsDriveLetters.java b/main/ui/src/main/java/org/cryptomator/ui/model/WindowsDriveLetters.java index 4c2c3111e..05efef31d 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/WindowsDriveLetters.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/WindowsDriveLetters.java @@ -5,6 +5,11 @@ *******************************************************************************/ package org.cryptomator.ui.model; +import org.apache.commons.lang3.CharUtils; +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.FxApplicationScoped; + +import javax.inject.Inject; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.Set; @@ -13,13 +18,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.StreamSupport; -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.apache.commons.lang3.CharUtils; -import org.apache.commons.lang3.SystemUtils; - -@Singleton +@FxApplicationScoped public final class WindowsDriveLetters { private static final Set D_TO_Z; diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategies.java b/main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeStrategies.java similarity index 87% rename from main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategies.java rename to main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeStrategies.java index 0d569b430..d90a6e5a7 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategies.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeStrategies.java @@ -3,17 +3,18 @@ * All rights reserved. This program and the accompanying materials * are made available under the terms of the accompanying LICENSE file. *******************************************************************************/ -package org.cryptomator.ui.model; +package org.cryptomator.ui.model.upgrade; +import org.cryptomator.common.FxApplicationScoped; +import org.cryptomator.ui.model.Vault; + +import javax.inject.Inject; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Objects; -import javax.inject.Inject; -import javax.inject.Singleton; - -@Singleton +@FxApplicationScoped public class UpgradeStrategies { private final Collection strategies; diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategy.java b/main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeStrategy.java similarity index 98% rename from main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategy.java rename to main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeStrategy.java index 29c51ff2b..14c0a7109 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategy.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeStrategy.java @@ -3,7 +3,7 @@ * All rights reserved. This program and the accompanying materials * are made available under the terms of the accompanying LICENSE file. *******************************************************************************/ -package org.cryptomator.ui.model; +package org.cryptomator.ui.model.upgrade; import java.io.IOException; import java.nio.file.Files; @@ -19,6 +19,7 @@ import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.api.KeyFile; import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; import org.cryptomator.ui.l10n.Localization; +import org.cryptomator.ui.model.Vault; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3DropBundleExtension.java b/main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeVersion3DropBundleExtension.java similarity index 94% rename from main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3DropBundleExtension.java rename to main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeVersion3DropBundleExtension.java index 1fa8ff306..a2456f81f 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3DropBundleExtension.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeVersion3DropBundleExtension.java @@ -3,25 +3,24 @@ * All rights reserved. This program and the accompanying materials * are made available under the terms of the accompanying LICENSE file. *******************************************************************************/ -package org.cryptomator.ui.model; +package org.cryptomator.ui.model.upgrade; +import javafx.application.Platform; +import org.apache.commons.lang3.StringUtils; +import org.cryptomator.common.FxApplicationScoped; +import org.cryptomator.cryptolib.Cryptors; +import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.ui.l10n.Localization; +import org.cryptomator.ui.model.Vault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.apache.commons.lang3.StringUtils; -import org.cryptomator.cryptolib.Cryptors; -import org.cryptomator.cryptolib.api.Cryptor; -import org.cryptomator.ui.l10n.Localization; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javafx.application.Platform; - -@Singleton +@FxApplicationScoped class UpgradeVersion3DropBundleExtension extends UpgradeStrategy { private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3DropBundleExtension.class); diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java b/main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeVersion3to4.java similarity index 97% rename from main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java rename to main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeVersion3to4.java index cb0981d52..db3350fe8 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeVersion3to4.java @@ -3,10 +3,20 @@ * All rights reserved. This program and the accompanying materials * are made available under the terms of the accompanying LICENSE file. *******************************************************************************/ -package org.cryptomator.ui.model; +package org.cryptomator.ui.model.upgrade; + +import com.google.common.io.BaseEncoding; +import org.apache.commons.lang3.StringUtils; +import org.cryptomator.common.FxApplicationScoped; +import org.cryptomator.cryptolib.Cryptors; +import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.common.MessageDigestSupplier; +import org.cryptomator.ui.l10n.Localization; +import org.cryptomator.ui.model.Vault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.inject.Inject; -import javax.inject.Singleton; import java.io.IOException; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitResult; @@ -19,22 +29,13 @@ import java.util.EnumSet; import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.google.common.io.BaseEncoding; -import org.apache.commons.lang3.StringUtils; -import org.cryptomator.cryptolib.Cryptors; -import org.cryptomator.cryptolib.api.Cryptor; -import org.cryptomator.cryptolib.common.MessageDigestSupplier; -import org.cryptomator.ui.l10n.Localization; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import static java.nio.charset.StandardCharsets.UTF_8; /** * Contains the collective knowledge of all creatures who were alive during the development of vault format 3. * This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these. */ -@Singleton +@FxApplicationScoped class UpgradeVersion3to4 extends UpgradeStrategy { private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3to4.class); diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion4to5.java b/main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeVersion4to5.java similarity index 97% rename from main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion4to5.java rename to main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeVersion4to5.java index 89b2847d1..b64fbdb7f 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion4to5.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeVersion4to5.java @@ -3,8 +3,18 @@ * All rights reserved. This program and the accompanying materials * are made available under the terms of the accompanying LICENSE file. *******************************************************************************/ -package org.cryptomator.ui.model; +package org.cryptomator.ui.model.upgrade; +import org.cryptomator.common.FxApplicationScoped; +import org.cryptomator.cryptolib.Cryptors; +import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.api.FileHeader; +import org.cryptomator.ui.l10n.Localization; +import org.cryptomator.ui.model.Vault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; @@ -18,21 +28,11 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.EnumSet; import java.util.regex.Pattern; -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.cryptomator.cryptolib.Cryptors; -import org.cryptomator.cryptolib.api.Cryptor; -import org.cryptomator.cryptolib.api.FileHeader; -import org.cryptomator.ui.l10n.Localization; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Contains the collective knowledge of all creatures who were alive during the development of vault format 3. * This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these. */ -@Singleton +@FxApplicationScoped class UpgradeVersion4to5 extends UpgradeStrategy { private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion4to5.class); diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion5toX.java b/main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeVersion5toX.java similarity index 93% rename from main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion5toX.java rename to main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeVersion5toX.java index a502459b6..4e0bcfe64 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion5toX.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/upgrade/UpgradeVersion5toX.java @@ -3,23 +3,23 @@ * All rights reserved. This program and the accompanying materials * are made available under the terms of the accompanying LICENSE file. *******************************************************************************/ -package org.cryptomator.ui.model; - -import java.io.IOException; - -import javax.inject.Inject; -import javax.inject.Singleton; +package org.cryptomator.ui.model.upgrade; +import org.cryptomator.common.FxApplicationScoped; import org.cryptomator.cryptofs.migration.Migrators; import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException; import org.cryptomator.cryptolib.Cryptors; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.ui.l10n.Localization; +import org.cryptomator.ui.model.Vault; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@Singleton +import javax.inject.Inject; +import java.io.IOException; + +@FxApplicationScoped class UpgradeVersion5toX extends UpgradeStrategy { private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion5toX.class); diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java b/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java index 3f9ccd063..373f698d2 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java @@ -8,24 +8,21 @@ *******************************************************************************/ package org.cryptomator.ui.util; -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import org.cryptomator.ui.l10n.Localization; - import com.google.common.base.Strings; import com.nulabinc.zxcvbn.Zxcvbn; - import javafx.geometry.Insets; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.CornerRadii; import javafx.scene.paint.Color; +import org.cryptomator.common.FxApplicationScoped; +import org.cryptomator.ui.l10n.Localization; -@Singleton +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; + +@FxApplicationScoped public class PasswordStrengthUtil { private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length diff --git a/main/ui/src/main/resources/fxml/change_password.fxml b/main/ui/src/main/resources/fxml/change_password.fxml index 9232e7f10..4e8947346 100644 --- a/main/ui/src/main/resources/fxml/change_password.fxml +++ b/main/ui/src/main/resources/fxml/change_password.fxml @@ -38,15 +38,15 @@