diff --git a/.travis.yml b/.travis.yml index f2722d748..5b65484a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,10 @@ language: java -sudo: required -dist: trusty +sudo: false jdk: -- oraclejdk8 +- oraclejdk9 +branches: + except: + - continuous # To avoid infinite loops, as this tag is created by this Travis config cache: directories: - $HOME/.m2 @@ -11,7 +13,11 @@ env: - secure: "IfYURwZaDWuBDvyn47n0k1Zod/IQw1FF+CS5nnV08Q+NfC3vGGJMwV8m59XnbfwnWGxwvCaAbk4qP6s6+ijgZNKkvgfFMo3rfTok5zt43bIqgaFOANYV+OC/1c59gYD6ZUxhW5iNgMgU3qdsRtJuwSmfkVv/jKyLGfAbS4kN8BA=" # COVERITY_SCAN_TOKEN - secure: "lV9OwUbHMrMpLUH1CY+Z4puLDdFXytudyPlG1eGRsesdpuG6KM3uQVz6uAtf6lrU8DRbMM/T7ML+PmvQ4UoPPYLdLxESLLBat2qUPOIVBOhTSlCc7I0DmGy04CSvkeMy8dPaQC0ukgNiR7zwoNzfcpGRN/U9S8tziDruuHoZSrg=" # BINTRAY_API_KEY - secure: "oWFgRTVP6lyTa7qVxlvkpm20MtVc3BtmsNXQJS6bfg2A0o/iCQMNx7OD59BaafCLGRKvCcJVESiC8FlSylVMS7CDSyYu0gg70NUiIuHp4NBM5inFWYCy/PdQsCTzr5uvNG+rMFQpMFRaCV0FrfM3tLondcVkhsHL68l93Xoexx4=" # CODACY_PROJECT_TOKEN + - secure: "zJxgytA2Ks5Xzv+7kUaUq+EBFNQw9Qec63lcMJVuXVWczjL16nKW1EzzV515ag+OWL46z3lEPForDhufw0VtFnNmaX68jkO0mp01eLrHApc1llN2Y/U8GBXfNNazN4+Kom4H+z/AO+wJr8EsKMMUczCdQ3APgd9uVI0hzXw/Z3M=" # GITHUB_API_KEY addons: + apt: + packages: + - haveged coverity_scan: project: name: "cryptomator/cryptomator" @@ -19,21 +25,58 @@ addons: build_command: "mvn -fmain/pom.xml clean test -DskipTests" branch_pattern: release.* install: -# "clean" needed until https://bugs.openjdk.java.net/browse/JDK-8067747 is resolved. -- mvn -fmain/pom.xml clean package -DskipTests dependency:go-offline -Pcoverage -- mvn -fmain/pom.xml clean package -DskipTests dependency:go-offline -Prelease +- curl -o $HOME/.m2/settings.xml https://gist.githubusercontent.com/cryptobot/cf5fbd909c4782aaeeeb7c7f4a1a43da/raw/e60ee486e34ee0c79f89f947abe2c83b4290c6bb/settings.xml +- mvn -fmain/pom.xml clean install -DskipTests org.codehaus.mojo:versions-maven-plugin:help dependency:go-offline -Pcoverage,release # "clean install" needed until we can exclude artifacts currently in the reactor, see https://maven.apache.org/plugins/maven-dependency-plugin/go-offline-mojo.html#excludeReactor and https://issues.apache.org/jira/browse/MDEP-568 script: - mvn --update-snapshots -fmain/pom.xml clean test jacoco:report verify -Pcoverage +after_success: +- jdk_switcher use oraclejdk8 +- curl -o ~/codacy-coverage-reporter-assembly-latest.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/2.0.1/codacy-coverage-reporter-2.0.1-assembly.jar +- $JAVA_HOME/bin/java -cp ~/codacy-coverage-reporter-assembly-latest.jar com.codacy.CodacyCoverageReporter -l Java -r main/jacoco-report/target/site/jacoco-aggregate/jacoco.xml before_deploy: -- mvn -fmain/pom.xml -Prelease clean package -DskipTests +- jdk_switcher use oraclejdk9 +- | + if [[ $TRAVIS_BRANCH == "develop" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]]; then + CONTINUOUS_RELEASE_URL=`curl -s https://api.github.com/repos/cryptomator/cryptomator/releases/tags/continuous | jq -re '.url'` + echo "Existing continuous release: ${CONTINUOUS_RELEASE_URL}" + if [[ $CONTINUOUS_RELEASE_URL == http* ]]; then + curl -u cryptobot:$GITHUB_API_KEY -X DELETE $CONTINUOUS_RELEASE_URL + fi + fi +- | + if [[ -n "$TRAVIS_TAG" ]]; then + mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=$TRAVIS_TAG + elif [[ $TRAVIS_BRANCH == "develop" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]]; then + mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=SNAPSHOT-$(echo $TRAVIS_COMMIT | head -c7) + git tag -f continuous + git remote add gh https://cryptobot:${GITHUB_API_KEY}@github.com/cryptomator/cryptomator.git + git push -f gh continuous + git remote remove gh + fi +- mvn -fmain/pom.xml clean package -Prelease -DskipTests deploy: -- provider: releases - prerelease: false - api_key: - secure: "d06x5z/a0GGPJWASlPn1m2qNUeIIMhdCeSW/fnzo8YtX1qNj+d5mZQe2HSRMItKYYqgt6hnwst0YngYKrGQ/Sg3vy9BxVzoJm04MDmBkDNfHNTMhcx0euFAgRsfkyFs/CVRBNW1MPOg8BTDtyKoknIuCl2FPIKouP2QHO+CBYYY=" +- provider: releases # CONTINUOUS + prerelease: true + api-key: $GITHUB_API_KEY + tag_name: continuous + overwrite: true + file_glob: true file: - - "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar" - - "main/ant-kit/target/antkit.tar.gz" + - "main/uber-jar/target/Cryptomator-*.jar" + - "main/ant-kit/target/antkit.tar.gz" + skip_cleanup: true + name: Cryptomator continuous build + body: Automatically built on $(date +'%F %T %Z'). + 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" + - "main/ant-kit/target/antkit.tar.gz" skip_cleanup: true on: repo: cryptomator/cryptomator diff --git a/README.md b/README.md index 235d688a3..1fe176f9a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![cryptomator](cryptomator.png) [![Build Status](https://travis-ci.org/cryptomator/cryptomator.svg?branch=master)](https://travis-ci.org/cryptomator/cryptomator) -[![Coverity Scan Build Status](https://scan.coverity.com/projects/cryptomator-cryptomator/badge.svg?flat=1)](https://scan.coverity.com/projects/cryptomator-cryptomator) +[![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/cryptomator/badge.svg?targetFile=main%2Fpom.xml)](https://snyk.io/test/github/cryptomator/cryptomator?targetFile=main%2Fpom.xml) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/2a0adf3cec6a4143b91035d3924178f1)](https://www.codacy.com/app/cryptomator/cryptomator?utm_source=github.com&utm_medium=referral&utm_content=cryptomator/cryptomator&utm_campaign=Badge_Grade) [![Twitter](https://img.shields.io/badge/twitter-@Cryptomator-blue.svg?style=flat)](http://twitter.com/Cryptomator) [![POEditor](https://img.shields.io/badge/POEditor-Help%20Translate-blue.svg?style=flat)](https://poeditor.com/join/project/bHwbvJmx0E) diff --git a/main/ant-kit/assembly.xml b/main/ant-kit/assembly.xml index 4ca4dfd66..182a1b654 100644 --- a/main/ant-kit/assembly.xml +++ b/main/ant-kit/assembly.xml @@ -1,6 +1,6 @@ - + tarball false @@ -29,6 +29,7 @@ target build.xml + logback.xml false . diff --git a/main/ant-kit/pom.xml b/main/ant-kit/pom.xml index 8373164f5..83401f36b 100644 --- a/main/ant-kit/pom.xml +++ b/main/ant-kit/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.3.5 + 1.4.0-beta1 ant-kit pom @@ -56,15 +56,15 @@ src/main/resources true - - fixed-binaries/** - + + build.xml + src/main/resources false - fixed-binaries/** + logback.xml @@ -76,7 +76,7 @@ maven-assembly-plugin - 3.0.0 + 3.1.0 make-assembly diff --git a/main/ant-kit/src/main/resources/build.xml b/main/ant-kit/src/main/resources/build.xml index 4252f0716..aae41cbe3 100644 --- a/main/ant-kit/src/main/resources/build.xml +++ b/main/ant-kit/src/main/resources/build.xml @@ -1,6 +1,6 @@ - + @@ -20,51 +20,25 @@ - - - - + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/main/ant-kit/src/main/resources/fixed-binaries/linux-launcher-x64 b/main/ant-kit/src/main/resources/fixed-binaries/linux-launcher-x64 deleted file mode 100644 index bffda959a..000000000 Binary files a/main/ant-kit/src/main/resources/fixed-binaries/linux-launcher-x64 and /dev/null differ diff --git a/main/ant-kit/src/main/resources/fixed-binaries/linux-launcher-x86 b/main/ant-kit/src/main/resources/fixed-binaries/linux-launcher-x86 deleted file mode 100644 index 805062d62..000000000 Binary files a/main/ant-kit/src/main/resources/fixed-binaries/linux-launcher-x86 and /dev/null differ diff --git a/main/ant-kit/src/main/resources/package/linux/control b/main/ant-kit/src/main/resources/package/linux/control deleted file mode 100644 index 95d2a7148..000000000 --- a/main/ant-kit/src/main/resources/package/linux/control +++ /dev/null @@ -1,16 +0,0 @@ -Package: APPLICATION_PACKAGE -Version: APPLICATION_VERSION -Section: contrib/utils -Maintainer: Sebastian Stenzel -Homepage: https://cryptomator.org -Vcs-Git: https://github.com/totalvoidness/cryptomator.git -Vcs-Browser: https://github.com/totalvoidness/cryptomator -Priority: optional -Architecture: APPLICATION_ARCH -Provides: APPLICATION_PACKAGE -Installed-Size: APPLICATION_INSTALLED_SIZE -Depends: gvfs-bin, gvfs-backends, gvfs-fuse -Description: Multi-platform client-side encryption of your cloud files. - Cryptomator provides free client-side AES encryption for your cloud files. - Create encrypted vaults, which get mounted as virtual volumes. Whatever - you save on one of these volumes will end up encrypted inside your vault. diff --git a/main/ant-kit/src/main/resources/package/linux/copyright b/main/ant-kit/src/main/resources/package/linux/copyright deleted file mode 100644 index 3323c421d..000000000 --- a/main/ant-kit/src/main/resources/package/linux/copyright +++ /dev/null @@ -1,23 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: cryptomator -Source: - -Copyright: 2015 Sebastian Stenzel and contributors. -License: MIT - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - . - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - . - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/main/ant-kit/src/main/resources/package/linux/postinst b/main/ant-kit/src/main/resources/package/linux/postinst deleted file mode 100644 index ee09f3b1d..000000000 --- a/main/ant-kit/src/main/resources/package/linux/postinst +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh -# postinst script for APPLICATION_NAME -# -# see: dh_installdeb(1) - -set -e - -# summary of how this script can be called: -# * `configure' -# * `abort-upgrade' -# * `abort-remove' `in-favour' -# -# * `abort-remove' -# * `abort-deconfigure' `in-favour' -# `removing' -# -# for details, see http://www.debian.org/doc/debian-policy/ or -# the debian-policy package - -case "$1" in - configure) - echo Adding shortcut to the menu -SECONDARY_LAUNCHERS_INSTALL -APP_CDS_CACHE - mkdir -pm 644 /usr/share/desktop-directories - xdg-desktop-menu install --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop -FILE_ASSOCIATION_INSTALL - - rm /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME - if [ $(uname -m) = "x86_64" ]; then - mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x64 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME - else - mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x86 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME - fi - ;; - - abort-upgrade|abort-remove|abort-deconfigure) - ;; - - *) - echo "postinst called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -# dh_installdeb will replace this with shell code automatically -# generated by other debhelper scripts. - -#DEBHELPER# - -exit 0 diff --git a/main/ant-kit/src/main/resources/package/linux/spec b/main/ant-kit/src/main/resources/package/linux/spec deleted file mode 100644 index b40f9224e..000000000 --- a/main/ant-kit/src/main/resources/package/linux/spec +++ /dev/null @@ -1,54 +0,0 @@ -Summary: APPLICATION_SUMMARY -Name: APPLICATION_PACKAGE -Version: APPLICATION_VERSION -Release: 1 -License: APPLICATION_LICENSE_TYPE -Vendor: APPLICATION_VENDOR -Prefix: /opt -Provides: APPLICATION_PACKAGE -Requires: ld-linux.so.2 libX11.so.6 libXext.so.6 libXi.so.6 libXrender.so.1 libXtst.so.6 libasound.so.2 libc.so.6 libdl.so.2 libgcc_s.so.1 libm.so.6 libpthread.so.0 libthread_db.so.1 -Autoprov: 0 -Autoreq: 0 - -#avoid ARCH subfolder -%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm - -#comment line below to enable effective jar compression -#it could easily get your package size from 40 to 15Mb but -#build time will substantially increase and it may require unpack200/system java to install -%define __jar_repack %{nil} - -%description -APPLICATION_DESCRIPTION - -%prep - -%build - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}/opt -cp -r %{_sourcedir}/APPLICATION_FS_NAME %{buildroot}/opt - -%files -APPLICATION_LICENSE_FILE -/opt/APPLICATION_FS_NAME - -%post -SECONDARY_LAUNCHERS_INSTALL -APP_CDS_CACHE -xdg-desktop-menu install --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop -FILE_ASSOCIATION_INSTALL -rm /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME -if [ $(uname -m) = "x86_64" ]; then - mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x64 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME -else - mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x86 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME -fi - -%preun -SECONDARY_LAUNCHERS_REMOVE -xdg-desktop-menu uninstall --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop -FILE_ASSOCIATION_REMOVE - -%clean diff --git a/main/commons/pom.xml b/main/commons/pom.xml index 570d05e07..c6097c3be 100644 --- a/main/commons/pom.xml +++ b/main/commons/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.3.5 + 1.4.0-beta1 commons Cryptomator Commons @@ -34,15 +34,6 @@ com.google.dagger dagger - - com.google.dagger - dagger-compiler - - - - com.google.dagger - dagger-compiler - diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java index a2721f4d3..bd32a1cb6 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java @@ -2,7 +2,7 @@ * Copyright (c) 2014, 2017 Sebastian Stenzel * All rights reserved. * This program and the accompanying materials are made available under the terms of the accompanying LICENSE file. - * + * * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ @@ -12,8 +12,10 @@ import java.util.function.Consumer; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ObservableValue; @@ -30,6 +32,7 @@ public class Settings { public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3; public static final String DEFAULT_GVFS_SCHEME = "dav"; public static final boolean DEFAULT_DEBUG_MODE = false; + public static final VolumeImpl DEFAULT_VOLUME_IMPL = VolumeImpl.FUSE; private final ObservableList directories = FXCollections.observableArrayList(VaultSettings::observables); private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES); @@ -37,6 +40,8 @@ public class Settings { private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS); private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME); private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE); + private final ObjectProperty volumeImpl = new SimpleObjectProperty<>(DEFAULT_VOLUME_IMPL); + private Consumer saveCmd; /** @@ -49,6 +54,7 @@ public class Settings { numTrayNotifications.addListener(this::somethingChanged); preferredGvfsScheme.addListener(this::somethingChanged); debugMode.addListener(this::somethingChanged); + volumeImpl.addListener(this::somethingChanged); } void setSaveCmd(Consumer saveCmd) { @@ -91,4 +97,8 @@ public class Settings { return debugMode; } + public ObjectProperty volumeImpl() { + return volumeImpl; + } + } diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java index e474fcf7c..00dc439f7 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java @@ -33,6 +33,7 @@ public class SettingsJsonAdapter extends TypeAdapter { out.name("numTrayNotifications").value(value.numTrayNotifications().get()); out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get()); out.name("debugMode").value(value.debugMode().get()); + out.name("volumeImpl").value(value.volumeImpl().get().name()); out.endObject(); } @@ -52,27 +53,30 @@ public class SettingsJsonAdapter extends TypeAdapter { while (in.hasNext()) { String name = in.nextName(); switch (name) { - case "directories": - settings.getDirectories().addAll(readVaultSettingsArray(in)); - break; - case "checkForUpdatesEnabled": - settings.checkForUpdates().set(in.nextBoolean()); - break; - case "port": - settings.port().set(in.nextInt()); - break; - case "numTrayNotifications": - settings.numTrayNotifications().set(in.nextInt()); - break; - case "preferredGvfsScheme": - settings.preferredGvfsScheme().set(in.nextString()); - break; - case "debugMode": - settings.debugMode().set(in.nextBoolean()); - break; - default: - LOG.warn("Unsupported vault setting found in JSON: " + name); - in.skipValue(); + case "directories": + settings.getDirectories().addAll(readVaultSettingsArray(in)); + break; + case "checkForUpdatesEnabled": + settings.checkForUpdates().set(in.nextBoolean()); + break; + case "port": + settings.port().set(in.nextInt()); + break; + case "numTrayNotifications": + settings.numTrayNotifications().set(in.nextInt()); + break; + case "preferredGvfsScheme": + settings.preferredGvfsScheme().set(in.nextString()); + break; + case "debugMode": + settings.debugMode().set(in.nextBoolean()); + break; + case "volumeImpl": + settings.volumeImpl().set(parseNioAdapterName(in.nextString())); + break; + default: + LOG.warn("Unsupported vault setting found in JSON: " + name); + in.skipValue(); } } in.endObject(); @@ -80,6 +84,14 @@ public class SettingsJsonAdapter extends TypeAdapter { return settings; } + private VolumeImpl parseNioAdapterName(String nioAdapterName) { + try { + return VolumeImpl.valueOf(nioAdapterName); + } catch (IllegalArgumentException e) { + return Settings.DEFAULT_VOLUME_IMPL; + } + } + private List readVaultSettingsArray(JsonReader in) throws IOException { List result = new ArrayList<>(); in.beginArray(); diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java index 8ee82146f..f6a0aa805 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java @@ -28,6 +28,7 @@ public class VaultSettings { public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false; public static final boolean DEFAULT_MOUNT_AFTER_UNLOCK = true; public static final boolean DEFAULT_REAVEAL_AFTER_MOUNT = true; + public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false; private final String id; private final ObjectProperty path = new SimpleObjectProperty<>(); @@ -36,6 +37,8 @@ public class VaultSettings { private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP); private final BooleanProperty mountAfterUnlock = new SimpleBooleanProperty(DEFAULT_MOUNT_AFTER_UNLOCK); private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT); + private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH); + private final StringProperty individualMountPath = new SimpleStringProperty(); public VaultSettings(String id) { this.id = Objects.requireNonNull(id); @@ -44,7 +47,7 @@ public class VaultSettings { } Observable[] observables() { - return new Observable[] {path, mountName, winDriveLetter, unlockAfterStartup, mountAfterUnlock, revealAfterMount}; + return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, mountAfterUnlock, revealAfterMount, usesIndividualMountPath, individualMountPath}; } private void deriveMountNameFromPath(Path path) { @@ -123,6 +126,14 @@ public class VaultSettings { return revealAfterMount; } + public BooleanProperty usesIndividualMountPath() { + return usesIndividualMountPath; + } + + public StringProperty individualMountPath() { + return individualMountPath; + } + /* Hashcode/Equals */ @Override diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java index d1de6231e..f63ab3c33 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java @@ -27,6 +27,9 @@ class VaultSettingsJsonAdapter { out.name("unlockAfterStartup").value(value.unlockAfterStartup().get()); out.name("mountAfterUnlock").value(value.mountAfterUnlock().get()); out.name("revealAfterMount").value(value.revealAfterMount().get()); + out.name("usesIndividualMountPath").value(value.usesIndividualMountPath().get()); + //TODO: should this always be written? ( because it could contain metadata, which the user does not want to save!) + out.name("individualMountPath").value(value.individualMountPath().get()); out.endObject(); } @@ -34,39 +37,47 @@ class VaultSettingsJsonAdapter { String id = null; String path = null; String mountName = null; + String individualMountPath = null; String winDriveLetter = null; boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP; boolean mountAfterUnlock = VaultSettings.DEFAULT_MOUNT_AFTER_UNLOCK; boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT; + boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH; in.beginObject(); while (in.hasNext()) { String name = in.nextName(); switch (name) { - case "id": - id = in.nextString(); - break; - case "path": - path = in.nextString(); - break; - case "mountName": - mountName = in.nextString(); - break; - case "winDriveLetter": - winDriveLetter = in.nextString(); - break; - case "unlockAfterStartup": - unlockAfterStartup = in.nextBoolean(); - break; - case "mountAfterUnlock": - mountAfterUnlock = in.nextBoolean(); - break; - case "revealAfterMount": - revealAfterMount = in.nextBoolean(); - break; - default: - LOG.warn("Unsupported vault setting found in JSON: " + name); - in.skipValue(); + case "id": + id = in.nextString(); + break; + case "path": + path = in.nextString(); + break; + case "mountName": + mountName = in.nextString(); + break; + case "winDriveLetter": + winDriveLetter = in.nextString(); + break; + case "unlockAfterStartup": + unlockAfterStartup = in.nextBoolean(); + break; + case "mountAfterUnlock": + mountAfterUnlock = in.nextBoolean(); + break; + case "revealAfterMount": + revealAfterMount = in.nextBoolean(); + break; + case "usesIndividualMountPath": + usesIndividualMountPath = in.nextBoolean(); + break; + case "individualMountPath": + individualMountPath = in.nextString(); + break; + default: + LOG.warn("Unsupported vault setting found in JSON: " + name); + in.skipValue(); } } in.endObject(); @@ -78,6 +89,8 @@ class VaultSettingsJsonAdapter { vaultSettings.unlockAfterStartup().set(unlockAfterStartup); vaultSettings.mountAfterUnlock().set(mountAfterUnlock); vaultSettings.revealAfterMount().set(revealAfterMount); + vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath); + vaultSettings.individualMountPath().set(individualMountPath); return vaultSettings; } diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/VolumeImpl.java b/main/commons/src/main/java/org/cryptomator/common/settings/VolumeImpl.java new file mode 100644 index 000000000..0862c499b --- /dev/null +++ b/main/commons/src/main/java/org/cryptomator/common/settings/VolumeImpl.java @@ -0,0 +1,33 @@ +package org.cryptomator.common.settings; + +import java.util.Arrays; + +public enum VolumeImpl { + WEBDAV("WebDAV"), + FUSE("FUSE"); + + private String displayName; + + VolumeImpl(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + + /** + * Finds a VolumeImpl by display name. + * + * @param displayName Display name of the VolumeImpl + * @return VolumeImpl with the given displayName. + * @throws IllegalArgumentException if not volumeImpl with the given displayName was found. + */ + public static VolumeImpl forDisplayName(String displayName) throws IllegalArgumentException { + return Arrays.stream(values()) // + .filter(impl -> impl.displayName.equals(displayName)) // + .findAny() // + .orElseThrow(IllegalArgumentException::new); + } + +} diff --git a/main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java index 23ddaab1d..511197d29 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 @@ -21,17 +21,17 @@ public class SettingsJsonAdapterTest { String json = "{\"directories\": [" + vault1Json + "," + vault2Json + "]," // + "\"checkForUpdatesEnabled\": true,"// + "\"port\": 8080,"// - + "\"useIpv6\": true,"// - + "\"numTrayNotifications\": 42}"; + + "\"numTrayNotifications\": 42,"// + + "\"volumeImpl\": \"FUSE\"}"; Settings settings = adapter.fromJson(json); Assert.assertTrue(settings.checkForUpdates().get()); Assert.assertEquals(2, settings.getDirectories().size()); Assert.assertEquals(8080, settings.port().get()); - // Assert.assertTrue(settings.useIpv6().get()); temporarily ignored Assert.assertEquals(42, settings.numTrayNotifications().get()); Assert.assertEquals("dav", settings.preferredGvfsScheme().get()); + Assert.assertEquals(VolumeImpl.FUSE, settings.volumeImpl().get()); } } diff --git a/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java index 7320b6768..e3f31251e 100644 --- a/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java +++ b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java @@ -20,7 +20,7 @@ public class VaultSettingsJsonAdapterTest { @Test public void testDeserialize() throws IOException { - String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true}"; + String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\"}"; JsonReader jsonReader = new JsonReader(new StringReader(json)); VaultSettings vaultSettings = adapter.read(jsonReader); @@ -28,6 +28,7 @@ public class VaultSettingsJsonAdapterTest { Assert.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get()); Assert.assertEquals("test", vaultSettings.mountName().get()); Assert.assertEquals("X", vaultSettings.winDriveLetter().get()); + Assert.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get()); } } diff --git a/main/jacoco-report/pom.xml b/main/jacoco-report/pom.xml index 748da08ef..cd65ba3dd 100644 --- a/main/jacoco-report/pom.xml +++ b/main/jacoco-report/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.3.5 + 1.4.0-beta1 jacoco-report Cryptomator Code Coverage Report @@ -27,27 +27,6 @@ org.cryptomator launcher - - - - org.apache.logging.log4j - * - - - - - - - com.codacy - codacy-coverage-reporter - 1.0.13 - assembly - - - * - * - - @@ -66,28 +45,6 @@ - - org.codehaus.mojo - exec-maven-plugin - 1.5.0 - - - verify - - java - - - com.codacy.CodacyCoverageReporter - - -l - Java - -r - ${project.build.directory}/site/jacoco-aggregate/jacoco.xml - - - - - diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml index 33f066dfc..084fafbf0 100644 --- a/main/keychain/pom.xml +++ b/main/keychain/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.3.5 + 1.4.0-beta1 keychain System Keychain Access @@ -34,10 +34,6 @@ com.google.dagger dagger - - com.google.dagger - dagger-compiler - diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java index f45492d1b..5db7bc372 100644 --- a/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java +++ b/main/keychain/src/main/java/org/cryptomator/keychain/KeychainModule.java @@ -8,17 +8,27 @@ package org.cryptomator.keychain; import java.util.Optional; import java.util.Set; -import org.cryptomator.jni.JniModule; - import com.google.common.collect.Sets; - import dagger.Module; import dagger.Provides; import dagger.multibindings.ElementsIntoSet; +import org.cryptomator.jni.JniFunctions; +import org.cryptomator.jni.MacFunctions; +import org.cryptomator.jni.WinFunctions; -@Module(includes = {JniModule.class}) +@Module public class KeychainModule { + @Provides + Optional provideOptionalMacFunctions() { + return JniFunctions.macFunctions(); + } + + @Provides + Optional provideOptionalWinFunctions() { + return JniFunctions.winFunctions(); + } + @Provides @ElementsIntoSet Set provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain) { diff --git a/main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java b/main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java index 6a8122129..b3b27a4ae 100644 --- a/main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java +++ b/main/keychain/src/main/java/org/cryptomator/keychain/MacSystemKeychainAccess.java @@ -15,35 +15,35 @@ import org.cryptomator.jni.MacKeychainAccess; class MacSystemKeychainAccess implements KeychainAccessStrategy { - private final MacKeychainAccess keychain; + private final Optional macFunctions; @Inject public MacSystemKeychainAccess(Optional macFunctions) { - if (macFunctions.isPresent()) { - this.keychain = macFunctions.get().keychainAccess(); - } else { - this.keychain = null; - } + this.macFunctions = macFunctions; + } + + private MacKeychainAccess keychain() { + return macFunctions.orElseThrow(IllegalStateException::new).keychainAccess(); } @Override public void storePassphrase(String key, CharSequence passphrase) { - keychain.storePassword(key, passphrase); + keychain().storePassword(key, passphrase); } @Override public char[] loadPassphrase(String key) { - return keychain.loadPassword(key); + return keychain().loadPassword(key); } @Override public boolean isSupported() { - return SystemUtils.IS_OS_MAC_OSX && keychain != null; + return SystemUtils.IS_OS_MAC_OSX && macFunctions.isPresent(); } @Override public void deletePassphrase(String key) { - keychain.deletePassword(key); + keychain().deletePassword(key); } } 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 f31536aff..57d9cdfa7 100644 --- a/main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java +++ b/main/keychain/src/main/java/org/cryptomator/keychain/WindowsProtectedKeychainAccess.java @@ -5,8 +5,6 @@ *******************************************************************************/ package org.cryptomator.keychain; -import static java.nio.charset.StandardCharsets.UTF_8; - import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -31,12 +29,6 @@ import java.util.UUID; import javax.inject.Inject; -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.jni.WinDataProtection; -import org.cryptomator.jni.WinFunctions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.google.common.io.BaseEncoding; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -49,6 +41,13 @@ import com.google.gson.JsonSerializationContext; 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.jni.WinDataProtection; +import org.cryptomator.jni.WinFunctions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.nio.charset.StandardCharsets.UTF_8; class WindowsProtectedKeychainAccess implements KeychainAccessStrategy { @@ -57,19 +56,15 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy { .registerTypeHierarchyAdapter(byte[].class, new ByteArrayJsonAdapter()) // .disableHtmlEscaping().create(); - private final WinDataProtection dataProtection; + private final Optional winFunctions; private final Path keychainPath; private Map keychainEntries; @Inject public WindowsProtectedKeychainAccess(Optional winFunctions) { - if (winFunctions.isPresent()) { - this.dataProtection = winFunctions.get().dataProtection(); - } else { - this.dataProtection = null; - } + this.winFunctions = winFunctions; String keychainPathProperty = System.getProperty("cryptomator.keychainPath"); - if (dataProtection != null && keychainPathProperty == null) { + if (keychainPathProperty == null) { LOG.warn("Windows DataProtection module loaded, but no cryptomator.keychainPath property found."); } if (keychainPathProperty != null) { @@ -82,6 +77,10 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy { } } + private WinDataProtection dataProtection() { + return winFunctions.orElseThrow(IllegalStateException::new).dataProtection(); + } + @Override public void storePassphrase(String key, CharSequence passphrase) { loadKeychainEntriesIfNeeded(); @@ -90,7 +89,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy { buf.get(cleartext); KeychainEntry entry = new KeychainEntry(); entry.salt = generateSalt(); - entry.ciphertext = dataProtection.protect(cleartext, entry.salt); + entry.ciphertext = dataProtection().protect(cleartext, entry.salt); Arrays.fill(buf.array(), (byte) 0x00); Arrays.fill(cleartext, (byte) 0x00); keychainEntries.put(key, entry); @@ -104,7 +103,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy { if (entry == null) { return null; } - byte[] cleartext = dataProtection.unprotect(entry.ciphertext, entry.salt); + byte[] cleartext = dataProtection().unprotect(entry.ciphertext, entry.salt); if (cleartext == null) { return null; } @@ -125,7 +124,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy { @Override public boolean isSupported() { - return SystemUtils.IS_OS_WINDOWS && dataProtection != null && keychainPath != null; + return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && keychainPath != null; } private byte[] generateSalt() { 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 f382190a5..1bd556061 100644 --- a/main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java +++ b/main/keychain/src/test/java/org/cryptomator/keychain/KeychainModuleTest.java @@ -14,9 +14,11 @@ public class KeychainModuleTest { @Test public void testGetKeychain() { - Optional keychainAccess = DaggerTestKeychainComponent.builder().jniModule(new TestJniModule()).keychainModule(new TestKeychainModule()).build().keychainAccess(); + Optional keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess(); Assert.assertTrue(keychainAccess.isPresent()); Assert.assertTrue(keychainAccess.get() instanceof MapKeychainAccess); + keychainAccess.get().storePassphrase("test", "asd"); + Assert.assertArrayEquals("asd".toCharArray(), keychainAccess.get().loadPassphrase("test")); } } diff --git a/main/keychain/src/test/java/org/cryptomator/keychain/TestJniModule.java b/main/keychain/src/test/java/org/cryptomator/keychain/TestJniModule.java deleted file mode 100644 index 6c54e2b4f..000000000 --- a/main/keychain/src/test/java/org/cryptomator/keychain/TestJniModule.java +++ /dev/null @@ -1,28 +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.keychain; - -import java.util.Optional; - -import org.cryptomator.jni.JniModule; -import org.cryptomator.jni.MacFunctions; -import org.cryptomator.jni.WinFunctions; - -import dagger.Lazy; - -public class TestJniModule extends JniModule { - - @Override - public Optional winFunctions(Lazy winFunction) { - return Optional.empty(); - } - - @Override - public Optional macFunctions(Lazy winFunction) { - return Optional.empty(); - } - -} diff --git a/main/launcher/pom.xml b/main/launcher/pom.xml index 5fc315ca0..ca4a8bfac 100644 --- a/main/launcher/pom.xml +++ b/main/launcher/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.3.5 + 1.4.0-beta1 launcher Cryptomator Launcher @@ -34,10 +34,6 @@ com.google.dagger dagger - - com.google.dagger - dagger-compiler - 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 3a0ac97c9..8f10c6aa2 100644 --- a/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java +++ b/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java @@ -6,6 +6,7 @@ *******************************************************************************/ package org.cryptomator.launcher; +import java.awt.Desktop; import java.io.File; import java.nio.file.FileSystem; import java.nio.file.FileSystems; @@ -13,7 +14,6 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.concurrent.BlockingQueue; -import org.cryptomator.ui.util.EawtApplicationWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,11 +24,13 @@ class FileOpenRequestHandler { public FileOpenRequestHandler(BlockingQueue fileOpenRequests) { this.fileOpenRequests = fileOpenRequests; - EawtApplicationWrapper.getApplication().ifPresent(app -> { - app.setOpenFileHandler(files -> { - files.stream().map(File::toPath).forEach(fileOpenRequests::add); + try { + Desktop.getDesktop().setOpenFileHandler(e -> { + e.getFiles().stream().map(File::toPath).forEach(fileOpenRequests::add); }); - }); + } catch (UnsupportedOperationException e) { + LOG.info("Unable to setOpenFileHandler, probably not supported on this OS."); + } } public void handleLaunchArgs(String[] args) { diff --git a/main/pom.xml b/main/pom.xml index cad6f1ed6..7ff14adfd 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator main - 1.3.5 + 1.4.0-beta1 pom Cryptomator @@ -25,18 +25,18 @@ 1.2.0 - 1.5.1 + 1.5.2 1.0.4 - 1.0.2 - + 2.0.0 + 0.1.4 + 2.5 3.6 - 4.5.3 - + 1.0.3 - 23.5-jre - 2.11 + 24.1-jre + 2.14.1 2.8.2 1.7.25 @@ -96,6 +96,11 @@ cryptofs ${cryptomator.cryptofs.version} + + org.cryptomator + fuse-nio-adapter + ${cryptomator.fuse.version} + org.cryptomator webdav-nio-adapter @@ -140,11 +145,6 @@ commons-lang3 ${commons-lang3.version} - - org.apache.httpcomponents - httpclient - ${httpclient.version} - @@ -164,12 +164,6 @@ dagger ${dagger.version} - - com.google.dagger - dagger-compiler - ${dagger.version} - true - com.google.code.gson gson @@ -270,7 +264,7 @@ maven-dependency-plugin - 3.0.2 + 3.1.0 copy-libs @@ -287,7 +281,7 @@ org.jacoco jacoco-maven-plugin - 0.7.9 + 0.8.1 prepare-agent @@ -310,8 +304,20 @@ maven-compiler-plugin 3.7.0 - 1.8 - 1.8 + 9 + 9 + 9 + + --add-modules + jdk.incubator.httpclient + + + + com.google.dagger + dagger-compiler + ${dagger.version} + + diff --git a/main/uber-jar/pom.xml b/main/uber-jar/pom.xml index c90bdd7cc..8e9da7d37 100644 --- a/main/uber-jar/pom.xml +++ b/main/uber-jar/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.3.5 + 1.4.0-beta1 uber-jar Single über jar with all dependencies diff --git a/main/ui/pom.xml b/main/ui/pom.xml index 1057b2af9..4b3f8098a 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.3.5 + 1.4.0-beta1 ui Cryptomator GUI @@ -30,7 +30,11 @@ org.cryptomator keychain - + + org.cryptomator + fuse-nio-adapter + + org.cryptomator @@ -62,20 +66,12 @@ org.apache.commons commons-lang3 - - org.apache.httpcomponents - httpclient - com.google.dagger dagger - - com.google.dagger - dagger-compiler - 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 60f6a76e7..4bcc9b877 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/UiModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/UiModule.java @@ -16,22 +16,20 @@ 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.settings.Settings; import org.cryptomator.common.settings.SettingsProvider; import org.cryptomator.frontend.webdav.WebDavServer; -import org.cryptomator.jni.JniModule; import org.cryptomator.keychain.KeychainModule; import org.cryptomator.ui.controllers.ViewControllerModule; import org.cryptomator.ui.model.VaultComponent; import org.fxmisc.easybind.EasyBind; -import dagger.Module; -import dagger.Provides; -import javafx.beans.binding.Binding; - -@Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class, JniModule.class}, subcomponents = {VaultComponent.class}) +@Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class}) public class UiModule { @Provides 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 682cd0afb..0f6010eec 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,8 +9,7 @@ ******************************************************************************/ package org.cryptomator.ui.controllers; -import static org.cryptomator.ui.util.DialogBuilderUtil.buildErrorDialog; - +import java.awt.Desktop; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -28,25 +27,6 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; -import org.apache.commons.lang3.SystemUtils; -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.AutoUnlocker; -import org.cryptomator.ui.model.UpgradeStrategies; -import org.cryptomator.ui.model.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.EawtApplicationWrapper; -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 javafx.application.Application; import javafx.application.Platform; import javafx.beans.binding.Binding; @@ -81,6 +61,25 @@ import javafx.scene.layout.Pane; import javafx.scene.text.Font; import javafx.stage.FileChooser; import javafx.stage.Stage; +import org.apache.commons.lang3.SystemUtils; +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.AutoUnlocker; +import org.cryptomator.ui.model.UpgradeStrategies; +import org.cryptomator.ui.model.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.fxmisc.easybind.EasyBind; +import org.fxmisc.easybind.Subscription; +import org.fxmisc.easybind.monadic.MonadicBinding; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.cryptomator.ui.util.DialogBuilderUtil.buildErrorDialog; @Singleton public class MainController implements ViewController { @@ -129,11 +128,13 @@ public class MainController implements ViewController { EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit); autoUnlocker.unlockAllSilently(); - EawtApplicationWrapper.getApplication().ifPresent(app -> { - app.setPreferencesHandler(() -> { + try { + Desktop.getDesktop().setPreferencesHandler(e -> { Platform.runLater(this::toggleShowSettings); }); - }); + } catch (UnsupportedOperationException e) { + LOG.info("Unable to setPreferencesHandler, probably not supported on this OS."); + } } @FXML @@ -169,8 +170,10 @@ public class MainController implements ViewController { @Override public void initialize() { vaultList.setItems(vaults); + vaultList.getSelectionModel().clearSelection(); vaultList.setOnKeyReleased(this::didPressKeyOnList); vaultList.setCellFactory(this::createDirecoryListCell); + root.setOnKeyReleased(this::didPressKeyOnRoot); activeController.set(viewControllerLoader.load("/fxml/welcome.fxml")); selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty()); removeVaultButton.disableProperty().bind(canEditSelectedVault.not()); @@ -191,6 +194,7 @@ public class MainController implements ViewController { public void initStage(Stage stage) { stage.setScene(new Scene(getRoot())); stage.sizeToScene(); + stage.setTitle(localization.getString("app.name")); // set once before bind to avoid display bugs with Linux window managers stage.titleProperty().bind(windowTitle()); stage.setResizable(false); loadFont("/css/ionicons.ttf"); @@ -200,9 +204,10 @@ public class MainController implements ViewController { subs = subs.and(EasyBind.includeWhen(mainWindow.getScene().getRoot().getStyleClass(), INACTIVE_WINDOW_STYLE_CLASS, mainWindow.focusedProperty().not())); Application.setUserAgentStylesheet(getClass().getResource("/css/mac_theme.css").toString()); } else if (SystemUtils.IS_OS_LINUX) { + stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon_512.png"))); Application.setUserAgentStylesheet(getClass().getResource("/css/linux_theme.css").toString()); } else if (SystemUtils.IS_OS_WINDOWS) { - stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon.png"))); + stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon_32.png"))); Application.setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString()); } exitUtil.initExitHandler(this::gracefulShutdown); @@ -396,7 +401,7 @@ public class MainController implements ViewController { } else if (newValue.isValidVaultDirectory() && upgradeStrategyForSelectedVault.isPresent()) { this.showUpgradeView(); } else if (newValue.isValidVaultDirectory()) { - this.showUnlockView(); + this.showUnlockView(UnlockController.State.UNLOCKING); } else { this.showInitializeView(); } @@ -408,6 +413,24 @@ public class MainController implements ViewController { } } + private void didPressKeyOnRoot(KeyEvent event) { + if ((event.isMetaDown() || event.isControlDown()) && event.getCode().isDigitKey()) { + int digit = Integer.valueOf(event.getText()); + switch (digit) { + case 0: { + vaultList.getSelectionModel().clearSelection(); + showWelcomeView(); + return; + } + default: { + vaultList.getSelectionModel().select(digit - 1); + activeController.get().focus(); + return; + } + } + } + } + private void didClickOnListCell(MouseEvent e) { if (MouseEvent.MOUSE_CLICKED.equals(e.getEventType()) && e.getSource() instanceof Cell && ((Cell) e.getSource()).isSelected()) { activeController.get().focus(); @@ -446,7 +469,7 @@ public class MainController implements ViewController { } public void didInitialize() { - showUnlockView(); + showUnlockView(UnlockController.State.INITIALIZED); activeController.get().focus(); } @@ -458,13 +481,13 @@ public class MainController implements ViewController { } public void didUpgrade() { - showUnlockView(); + showUnlockView(UnlockController.State.UPGRADED); activeController.get().focus(); } - private void showUnlockView() { + private void showUnlockView(UnlockController.State state) { final UnlockController ctrl = viewControllerLoader.load("/fxml/unlock.fxml"); - ctrl.setVault(selectedVault.get()); + ctrl.setVault(selectedVault.get(), state); ctrl.setListener(this::didUnlock); activeController.set(ctrl); } @@ -486,7 +509,7 @@ public class MainController implements ViewController { public void didLock(UnlockedController ctrl) { unlockedVaults.remove(ctrl.getVault()); - showUnlockView(); + showUnlockView(UnlockController.State.UNLOCKING); activeController.get().focus(); } @@ -499,7 +522,7 @@ public class MainController implements ViewController { } public void didChangePassword() { - showUnlockView(); + showUnlockView(UnlockController.State.PASSWORD_CHANGED); activeController.get().focus(); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java index 20ac7cac6..f4b997314 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java @@ -2,7 +2,7 @@ * Copyright (c) 2014, 2017 Sebastian Stenzel * All rights reserved. * This program and the accompanying materials are made available under the terms of the accompanying LICENSE file. - * + * * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ @@ -14,13 +14,8 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.common.settings.Settings; -import org.cryptomator.ui.l10n.Localization; - import com.google.common.base.CharMatcher; import com.google.common.base.Strings; - import javafx.beans.binding.Bindings; import javafx.event.ActionEvent; import javafx.fxml.FXML; @@ -31,7 +26,13 @@ import javafx.scene.control.ChoiceBox; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.input.KeyEvent; +import javafx.scene.layout.GridPane; import javafx.scene.layout.VBox; +import javafx.util.StringConverter; +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.VolumeImpl; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.ui.l10n.Localization; @Singleton public class SettingsController implements ViewController { @@ -52,6 +53,15 @@ public class SettingsController implements ViewController { @FXML private CheckBox checkForUpdatesCheckbox; + @FXML + private GridPane webdavVolume; + + @FXML + private GridPane fuseVolume; + + @FXML + private Label portFieldLabel; + @FXML private TextField portField; @@ -67,6 +77,12 @@ public class SettingsController implements ViewController { @FXML private ChoiceBox prefGvfsScheme; + @FXML + private Label volumeLabel; + + @FXML + private ChoiceBox volume; + @FXML private CheckBox debugModeCheckbox; @@ -75,25 +91,51 @@ public class SettingsController implements ViewController { @Override public void initialize() { + versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion.orElse("SNAPSHOT"))); checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally()); checkForUpdatesCheckbox.setSelected(settings.checkForUpdates().get() && !areUpdatesManagedExternally()); + + //NIOADAPTER + volume.getItems().addAll(getSupportedAdapters()); + volume.setValue(settings.volumeImpl().get()); + volume.setVisible(true); + volume.setConverter(new NioAdapterImplStringConverter()); + + //WEBDAV + webdavVolume.visibleProperty().bind(volume.valueProperty().isEqualTo(VolumeImpl.WEBDAV)); + webdavVolume.managedProperty().bind(webdavVolume.visibleProperty()); + prefGvfsScheme.managedProperty().bind(webdavVolume.visibleProperty()); + prefGvfsSchemeLabel.managedProperty().bind(webdavVolume.visibleProperty()); + portFieldLabel.managedProperty().bind(webdavVolume.visibleProperty()); + changePortButton.managedProperty().bind(webdavVolume.visibleProperty()); + portField.managedProperty().bind(webdavVolume.visibleProperty()); portField.setText(String.valueOf(settings.port().intValue())); portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents); changePortButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(portField.textProperty())); changePortButton.disableProperty().bind(Bindings.createBooleanBinding(this::isPortValid, portField.textProperty()).not()); - versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion.orElse("SNAPSHOT"))); - prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX); - prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX); prefGvfsScheme.getItems().add("dav"); prefGvfsScheme.getItems().add("webdav"); prefGvfsScheme.setValue(settings.preferredGvfsScheme().get()); + prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX); + prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX); + + //FUSE + fuseVolume.visibleProperty().bind(volume.valueProperty().isEqualTo(VolumeImpl.FUSE)); + fuseVolume.managedProperty().bind(fuseVolume.visibleProperty()); + debugModeCheckbox.setSelected(settings.debugMode().get()); settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty()); settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty()); + settings.volumeImpl().bind(volume.valueProperty()); settings.debugMode().bind(debugModeCheckbox.selectedProperty()); } + //TODO: how to implement this? + private VolumeImpl[] getSupportedAdapters() { + return new VolumeImpl[]{VolumeImpl.FUSE, VolumeImpl.WEBDAV}; + } + @Override public Parent getRoot() { return root; @@ -133,4 +175,17 @@ public class SettingsController implements ViewController { return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false")); } + private static class NioAdapterImplStringConverter extends StringConverter { + + @Override + public String toString(VolumeImpl object) { + return object.getDisplayName(); + } + + @Override + public VolumeImpl fromString(String string) { + return VolumeImpl.forDisplayName(string); + } + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java index 6d30a7e2f..fe2c2ca90 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java @@ -2,13 +2,14 @@ * Copyright (c) 2014, 2017 Sebastian Stenzel * All rights reserved. * This program and the accompanying materials are made available under the terms of the accompanying LICENSE file. - * + * * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ package org.cryptomator.ui.controllers; import java.io.IOException; +import java.nio.file.*; import java.util.Arrays; import java.util.Comparator; import java.util.Objects; @@ -16,8 +17,12 @@ import java.util.Optional; import javax.inject.Inject; +import javafx.beans.binding.Bindings; +import javafx.scene.layout.HBox; import org.apache.commons.lang3.CharUtils; import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.VolumeImpl; +import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException; @@ -72,17 +77,19 @@ public class UnlockController implements ViewController { private final WindowsDriveLetters driveLetters; private final ChangeListener driveLetterChangeListener = this::winDriveLetterDidChange; private final Optional keychainAccess; + private final Settings settings; private Vault vault; private Optional listener = Optional.empty(); private Subscription vaultSubs = Subscription.EMPTY; @Inject - public UnlockController(Application app, Localization localization, AsyncTaskService asyncTaskService, WindowsDriveLetters driveLetters, Optional keychainAccess) { + public UnlockController(Application app, Localization localization, AsyncTaskService asyncTaskService, WindowsDriveLetters driveLetters, Optional keychainAccess, Settings settings) { this.app = app; this.localization = localization; this.asyncTaskService = asyncTaskService; this.driveLetters = driveLetters; this.keychainAccess = keychainAccess; + this.settings = settings; } @FXML @@ -94,6 +101,9 @@ public class UnlockController implements ViewController { @FXML private Button unlockButton; + @FXML + private Label successMessage; + @FXML private CheckBox savePassword; @@ -112,6 +122,21 @@ public class UnlockController implements ViewController { @FXML private ChoiceBox winDriveLetter; + @FXML + private CheckBox useOwnMountPath; + + @FXML + private HBox mountPathBox; + + @FXML + private Label mountPathLabel; + + @FXML + private TextField mountPath; + + @FXML + private Button changeMountPathButton; + @FXML private ProgressIndicator progressIndicator; @@ -140,14 +165,35 @@ public class UnlockController implements ViewController { mountName.textProperty().addListener(this::mountNameDidChange); savePassword.setDisable(!keychainAccess.isPresent()); unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not())); + + mountPathLabel.setVisible(false); + mountPathBox.visibleProperty().bind(mountPathLabel.visibleProperty()); + mountPathBox.managedProperty().bind(mountPathLabel.managedProperty()); + mountPath.visibleProperty().bind(mountPathLabel.visibleProperty()); + mountPath.managedProperty().bind(mountPathLabel.managedProperty()); + changeMountPathButton.visibleProperty().bind(mountPathLabel.visibleProperty()); + changeMountPathButton.managedProperty().bind(mountPathLabel.managedProperty()); + if (SystemUtils.IS_OS_WINDOWS) { winDriveLetter.setConverter(new WinDriveLetterLabelConverter()); + useOwnMountPath.setVisible(false); + useOwnMountPath.setManaged(false); + mountPathLabel.setManaged(false); + //dirty cheat + mountPathBox.setMouseTransparent(true); } else { winDriveLetterLabel.setVisible(false); winDriveLetterLabel.setManaged(false); winDriveLetter.setVisible(false); winDriveLetter.setManaged(false); + if (VolumeImpl.WEBDAV.equals(settings.volumeImpl().get())) { + useOwnMountPath.setVisible(false); + useOwnMountPath.setManaged(false); + mountPathLabel.setManaged(false); + } } + changeMountPathButton.disableProperty().bind(Bindings.createBooleanBinding(this::isDirVaild, mountPath.textProperty()).not()); + } @Override @@ -160,7 +206,7 @@ public class UnlockController implements ViewController { passwordField.requestFocus(); } - void setVault(Vault vault) { + void setVault(Vault vault, State state) { vaultSubs.unsubscribe(); vaultSubs = Subscription.EMPTY; @@ -175,6 +221,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); if (SystemUtils.IS_OS_WINDOWS) { winDriveLetter.valueProperty().removeListener(driveLetterChangeListener); winDriveLetter.getItems().clear(); @@ -200,14 +248,23 @@ public class UnlockController implements ViewController { Arrays.fill(storedPw, ' '); } } - VaultSettings settings = vault.getVaultSettings(); - unlockAfterStartup.setSelected(savePassword.isSelected() && settings.unlockAfterStartup().get()); - mountAfterUnlock.setSelected(settings.mountAfterUnlock().get()); - revealAfterMount.setSelected(settings.revealAfterMount().get()); + VaultSettings vaultSettings = vault.getVaultSettings(); + unlockAfterStartup.setSelected(savePassword.isSelected() && vaultSettings.unlockAfterStartup().get()); + mountAfterUnlock.setSelected(vaultSettings.mountAfterUnlock().get()); + revealAfterMount.setSelected(vaultSettings.revealAfterMount().get()); + useOwnMountPath.setSelected(vaultSettings.usesIndividualMountPath().get()); + + vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), vaultSettings.unlockAfterStartup()::set)); + vaultSubs = vaultSubs.and(EasyBind.subscribe(mountAfterUnlock.selectedProperty(), vaultSettings.mountAfterUnlock()::set)); + vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), vaultSettings.revealAfterMount()::set)); + vaultSubs = vaultSubs.and(EasyBind.subscribe(useOwnMountPath.selectedProperty(), vaultSettings.usesIndividualMountPath()::set)); + + changeMountPathButton.visibleProperty().bind( + vaultSettings.individualMountPath().isNotEqualTo(mountPath.textProperty()) + ); + mountPath.textProperty().setValue(vaultSettings.individualMountPath().getValueSafe()); + mountPathLabel.visibleProperty().bind(useOwnMountPath.selectedProperty()); - vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), settings.unlockAfterStartup()::set)); - vaultSubs = vaultSubs.and(EasyBind.subscribe(mountAfterUnlock.selectedProperty(), settings.mountAfterUnlock()::set)); - vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), settings.revealAfterMount()::set)); } // **************************************** @@ -225,6 +282,7 @@ public class UnlockController implements ViewController { @FXML private void didClickAdvancedOptionsButton(ActionEvent event) { + successMessage.setVisible(false); advancedOptions.setVisible(!advancedOptions.isVisible()); if (advancedOptions.isVisible()) { advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.hide")); @@ -233,6 +291,28 @@ public class UnlockController implements ViewController { } } + @FXML + private void didClickchangeMountPathButton(ActionEvent event) { + assert isDirVaild(); + vault.setMountPath(mountPath.getText()); + } + + private boolean isDirVaild() { + try { + if (!mountPath.textProperty().isEmpty().get()) { + Path p = Paths.get(mountPath.textProperty().get()); + return Files.isDirectory(p) && Files.isReadable(p) && Files.isWritable(p) && Files.isExecutable(p); + } else { + return false; + } + + } catch (InvalidPathException e) { + LOG.info("Invalid path"); + return false; + } + } + + private void filterAlphanumericKeyEvents(KeyEvent t) { if (!Strings.isNullOrEmpty(t.getCharacter()) && !ALPHA_NUMERIC_MATCHER.matchesAllOf(t.getCharacter())) { t.consume(); @@ -397,7 +477,28 @@ public class UnlockController implements ViewController { @FunctionalInterface interface UnlockListener { + void didUnlock(Vault vault); } + /* state */ + + public enum State { + UNLOCKING(null), + INITIALIZED("unlock.successLabel.vaultCreated"), + PASSWORD_CHANGED("unlock.successLabel.passwordChanged"), + UPGRADED("unlock.successLabel.upgraded"); + + private Optional successMessage; + + State(String successMessage) { + this.successMessage = Optional.ofNullable(successMessage); + } + + public Optional successMessage() { + return successMessage; + } + + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java index fdafb99c4..d1f0e0417 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java @@ -244,8 +244,8 @@ public class UnlockedController implements ViewController { @FXML private void didClickCopyUrl(ActionEvent event) { ClipboardContent clipboardContent = new ClipboardContent(); - clipboardContent.putUrl(vault.get().getWebDavUrl()); - clipboardContent.putString(vault.get().getWebDavUrl()); + clipboardContent.putUrl(vault.get().getFilesystemRootUrl()); + clipboardContent.putString(vault.get().getFilesystemRootUrl()); Clipboard.getSystemClipboard().setContent(clipboardContent); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java index c2507a6bc..aed401acc 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,11 +8,10 @@ ******************************************************************************/ package org.cryptomator.ui.controllers; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; +import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.Comparator; import java.util.Map; import java.util.Optional; @@ -22,23 +21,9 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; -import org.apache.commons.lang3.SystemUtils; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.cryptomator.common.settings.Settings; -import org.cryptomator.ui.l10n.Localization; -import org.cryptomator.ui.util.AsyncTaskService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; - import javafx.application.Application; import javafx.application.Platform; import javafx.event.ActionEvent; @@ -49,6 +34,15 @@ import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; import javafx.scene.layout.VBox; +import jdk.incubator.http.HttpClient; +import jdk.incubator.http.HttpRequest; +import jdk.incubator.http.HttpResponse; +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.ui.l10n.Localization; +import org.cryptomator.ui.util.AsyncTaskService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Singleton public class WelcomeController implements ViewController { @@ -114,31 +108,21 @@ public class WelcomeController implements ViewController { checkForUpdatesStatus.setText(localization.getString("welcome.checkForUpdates.label.currentlyChecking")); checkForUpdatesIndicator.setVisible(true); asyncTaskService.asyncTaskOf(() -> { - RequestConfig requestConfig = RequestConfig.custom() // - .setConnectTimeout(5000) // - .setConnectionRequestTimeout(5000) // - .setSocketTimeout(5000) // - .build(); String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH); - HttpClientBuilder httpClientBuilder = HttpClients.custom() // - .disableCookieManagement() // - .setDefaultRequestConfig(requestConfig) // - .setUserAgent(userAgent); - LOG.debug("Checking for updates..."); - try (CloseableHttpClient client = httpClientBuilder.build()) { - HttpGet request = new HttpGet("https://api.cryptomator.org/updates/latestVersion.json"); - try (CloseableHttpResponse response = client.execute(request)) { - if (response.getStatusLine().getStatusCode() == 200 && response.getEntity() != null) { - try (InputStream in = response.getEntity().getContent()) { - Gson gson = new GsonBuilder().setLenient().create(); - Reader utf8Reader = new InputStreamReader(in, StandardCharsets.UTF_8); - Map map = gson.fromJson(utf8Reader, new TypeToken>() { - }.getType()); - if (map != null) { - this.compareVersions(map); - } - } - } + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .GET() + .uri(new URI("https://api.cryptomator.org/updates/latestVersion.json")) + .header("User-Agent", userAgent) + .timeout(Duration.ofSeconds(5)) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandler.asString(StandardCharsets.UTF_8)); + if (response.statusCode() == 200) { + Gson gson = new GsonBuilder().setLenient().create(); + Map map = gson.fromJson(response.body(), new TypeToken>() { + }.getType()); + if (map != null) { + this.compareVersions(map); } } }).andFinally(() -> { diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java b/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java index 3d5ac4201..ba982c872 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/AutoUnlocker.java @@ -18,7 +18,6 @@ import javax.inject.Inject; import javax.inject.Singleton; import org.cryptomator.cryptolib.api.CryptoException; -import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException; import org.cryptomator.keychain.KeychainAccess; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/CommandFailedException.java b/main/ui/src/main/java/org/cryptomator/ui/model/CommandFailedException.java new file mode 100644 index 000000000..77d04f845 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/CommandFailedException.java @@ -0,0 +1,17 @@ +package org.cryptomator.ui.model; + +public class CommandFailedException extends Exception { + + public CommandFailedException(String message) { + super(message); + } + + public CommandFailedException(Throwable cause) { + super(cause); + } + + public CommandFailedException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java b/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java new file mode 100644 index 000000000..e1c6907d6 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java @@ -0,0 +1,144 @@ +package org.cryptomator.ui.model; + +import java.io.IOException; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.inject.Inject; + +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.cryptofs.CryptoFileSystem; +import org.cryptomator.frontend.fuse.mount.EnvironmentVariables; +import org.cryptomator.frontend.fuse.mount.FuseMountFactory; +import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException; +import org.cryptomator.frontend.fuse.mount.Mount; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@VaultModule.PerVault +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 final VaultSettings vaultSettings; + private final WindowsDriveLetters windowsDriveLetters; + + private Mount fuseMnt; + private CryptoFileSystem cfs; + private Path mountPath; + private boolean extraDirCreated; + + @Inject + public FuseVolume(VaultSettings vaultSettings, WindowsDriveLetters windowsDriveLetters) { + this.vaultSettings = vaultSettings; + this.windowsDriveLetters = windowsDriveLetters; + this.extraDirCreated = false; + } + + @Override + public void prepare(CryptoFileSystem fs) throws IOException, FuseNotSupportedException { + this.cfs = fs; + String mountPath; + if (SystemUtils.IS_OS_WINDOWS) { + //windows case + if (vaultSettings.winDriveLetter().get() != null) { + // specific drive letter selected + mountPath = vaultSettings.winDriveLetter().get() + ":\\"; + } else { + // auto assign drive letter + mountPath = windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\"; + } + } else if (vaultSettings.usesIndividualMountPath().get()) { + //specific path given + mountPath = vaultSettings.individualMountPath().get(); + } else { + //choose default path & create extra directory + mountPath = createDirIfNotExist(SystemUtils.IS_OS_MAC ? DEFAULT_MOUNTROOTPATH_MAC : DEFAULT_MOUNTROOTPATH_LINUX, vaultSettings.mountName().get()); + extraDirCreated = true; + } + this.mountPath = Paths.get(mountPath).toAbsolutePath(); + } + + private String createDirIfNotExist(String prefix, String dirName) throws IOException { + Path p = Paths.get(prefix, dirName + vaultSettings.getId()); + if (Files.isDirectory(p) && !Files.newDirectoryStream(p).iterator().hasNext()) { + throw new DirectoryNotEmptyException("Mount point is not empty."); + } else { + Files.createDirectory(p); + return p.toString(); + } + } + + @Override + public void mount() throws CommandFailedException { + try { + EnvironmentVariables envVars = EnvironmentVariables.create() + .withMountName(vaultSettings.mountName().getValue()) + .withMountPath(mountPath) + .build(); + this.fuseMnt = FuseMountFactory.getMounter().mount(cfs.getPath("/"), envVars); + } catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) { + throw new CommandFailedException("Unable to mount Filesystem", e); + } + } + + @Override + public void reveal() throws CommandFailedException { + try { + fuseMnt.revealInFileManager(); + } catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) { + LOG.info("Revealing the vault in file manger failed: " + e.getMessage()); + throw new CommandFailedException(e); + } + } + + @Override + public synchronized void unmount() throws CommandFailedException { + try { + fuseMnt.close(); + } catch (org.cryptomator.frontend.fuse.mount.CommandFailedException e) { + throw new CommandFailedException(e); + } + } + + @Override + public synchronized void unmountForced() throws CommandFailedException { + unmount(); + } + + @Override + public void stop() { + if (extraDirCreated) { + try { + Files.delete(mountPath); + } catch (IOException e) { + LOG.warn("Could not delete mounting directory:" + e.getMessage()); + } + } + } + + @Override + public String getMountUri() { + return ""; + } + + @Override + public boolean isSupported() { + return FuseMountFactory.isFuseSupported(); + } + + @Override + public boolean supportsForcedUnmount() { + return false; + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java index 2b57d14ab..f1040a56e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java @@ -9,25 +9,18 @@ package org.cryptomator.ui.model; import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.nio.file.FileAlreadyExistsException; -import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; import java.util.function.Predicate; import javax.inject.Inject; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.LazyInitializer; -import org.cryptomator.common.Optionals; import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.cryptofs.CryptoFileSystem; @@ -35,17 +28,7 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties; import org.cryptomator.cryptofs.CryptoFileSystemProvider; import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.InvalidPassphraseException; -import org.cryptomator.frontend.webdav.ServerLifecycleException; -import org.cryptomator.frontend.webdav.WebDavServer; -import org.cryptomator.frontend.webdav.mount.MountParams; -import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException; -import org.cryptomator.frontend.webdav.mount.Mounter.Mount; -import org.cryptomator.frontend.webdav.mount.Mounter.UnmountOperation; -import org.cryptomator.frontend.webdav.servlet.WebDavServletController; import org.cryptomator.ui.model.VaultModule.PerVault; -import org.fxmisc.easybind.EasyBind; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javafx.application.Platform; import javafx.beans.Observable; @@ -53,6 +36,12 @@ import javafx.beans.binding.Binding; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.StringProperty; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.SystemUtils; +import org.fxmisc.easybind.EasyBind; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @PerVault public class Vault { @@ -64,22 +53,20 @@ public class Vault { private final Settings settings; private final VaultSettings vaultSettings; - private final WebDavServer server; private final AtomicReference cryptoFileSystem = new AtomicReference<>(); private final ObjectProperty state = new SimpleObjectProperty(State.LOCKED); - private WebDavServletController servlet; - private Mount mount; + private Volume volume; public enum State { LOCKED, UNLOCKED, MOUNTING, MOUNTED, UNMOUNTING - }; + } @Inject - Vault(Settings settings, VaultSettings vaultSettings, WebDavServer server) { + Vault(Settings settings, VaultSettings vaultSettings, Volume volume) { this.settings = settings; this.vaultSettings = vaultSettings; - this.server = server; + this.volume = volume; } // ****************************************************************************** @@ -111,80 +98,48 @@ public class Vault { CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase); } - public synchronized void unlock(CharSequence passphrase) throws ServerLifecycleException, CryptoException, IOException { - FileSystem fs = getCryptoFileSystem(passphrase); - if (!server.isRunning()) { - server.start(); - } - servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get()); - servlet.start(); + public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException { + CryptoFileSystem fs = getCryptoFileSystem(passphrase); + volume.prepare(fs); Platform.runLater(() -> { state.set(State.UNLOCKED); }); } public synchronized void mount() throws CommandFailedException { - if (servlet == null) { - throw new IllegalStateException("Mounting requires unlocked WebDAV servlet."); - } - - MountParams mountParams = MountParams.create() // - .withWindowsDriveLetter(vaultSettings.winDriveLetter().get()) // - .withPreferredGvfsScheme(settings.preferredGvfsScheme().get()) // - .withWebdavHostname(getLocalhostAliasOrNull()) // - .build(); - Platform.runLater(() -> { state.set(State.MOUNTING); }); - mount = servlet.mount(mountParams); // might block this thread for a while + volume.mount(); Platform.runLater(() -> { state.set(State.MOUNTED); }); } - private String getLocalhostAliasOrNull() { - try { - InetAddress alias = InetAddress.getByName(LOCALHOST_ALIAS); - if (alias.getHostAddress().equals("127.0.0.1")) { - return LOCALHOST_ALIAS; - } else { - return null; - } - } catch (UnknownHostException e) { - return null; - } + public synchronized void unmountForced() throws CommandFailedException { + unmount(true); } public synchronized void unmount() throws CommandFailedException { - unmount(Function.identity()); + unmount(false); } - public synchronized void unmountForced() throws CommandFailedException { - unmount(Optionals.unwrap(Mount::forced)); - } - - private synchronized void unmount(Function unmountOperationChooser) throws CommandFailedException { + private synchronized void unmount(boolean forced) throws CommandFailedException { Platform.runLater(() -> { state.set(State.UNMOUNTING); }); - if (mount != null) { - unmountOperationChooser.apply(mount).unmount(); - mount = null; + if (forced && volume.supportsForcedUnmount()) { + volume.unmountForced(); + } else { + volume.unmount(); } Platform.runLater(() -> { state.set(State.UNLOCKED); }); } - public boolean supportsForcedUnmount() { - return mount != null && mount.forced().isPresent(); - } - - public synchronized void lock() throws ServerLifecycleException, IOException { - if (servlet != null) { - servlet.stop(); - } + public synchronized void lock() throws IOException { + volume.stop(); CryptoFileSystem fs = cryptoFileSystem.getAndSet(null); if (fs != null) { fs.close(); @@ -201,7 +156,7 @@ public class Vault { try { unmount(); } catch (CommandFailedException e) { - if (supportsForcedUnmount()) { + if (volume.supportsForcedUnmount()) { try { unmountForced(); } catch (CommandFailedException e1) { @@ -219,9 +174,7 @@ public class Vault { } public void reveal() throws CommandFailedException { - if (mount != null) { - mount.reveal(); - } + volume.reveal(); } // ****************************************************************************** @@ -243,17 +196,13 @@ public class Vault { } public Observable[] observables() { - return new Observable[] {state}; + return new Observable[]{state}; } public VaultSettings getVaultSettings() { return vaultSettings; } - public synchronized String getWebDavUrl() { - return servlet.getServletRootUri().toString(); - } - public Path getPath() { return vaultSettings.path().getValue(); } @@ -308,6 +257,14 @@ public class Vault { return vaultSettings.mountName().get(); } + public StringProperty getMountPathProperty() { + return vaultSettings.individualMountPath(); + } + + public void setMountPath(String mountPath) { + vaultSettings.individualMountPath().set(mountPath); + } + public void setMountName(String mountName) throws IllegalArgumentException { if (StringUtils.isBlank(mountName)) { throw new IllegalArgumentException("mount name is empty"); @@ -332,6 +289,10 @@ public class Vault { } } + public String getFilesystemRootUrl() { + return volume.getMountUri(); + } + public String getId() { return vaultSettings.getId(); } @@ -355,4 +316,7 @@ public class Vault { } } + public boolean supportsForcedUnmount() { + return volume.supportsForcedUnmount(); + } } \ No newline at end of file diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java index f2c9718fc..5ce2685fa 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 @@ -38,6 +38,11 @@ public class VaultList extends TransformationList { return index; } + @Override + public int getViewIndex(int index) { + return index; + } + @Override public Vault get(int index) { VaultSettings s = source.get(index); diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java index c8a622233..93c130522 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java @@ -12,6 +12,8 @@ import java.util.Objects; import javax.inject.Scope; +import org.cryptomator.common.settings.VolumeImpl; +import org.cryptomator.common.settings.Settings; import org.cryptomator.common.settings.VaultSettings; import dagger.Module; @@ -36,6 +38,26 @@ public class VaultModule { @Documented @Retention(RetentionPolicy.RUNTIME) @interface PerVault { + + } + + @Provides + @PerVault + public Volume provideNioAdpater(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume) { + VolumeImpl impl = settings.volumeImpl().get(); + switch (impl) { + case FUSE: + if (fuseVolume.isSupported()) { + return fuseVolume; + } else { + settings.volumeImpl().set(VolumeImpl.WEBDAV); + // fallthrough to WEBDAV + } + case WEBDAV: + return webDavVolume; + default: + throw new IllegalStateException("Unsupported NioAdapter: " + impl); + } } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java b/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java new file mode 100644 index 000000000..e1bdbb6fc --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Volume.java @@ -0,0 +1,38 @@ +package org.cryptomator.ui.model; + +import org.cryptomator.cryptofs.CryptoFileSystem; + +import java.io.IOException; + +/** + * Takes a Volume and usess it to mount an unlocked vault + */ +public interface Volume { + + void prepare(CryptoFileSystem fs) throws IOException; + + void mount() throws CommandFailedException; + + default void reveal() throws CommandFailedException { + throw new CommandFailedException("Not implemented."); + } + + void unmount() throws CommandFailedException; + + default void unmountForced() throws CommandFailedException { + throw new CommandFailedException("Operation not supported."); + } + + void stop(); + + String getMountUri(); + + default boolean isSupported() { + return false; + } + + default boolean supportsForcedUnmount() { + return false; + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java b/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java new file mode 100644 index 000000000..037c77a49 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java @@ -0,0 +1,130 @@ +package org.cryptomator.ui.model; + + +import org.cryptomator.common.settings.Settings; +import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.cryptofs.CryptoFileSystem; +import org.cryptomator.frontend.webdav.WebDavServer; +import org.cryptomator.frontend.webdav.mount.MountParams; +import org.cryptomator.frontend.webdav.mount.Mounter; +import org.cryptomator.frontend.webdav.servlet.WebDavServletController; + +import javax.inject.Inject; +import javax.inject.Provider; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +@VaultModule.PerVault +public class WebDavVolume implements Volume { + + private static final String LOCALHOST_ALIAS = "cryptomator-vault"; + + private final Provider serverProvider; + private final VaultSettings vaultSettings; + private final Settings settings; + + private WebDavServer server; + private WebDavServletController servlet; + private Mounter.Mount mount; + + @Inject + public WebDavVolume(Provider serverProvider, VaultSettings vaultSettings, Settings settings) { + this.serverProvider = serverProvider; + this.vaultSettings = vaultSettings; + this.settings = settings; + } + + @Override + public void prepare(CryptoFileSystem fs) { + if (server == null) { + server = serverProvider.get(); + } + if (!server.isRunning()) { + server.start(); + } + servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get()); + servlet.start(); + } + + @Override + public void mount() throws CommandFailedException { + if (servlet == null) { + throw new IllegalStateException("Mounting requires unlocked WebDAV servlet."); + } + MountParams mountParams = MountParams.create() // + .withWindowsDriveLetter(vaultSettings.winDriveLetter().get()) // + .withPreferredGvfsScheme(settings.preferredGvfsScheme().get())// + .withWebdavHostname(getLocalhostAliasOrNull()) // + .build(); + try { + this.mount = servlet.mount(mountParams); // might block this thread for a while + } catch (Mounter.CommandFailedException e) { + e.printStackTrace(); + throw new CommandFailedException(e); + } + } + + @Override + public void reveal() throws CommandFailedException { + try { + mount.reveal(); + } catch (Mounter.CommandFailedException e) { + e.printStackTrace(); + throw new CommandFailedException(e); + } + } + + @Override + public synchronized void unmount() throws CommandFailedException { + try { + mount.unmount(); + } catch (Mounter.CommandFailedException e) { + throw new CommandFailedException(e); + } + } + + @Override + public synchronized void unmountForced() { + mount.forced(); + } + + private String getLocalhostAliasOrNull() { + try { + InetAddress alias = InetAddress.getByName(LOCALHOST_ALIAS); + if (alias.getHostAddress().equals("127.0.0.1")) { + return LOCALHOST_ALIAS; + } else { + return null; + } + } catch (UnknownHostException e) { + return null; + } + } + + @Override + public void stop() { + if (servlet != null) { + servlet.stop(); + } + + } + + public synchronized String getMountUri() { + return servlet.getServletRootUri().toString() + "/"; + } + + /** + * TODO: what to check wether it is implemented? + * + * @return + */ + @Override + public boolean isSupported() { + return true; + } + + public boolean supportsForcedUnmount() { + return mount != null && mount.forced().isPresent(); + } +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java b/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java deleted file mode 100644 index 3c524e418..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java +++ /dev/null @@ -1,127 +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.ui.util; - -import java.io.File; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Function; - -import org.apache.commons.lang3.SystemUtils; -import org.cryptomator.common.SupplierThrowingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Reflection-based wrapper for com.apple.eawt.Application. - */ -public class EawtApplicationWrapper { - - private static final Logger LOG = LoggerFactory.getLogger(EawtApplicationWrapper.class); - - private final Class applicationClass; - private final Object application; - - private EawtApplicationWrapper() throws ReflectiveOperationException { - this.applicationClass = Class.forName("com.apple.eawt.Application"); - this.application = applicationClass.getMethod("getApplication").invoke(null); - } - - /** - * @return A wrapper for com.apple.ewat.Application if the current OS is macOS and the class is available in this JVM. - */ - public static Optional getApplication() { - if (!SystemUtils.IS_OS_MAC_OSX) { - return Optional.empty(); - } - try { - return Optional.of(new EawtApplicationWrapper()); - } catch (ReflectiveOperationException e) { - return Optional.empty(); - } - } - - private void setOpenFileHandler(InvocationHandler handler) throws ReflectiveOperationException { - Class handlerClass = Class.forName("com.apple.eawt.OpenFilesHandler"); - Method setter = applicationClass.getMethod("setOpenFileHandler", handlerClass); - Object proxy = Proxy.newProxyInstance(applicationClass.getClassLoader(), new Class[] {handlerClass}, handler); - setter.invoke(application, proxy); - } - - public void setOpenFileHandler(Consumer> handler) { - try { - Class openFilesEventClass = Class.forName("com.apple.eawt.AppEvent$OpenFilesEvent"); - Method getFiles = openFilesEventClass.getMethod("getFiles"); - setOpenFileHandler(methodSpecificInvocationHandler("openFiles", args -> { - Object openFilesEvent = args[0]; - assert openFilesEventClass.isInstance(openFilesEvent); - @SuppressWarnings("unchecked") - List files = (List) uncheckedReflectiveOperation(() -> getFiles.invoke(openFilesEvent)); - handler.accept(files); - return null; - })); - } catch (ReflectiveOperationException e) { - LOG.error("Exception setting openFileHandler.", e); - } - } - - private void setPreferencesHandler(InvocationHandler handler) throws ReflectiveOperationException { - Class handlerClass = Class.forName("com.apple.eawt.PreferencesHandler"); - Method setter = applicationClass.getMethod("setPreferencesHandler", handlerClass); - Object proxy = Proxy.newProxyInstance(applicationClass.getClassLoader(), new Class[] {handlerClass}, handler); - setter.invoke(application, proxy); - } - - public void setPreferencesHandler(Runnable handler) { - try { - setPreferencesHandler(methodSpecificInvocationHandler("handlePreferences", args -> { - handler.run(); - return null; - })); - } catch (ReflectiveOperationException e) { - LOG.error("Exception setting preferencesHandler.", e); - } - } - - private static InvocationHandler methodSpecificInvocationHandler(String methodName, Function handler) { - return new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (method.getName().equals(methodName)) { - return handler.apply(args); - } else { - throw new UnsupportedOperationException("Unexpected invocation " + method.getName() + ", expected " + methodName); - } - } - }; - } - - /** - * Wraps {@link ReflectiveOperationException}s as {@link UncheckedReflectiveOperationException}. - * - * @param operation Invokation throwing an ReflectiveOperationException - * @return Result returned by operation - * @throws UncheckedReflectiveOperationException in case operation throws an ReflectiveOperationException. - */ - private static T uncheckedReflectiveOperation(SupplierThrowingException operation) throws UncheckedReflectiveOperationException { - try { - return operation.get(); - } catch (ReflectiveOperationException e) { - throw new UncheckedReflectiveOperationException(e); - } - } - - private static class UncheckedReflectiveOperationException extends RuntimeException { - public UncheckedReflectiveOperationException(ReflectiveOperationException cause) { - super(cause); - } - } - -} diff --git a/main/ui/src/main/resources/css/mac_theme.css b/main/ui/src/main/resources/css/mac_theme.css index eed57d17b..8d7004f37 100644 --- a/main/ui/src/main/resources/css/mac_theme.css +++ b/main/ui/src/main/resources/css/mac_theme.css @@ -434,6 +434,52 @@ -fx-background-color: COLOR_TEXT_DISABLED; } +/******************************************************************************* + * * + * ChoiceBox * + * * + ******************************************************************************/ + +.choice-box { + -fx-background-color: COLOR_HGRAD_BTN_BORDER, #FFFFFF; + -fx-background-insets: 0, 1; + -fx-background-radius: 4; + -fx-padding: 0 0 0 0.8em; + -fx-text-fill: COLOR_TEXT; + -fx-alignment: CENTER; + -fx-effect: dropshadow(one-pass-box, #DCDCDC, 0.0, 0.0, 0.0, 1.0); +} + +.choice-box > .open-button { + -fx-background-insets: 0, 1 1 1 0; + -fx-background-radius: 0 4 4 0; + -fx-padding: 4 5 4 4; +} +.root.active-window .choice-box > .open-button { + -fx-background-color: COLOR_HGRAD_BTN_DEF_BORDER, COLOR_HGRAD_BTN_DEF_BACKGROUND; +} +.root.inactive-window .choice-box > .open-button { + -fx-background-color: COLOR_HGRAD_BTN_BORDER, #FFFFFF; +} + +.choice-box > .open-button > .arrow { + -fx-shape: "M 0 5 L 4 0 L 8 5 L 6 5 L 4 2 L 2 5 Z M 0 8 L 4 13 L 8 8 L 6 8 L 4 11 L 2 8 Z"; + -fx-scale-shape: true; + -fx-min-width: 9px; + -fx-min-height: 13px; +} +.root.active-window .choice-box > .open-button > .arrow { + -fx-background-color: #FFFFFF; +} +.root.inactive-window .choice-box > .open-button > .arrow { + -fx-background-color: #444444; +} + +.choice-box .context-menu { + -fx-background-color: COLOR_BORDER, #FFF; + -fx-background-insets: 0, 1; +} + /**************************************************************************** * * * ProgressIndicator * diff --git a/main/ui/src/main/resources/fxml/settings.fxml b/main/ui/src/main/resources/fxml/settings.fxml index d3f9c7c12..794181cf5 100644 --- a/main/ui/src/main/resources/fxml/settings.fxml +++ b/main/ui/src/main/resources/fxml/settings.fxml @@ -35,22 +35,32 @@