mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 16:51:28 +00:00
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cc0b0728d | ||
|
|
7e7d5e46d3 | ||
|
|
5625525b21 | ||
|
|
b8fa226163 | ||
|
|
2b44f0ee9f | ||
|
|
b1ea21809b | ||
|
|
8def0b2060 | ||
|
|
2f61964758 | ||
|
|
2668299131 | ||
|
|
a7de849800 | ||
|
|
c66a8d0cfe | ||
|
|
efa7f78ffd | ||
|
|
57c858351d | ||
|
|
c3e48934b2 | ||
|
|
06b8c7cdf4 | ||
|
|
cbf677a51c | ||
|
|
923e58ba18 | ||
|
|
65c12d7ae1 | ||
|
|
8e324ef0eb | ||
|
|
29a0336bf4 | ||
|
|
44fc6761e3 | ||
|
|
a4ef082bc4 | ||
|
|
1272279b96 | ||
|
|
6aafa7bb5c | ||
|
|
43f2110f68 | ||
|
|
0b8f8e53af | ||
|
|
336d67195d | ||
|
|
6677079623 | ||
|
|
0974a57671 | ||
|
|
ab77673fed | ||
|
|
a70401596f | ||
|
|
ab198271a1 | ||
|
|
d62edcda73 | ||
|
|
8cba58075d | ||
|
|
426f36ce04 | ||
|
|
dd3c969f0f | ||
|
|
6a270ceccd | ||
|
|
a3474e05eb | ||
|
|
dd190b5a16 | ||
|
|
fe722629be | ||
|
|
f8e5d8aefb | ||
|
|
3e3a4ceefc | ||
|
|
a09edad165 | ||
|
|
129e9c63f8 | ||
|
|
9af58b8e6e | ||
|
|
1048ff5728 | ||
|
|
6adb591c9a | ||
|
|
d06720838e | ||
|
|
cf020e5b96 | ||
|
|
4bfd1e6433 | ||
|
|
deded33da8 | ||
|
|
be5fce0ee9 | ||
|
|
39f9da16f9 | ||
|
|
53b0d5cb9f | ||
|
|
b9a120b51b | ||
|
|
debcab47e2 | ||
|
|
8814372c68 | ||
|
|
98e5c3ff88 | ||
|
|
f1c332f455 | ||
|
|
79306ea498 | ||
|
|
d7dda7d249 | ||
|
|
1e80f4bba4 | ||
|
|
ffd3981f36 | ||
|
|
aa23635744 | ||
|
|
0317e7c21d | ||
|
|
ec5e8bba30 | ||
|
|
0caa9988d3 | ||
|
|
f16c3d5110 | ||
|
|
e1930505d1 | ||
|
|
757549919c | ||
|
|
0257802bb0 | ||
|
|
5cb4b403cd | ||
|
|
8831df9242 | ||
|
|
2229a56831 | ||
|
|
c3370a8388 | ||
|
|
1175a114ec | ||
|
|
3374dbf9a5 | ||
|
|
26aee9e42c | ||
|
|
ab82874013 | ||
|
|
39f1da105e | ||
|
|
86d8599d07 | ||
|
|
744225cf7a | ||
|
|
54f2667e45 |
17
.idea/compiler.xml
generated
17
.idea/compiler.xml
generated
@@ -7,27 +7,30 @@
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.20/dagger-compiler-2.20.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.20/dagger-2.20.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.22.1/dagger-compiler-2.22.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.22.1/dagger-2.22.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.20/dagger-producers-2.20.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.22.1/dagger-producers-2.22.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/25.0-jre/guava-25.0-jre.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.3/checker-compat-qual-2.5.3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.1.3/error_prone_annotations-2.1.3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/codehaus/mojo/animal-sniffer-annotations/1.14/animal-sniffer-annotations-1.14.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.20/dagger-spi-2.20.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.22.1/dagger-spi-2.22.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.11.1/javapoet-1.11.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.11.1/javapoet-1.11.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar" />
|
||||
</processorPath>
|
||||
<module name="commons" />
|
||||
<module name="keychain" />
|
||||
<module name="launcher" />
|
||||
<module name="commons" />
|
||||
<module name="ui" />
|
||||
<module name="launcher" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel>
|
||||
<module name="buildkit" target="11" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
||||
3
.idea/encodings.xml
generated
3
.idea/encodings.xml
generated
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<component name="Encoding" addBOMForNewFiles="with NO BOM">
|
||||
<file url="file://$PROJECT_DIR$/main" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/buildkit" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/commons" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/keychain" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/launcher" charset="UTF-8" />
|
||||
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -8,7 +8,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_10" project-jdk-name="10" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
9
.idea/modules.xml
generated
Normal file
9
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Desktop.iml" filepath="$PROJECT_DIR$/.idea/Desktop.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/main/buildkit/buildkit.iml" filepath="$PROJECT_DIR$/main/buildkit/buildkit.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/runConfigurations/Cryptomator_Linux.xml
generated
Normal file
10
.idea/runConfigurations/Cryptomator_Linux.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application">
|
||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||
<module name="launcher" />
|
||||
<option name="VM_PARAMETERS" value="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/.config/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/.local/share/Cryptomator/logs" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" -Xss20m -Xmx512m" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
10
.idea/runConfigurations/Cryptomator_Windows.xml
generated
Normal file
10
.idea/runConfigurations/Cryptomator_Windows.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
|
||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||
<module name="launcher" />
|
||||
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" -Xss2m -Xmx512m" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
11
.idea/runConfigurations/Cryptomator_macOS.xml
generated
Normal file
11
.idea/runConfigurations/Cryptomator_macOS.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Cryptomator macOS" type="Application" factoryName="Application">
|
||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||
<module name="launcher" />
|
||||
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/Library/Application Support/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/Library/Logs/Cryptomator" -Dcryptomator.mountPointsDir="/Volumes/" -Xss2m -Xmx512m" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
19
.travis-deploy-release.tmpl.json
Normal file
19
.travis-deploy-release.tmpl.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"package": {
|
||||
"name": "buildkit",
|
||||
"repo": "cryptomator",
|
||||
"subject": "cryptomator"
|
||||
},
|
||||
"version": {
|
||||
"name": "$TRAVIS_TAG",
|
||||
"desc": "Cryptomator version $TRAVIS_TAG",
|
||||
"released": "$TODAY",
|
||||
"vcs_tag": "$TRAVIS_TAG",
|
||||
"gpgSign": true
|
||||
},
|
||||
"files":
|
||||
[
|
||||
{"includePattern": "main/buildkit/target/(buildkit-[a-z]+\\.zip)", "uploadPattern": "/$TRAVIS_TAG/$1"}
|
||||
],
|
||||
"publish": true
|
||||
}
|
||||
15
.travis-deploy-snapshot.json
Normal file
15
.travis-deploy-snapshot.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"package": {
|
||||
"name": "buildkit",
|
||||
"repo": "cryptomator",
|
||||
"subject": "cryptomator"
|
||||
},
|
||||
"version": {
|
||||
"name": "snapshot"
|
||||
},
|
||||
"files":
|
||||
[
|
||||
{"includePattern": "main/buildkit/target/(buildkit-[a-z]+\\.zip)", "uploadPattern": "/snapshot/$1", "matrixParams": {"override": 1}}
|
||||
],
|
||||
"publish": true
|
||||
}
|
||||
41
.travis.yml
41
.travis.yml
@@ -1,7 +1,7 @@
|
||||
language: java
|
||||
sudo: false
|
||||
jdk:
|
||||
- oraclejdk9
|
||||
- openjdk11
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
@@ -34,40 +34,21 @@ before_deploy:
|
||||
mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=SNAPSHOT-$(echo $TRAVIS_COMMIT | head -c7)
|
||||
fi
|
||||
- mvn -fmain/pom.xml clean package -Prelease -DskipTests
|
||||
- export TODAY=`date +'%Y-%m-%d'`; envsubst '$TRAVIS_TAG $TODAY' < .travis-deploy-release.tmpl.json > .travis-deploy-release.json
|
||||
deploy:
|
||||
- provider: script # SNAPSHOTS
|
||||
- provider: bintray # SNAPSHOTS
|
||||
file: .travis-deploy-snapshot.json
|
||||
user: cryptobot
|
||||
key: $BINTRAY_API_KEY
|
||||
skip_cleanup: true
|
||||
script: >-
|
||||
curl -T main/ant-kit/target/antkit.zip
|
||||
-u cryptobot:${BINTRAY_API_KEY}
|
||||
-H "X-Bintray-Package:ant-kit"
|
||||
-H "X-Bintray-Version:continuous"
|
||||
-H "X-Bintray-Override:1"
|
||||
-H "X-Bintray-Publish:1"
|
||||
https://api.bintray.com/content/cryptomator/cryptomator/antkit-continuous.zip
|
||||
on:
|
||||
repo: cryptomator/cryptomator
|
||||
branch: develop
|
||||
condition: $TRAVIS_TAG = ''
|
||||
- provider: releases # RELEASE
|
||||
prerelease: false
|
||||
api_key: $GITHUB_API_KEY
|
||||
file:
|
||||
- "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar"
|
||||
- provider: bintray # RELEASES
|
||||
file: .travis-deploy-release.json
|
||||
user: cryptobot
|
||||
key: $BINTRAY_API_KEY
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: cryptomator/cryptomator
|
||||
tags: true
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: >-
|
||||
curl -T main/ant-kit/target/antkit.zip
|
||||
-u cryptobot:${BINTRAY_API_KEY}
|
||||
-H "X-Bintray-Package:ant-kit"
|
||||
-H "X-Bintray-Version:${TRAVIS_TAG}"
|
||||
-H "X-Bintray-Override:1"
|
||||
-H "X-Bintray-Publish:1"
|
||||
https://api.bintray.com/content/cryptomator/cryptomator/antkit-${TRAVIS_TAG}.zip
|
||||
on:
|
||||
repo: cryptomator/cryptomator
|
||||
tags: true
|
||||
tags: true
|
||||
@@ -1,99 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.3</version>
|
||||
</parent>
|
||||
<artifactId>ant-kit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator Ant Build Kit</name>
|
||||
<description>Builds a package that can be built with Ant locally</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>launcher</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- copy libraries to target/libs/: -->
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- copy resources to target/: -->
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||
<escapeString>\</escapeString>
|
||||
<encoding>UTF-8</encoding>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>build.xml</include>
|
||||
</includes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
<includes>
|
||||
<include>logback.xml</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- create antkit.zip: -->
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>antkit</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="Cryptomator" default="create-jar" basedir="." xmlns:fx="javafx:com.sun.javafx.tools.ant">
|
||||
<taskdef uri="javafx:com.sun.javafx.tools.ant" resource="com/sun/javafx/tools/ant/antlib.xml" classpath="\${java.class.path}:\${java.home}/lib/ant-javafx.jar:." />
|
||||
|
||||
<!-- Define application to build -->
|
||||
<fx:application id="Cryptomator" name="Cryptomator" version="${project.version}" mainClass="org.cryptomator.launcher.Cryptomator" />
|
||||
|
||||
<!-- Print build environment properties -->
|
||||
<target name="check-env">
|
||||
<echoproperties/>
|
||||
</target>
|
||||
|
||||
<!-- Create main application jar -->
|
||||
<target name="create-jar" depends="check-env">
|
||||
<fx:jar destfile="antbuild/Cryptomator-${project.version}.jar">
|
||||
<fx:application refid="Cryptomator" />
|
||||
<fx:fileset dir="libs" includes="launcher-${project.version}.jar" />
|
||||
<fx:resources>
|
||||
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar" />
|
||||
</fx:resources>
|
||||
<fx:manifest>
|
||||
<fx:attribute name="Implementation-Vendor" value="cryptomator.org" />
|
||||
<fx:attribute name="Implementation-Title" value="Cryptomator"/>
|
||||
<fx:attribute name="Implementation-Version" value="${project.version}" />
|
||||
</fx:manifest>
|
||||
</fx:jar>
|
||||
</target>
|
||||
|
||||
<!-- Create Image -->
|
||||
<target name="image" depends="create-jar">
|
||||
<fx:deploy nativeBundles="image" outdir="antbuild" verbose="true">
|
||||
<fx:application refid="Cryptomator" />
|
||||
<fx:info title="Cryptomator" vendor="cryptomator.org" copyright="cryptomator.org" license="GPL" category="Utility"/>
|
||||
<fx:platform j2se="10">
|
||||
<fx:property name="logback.configurationFile" value="\${antbuild.logback.configurationFile}" />
|
||||
<fx:property name="cryptomator.settingsPath" value="\${antbuild.cryptomator.settingsPath}" />
|
||||
<fx:property name="cryptomator.ipcPortPath" value="\${antbuild.cryptomator.ipcPortPath}" />
|
||||
<fx:property name="cryptomator.keychainPath" value="\${antbuild.cryptomator.keychainPath}"/>
|
||||
<fx:jvmarg value="-Xss2m"/>
|
||||
<fx:jvmarg value="-Xmx512m"/>
|
||||
</fx:platform>
|
||||
<fx:resources>
|
||||
<fx:fileset dir="antbuild" type="jar" includes="Cryptomator-${project.version}.jar" />
|
||||
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar"/>
|
||||
</fx:resources>
|
||||
<fx:permissions elevated="false" />
|
||||
<fx:preferences install="true" />
|
||||
<fx:bundleArgument arg="dropinResourcesRoot" value="\${antbuild.dropinResourcesRoot}"/>
|
||||
</fx:deploy>
|
||||
</target>
|
||||
|
||||
</project>
|
||||
@@ -7,6 +7,13 @@
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
<include>version.txt</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/libs</directory>
|
||||
<includes>
|
||||
@@ -15,24 +22,11 @@
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/fixed-binaries</directory>
|
||||
<filtered>false</filtered>
|
||||
<outputDirectory>fixed-binaries</outputDirectory>
|
||||
<fileMode>755</fileMode>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/package</directory>
|
||||
<filtered>false</filtered>
|
||||
<outputDirectory>package</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target</directory>
|
||||
<directory>target/linux-libs</directory>
|
||||
<includes>
|
||||
<include>build.xml</include>
|
||||
<include>logback.xml</include>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<filtered>false</filtered>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
32
main/buildkit/assembly-mac.xml
Normal file
32
main/buildkit/assembly-mac.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>tarball</id>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
<include>version.txt</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/mac-libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
32
main/buildkit/assembly-win.xml
Normal file
32
main/buildkit/assembly-win.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>tarball</id>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
<include>version.txt</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/win-libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
157
main/buildkit/pom.xml
Normal file
157
main/buildkit/pom.xml
Normal file
@@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.8</version>
|
||||
</parent>
|
||||
<artifactId>buildkit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator Build Kit</name>
|
||||
<description>Builds a package that can be built with Ant locally</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>launcher</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- generate version.txt -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
<includes>
|
||||
<include>version.txt</include>
|
||||
</includes>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- copy libraries to target/libs/: -->
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||
<excludeClassifiers>linux,mac,win</excludeClassifiers>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy-linux-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/linux-libs</outputDirectory>
|
||||
<includeGroupIds>org.openjfx</includeGroupIds>
|
||||
<classifier>linux</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy-mac-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/mac-libs</outputDirectory>
|
||||
<includeGroupIds>org.openjfx</includeGroupIds>
|
||||
<classifier>mac</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy-win-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/win-libs</outputDirectory>
|
||||
<includeGroupIds>org.openjfx</includeGroupIds>
|
||||
<classifier>win</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- create buildkit.zip: -->
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>assemble-linux</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly-linux.xml</descriptor>
|
||||
</descriptors>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>buildkit-linux</finalName>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>assemble-mac</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly-mac.xml</descriptor>
|
||||
</descriptors>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>buildkit-mac</finalName>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>assemble-win</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly-win.xml</descriptor>
|
||||
</descriptors>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>buildkit-win</finalName>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
1
main/buildkit/src/main/resources/version.txt
Normal file
1
main/buildkit/src/main/resources/version.txt
Normal file
@@ -0,0 +1 @@
|
||||
${project.version}
|
||||
@@ -4,13 +4,19 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.3</version>
|
||||
<version>1.4.8</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator Commons</name>
|
||||
<description>Shared utilities</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- JavaFx -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-base</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Libs -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@Singleton
|
||||
public class Environment {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Environment.class);
|
||||
private static final String USER_HOME = System.getProperty("user.home");
|
||||
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
|
||||
private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME);
|
||||
private static final char PATH_LIST_SEP = ':';
|
||||
|
||||
@Inject
|
||||
public Environment() {
|
||||
LOG.debug("user.language: {}", System.getProperty("user.language"));
|
||||
LOG.debug("user.region: {}", System.getProperty("user.region"));
|
||||
LOG.debug("logback.configurationFile: {}", System.getProperty("logback.configurationFile"));
|
||||
LOG.debug("cryptomator.settingsPath: {}", System.getProperty("cryptomator.settingsPath"));
|
||||
LOG.debug("cryptomator.ipcPortPath: {}", System.getProperty("cryptomator.ipcPortPath"));
|
||||
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
|
||||
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
|
||||
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
||||
}
|
||||
|
||||
public boolean useCustomLogbackConfig() {
|
||||
return getPath("logback.configurationFile").map(Files::exists).orElse(false);
|
||||
}
|
||||
|
||||
public Stream<Path> getSettingsPath() {
|
||||
return getPaths("cryptomator.settingsPath");
|
||||
}
|
||||
|
||||
public Stream<Path> getIpcPortPath() {
|
||||
return getPaths("cryptomator.ipcPortPath");
|
||||
}
|
||||
|
||||
public Stream<Path> getKeychainPath() {
|
||||
return getPaths("cryptomator.keychainPath");
|
||||
}
|
||||
|
||||
public Optional<Path> getLogDir() {
|
||||
return getPath("cryptomator.logDir").map(this::replaceHomeDir);
|
||||
}
|
||||
|
||||
public Optional<Path> getMountPointsDir() {
|
||||
return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir);
|
||||
}
|
||||
|
||||
private Optional<Path> getPath(String propertyName) {
|
||||
String value = System.getProperty(propertyName);
|
||||
return Optional.ofNullable(value).map(Paths::get);
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
Stream<Path> getPaths(String propertyName) {
|
||||
Stream<String> rawSettingsPaths = getRawList(propertyName, PATH_LIST_SEP);
|
||||
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Paths::get).map(this::replaceHomeDir);
|
||||
}
|
||||
|
||||
private Path replaceHomeDir(Path path) {
|
||||
if (path.startsWith(RELATIVE_HOME_DIR)) {
|
||||
return ABSOLUTE_HOME_DIR.resolve(RELATIVE_HOME_DIR.relativize(path));
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
private Stream<String> getRawList(String propertyName, char separator) {
|
||||
String value = System.getProperty(propertyName);
|
||||
if (value == null) {
|
||||
return Stream.empty();
|
||||
} else {
|
||||
Iterable<String> iter = Splitter.on(separator).split(value);
|
||||
Spliterator<String> spliter = Spliterators.spliteratorUnknownSize(iter.iterator(), Spliterator.ORDERED | Spliterator.IMMUTABLE);
|
||||
return StreamSupport.stream(spliter, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface FxApplicationScoped {
|
||||
|
||||
}
|
||||
@@ -80,6 +80,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
default:
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
@@ -8,6 +8,16 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.LazyInitializer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -16,9 +26,8 @@ import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Optional;
|
||||
@@ -27,110 +36,83 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.LazyInitializer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Singleton
|
||||
public class SettingsProvider implements Provider<Settings> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
|
||||
private static final Path DEFAULT_SETTINGS_PATH;
|
||||
private static final long SAVE_DELAY_MS = 1000;
|
||||
|
||||
static {
|
||||
final FileSystem fs = FileSystems.getDefault();
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, "AppData/Roaming/Cryptomator/settings.json");
|
||||
} else if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, "Library/Application Support/Cryptomator/settings.json");
|
||||
} else {
|
||||
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, ".Cryptomator/settings.json");
|
||||
}
|
||||
}
|
||||
|
||||
private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
|
||||
private final AtomicReference<Settings> settings = new AtomicReference<>();
|
||||
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter();
|
||||
private final Environment env;
|
||||
private final Gson gson;
|
||||
|
||||
@Inject
|
||||
public SettingsProvider() {
|
||||
public SettingsProvider(Environment env) {
|
||||
this.env = env;
|
||||
this.gson = new GsonBuilder() //
|
||||
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
|
||||
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
|
||||
.create();
|
||||
}
|
||||
|
||||
private Path getSettingsPath() {
|
||||
final String settingsPathProperty = System.getProperty("cryptomator.settingsPath");
|
||||
return Optional.ofNullable(settingsPathProperty).filter(StringUtils::isNotBlank).map(this::replaceHomeDir).map(FileSystems.getDefault()::getPath).orElse(DEFAULT_SETTINGS_PATH);
|
||||
}
|
||||
|
||||
private String replaceHomeDir(String path) {
|
||||
if (path.startsWith("~/")) {
|
||||
return SystemUtils.USER_HOME + path.substring(1);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Settings get() {
|
||||
return LazyInitializer.initializeLazily(settings, this::load);
|
||||
}
|
||||
|
||||
private Settings load() {
|
||||
Settings settings;
|
||||
final Path settingsPath = getSettingsPath();
|
||||
try (InputStream in = Files.newInputStream(settingsPath, StandardOpenOption.READ); //
|
||||
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||
settings = gson.fromJson(reader, Settings.class);
|
||||
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElse(new Settings());
|
||||
settings.setSaveCmd(this::scheduleSave);
|
||||
return settings;
|
||||
}
|
||||
|
||||
private Stream<Settings> tryLoad(Path path) {
|
||||
LOG.debug("Attempting to load settings from {}", path);
|
||||
try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ); //
|
||||
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||
Settings settings = gson.fromJson(reader, Settings.class);
|
||||
if (settings == null) {
|
||||
throw new IOException("Unexpected EOF");
|
||||
}
|
||||
LOG.info("Settings loaded from " + settingsPath);
|
||||
LOG.info("Settings loaded from {}", path);
|
||||
return Stream.of(settings);
|
||||
} catch (NoSuchFileException e) {
|
||||
return Stream.empty();
|
||||
} catch (IOException e) {
|
||||
LOG.info("Failed to load settings, creating new one.");
|
||||
settings = new Settings();
|
||||
LOG.warn("Exception while loading settings from " + path, e);
|
||||
return Stream.empty();
|
||||
}
|
||||
settings.setSaveCmd(this::scheduleSave);
|
||||
return settings;
|
||||
}
|
||||
|
||||
private void scheduleSave(Settings settings) {
|
||||
if (settings == null) {
|
||||
return;
|
||||
}
|
||||
ScheduledFuture<?> saveCmd = saveScheduler.schedule(() -> {
|
||||
this.save(settings);
|
||||
}, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
ScheduledFuture<?> previousSaveCmd = scheduledSaveCmd.getAndSet(saveCmd);
|
||||
if (previousSaveCmd != null) {
|
||||
previousSaveCmd.cancel(false);
|
||||
}
|
||||
final Optional<Path> settingsPath = env.getSettingsPath().findFirst(); // alway save to preferred (first) path
|
||||
settingsPath.ifPresent(path -> {
|
||||
Runnable saveCommand = () -> this.save(settings, path);
|
||||
ScheduledFuture<?> scheduledTask = saveScheduler.schedule(saveCommand, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
ScheduledFuture<?> previouslyScheduledTask = scheduledSaveCmd.getAndSet(scheduledTask);
|
||||
if (previouslyScheduledTask != null) {
|
||||
previouslyScheduledTask.cancel(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void save(Settings settings) {
|
||||
private void save(Settings settings, Path settingsPath) {
|
||||
assert settings != null : "method should only be invoked by #scheduleSave, which checks for null";
|
||||
final Path settingsPath = getSettingsPath();
|
||||
LOG.debug("Attempting to save settings to {}", settingsPath);
|
||||
try {
|
||||
Files.createDirectories(settingsPath.getParent());
|
||||
try (OutputStream out = Files.newOutputStream(settingsPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
|
||||
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
|
||||
gson.toJson(settings, writer);
|
||||
LOG.info("Settings saved to " + settingsPath);
|
||||
LOG.info("Settings saved to {}", settingsPath);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to save settings.", e);
|
||||
|
||||
@@ -76,6 +76,7 @@ class VaultSettingsJsonAdapter {
|
||||
default:
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@DisplayName("Environment Variables Test")
|
||||
class EnvironmentTest {
|
||||
|
||||
private Environment env;
|
||||
|
||||
@BeforeAll
|
||||
static void init() {
|
||||
System.setProperty("user.home", "/home/testuser");
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void initEach() {
|
||||
env = new Environment();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("cryptomator.settingsPath=~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json")
|
||||
public void testSettingsPath() {
|
||||
System.setProperty("cryptomator.settingsPath", "~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json");
|
||||
|
||||
List<Path> result = env.getSettingsPath().collect(Collectors.toList());
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"),
|
||||
Paths.get("/home/testuser/.Cryptomator/settings.json")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("cryptomator.ipcPortPath=~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin")
|
||||
public void testIpcPortPath() {
|
||||
System.setProperty("cryptomator.ipcPortPath", "~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin");
|
||||
|
||||
List<Path> result = env.getIpcPortPath().collect(Collectors.toList());
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"),
|
||||
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("cryptomator.keychainPath=~/AppData/Roaming/Cryptomator/keychain.json")
|
||||
public void testKeychainPath() {
|
||||
System.setProperty("cryptomator.keychainPath", "~/AppData/Roaming/Cryptomator/keychain.json");
|
||||
|
||||
List<Path> result = env.getKeychainPath().collect(Collectors.toList());
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/AppData/Roaming/Cryptomator/keychain.json")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("cryptomator.logDir=/foo/bar")
|
||||
public void testAbsoluteLogDir() {
|
||||
System.setProperty("cryptomator.logDir", "/foo/bar");
|
||||
|
||||
Optional<Path> logDir = env.getLogDir();
|
||||
|
||||
Assertions.assertTrue(logDir.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("cryptomator.logDir=~/foo/bar")
|
||||
public void testRelativeLogDir() {
|
||||
System.setProperty("cryptomator.logDir", "~/foo/bar");
|
||||
|
||||
Optional<Path> logDir = env.getLogDir();
|
||||
|
||||
Assertions.assertTrue(logDir.isPresent());
|
||||
Assertions.assertEquals(Paths.get("/home/testuser/foo/bar"), logDir.get());
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Path Lists")
|
||||
class SettingsPath {
|
||||
|
||||
@Test
|
||||
@DisplayName("test.path.property=")
|
||||
public void testEmptyList() {
|
||||
System.setProperty("test.path.property", "");
|
||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
||||
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("test.path.property=/foo/bar/test")
|
||||
public void testSingleAbsolutePath() {
|
||||
System.setProperty("test.path.property", "/foo/bar/test");
|
||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
||||
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/foo/bar/test")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("test.path.property=~/test")
|
||||
public void testSingleHomeRelativePath() {
|
||||
System.setProperty("test.path.property", "~/test");
|
||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
||||
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/home/testuser/test")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("test.path.property=~/test:~/test2:/foo/bar/test")
|
||||
public void testMultiplePaths() {
|
||||
System.setProperty("test.path.property", "~/test:~/test2:/foo/bar/test");
|
||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
||||
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(3));
|
||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"),
|
||||
Paths.get("/home/testuser/test2"),
|
||||
Paths.get("/foo/bar/test")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,10 +8,10 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common;
|
||||
|
||||
import java.util.Comparator;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import java.util.Comparator;
|
||||
|
||||
public class SemVerComparatorTest {
|
||||
|
||||
@@ -21,38 +21,38 @@ public class SemVerComparatorTest {
|
||||
|
||||
@Test
|
||||
public void compareEqualVersions() {
|
||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4")));
|
||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha")));
|
||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4+20170101", "1.23.4+20171231")));
|
||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha+20170101", "1.23.4-alpha+20171231")));
|
||||
Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4")));
|
||||
Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha")));
|
||||
Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4+20170101", "1.23.4+20171231")));
|
||||
Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha+20170101", "1.23.4-alpha+20171231")));
|
||||
}
|
||||
|
||||
// newer versions in first argument
|
||||
|
||||
@Test
|
||||
public void compareHigherToLowerVersions() {
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.5", "1.23.4")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.24.4", "1.23.4")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-SNAPSHOT")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-56.78")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-beta", "1.23.4-alpha")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-alpha.1", "1.23.4-alpha")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-56.79", "1.23.4-56.78")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.5", "1.23.4")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.24.4", "1.23.4")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-SNAPSHOT")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-56.78")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-beta", "1.23.4-alpha")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-alpha.1", "1.23.4-alpha")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-56.79", "1.23.4-56.78")));
|
||||
}
|
||||
|
||||
// newer versions in second argument
|
||||
|
||||
@Test
|
||||
public void compareLowerToHigherVersions() {
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.5")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.24.4")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23", "1.23.4")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-SNAPSHOT", "1.23.4")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-beta")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha.1")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4-56.79")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.5")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.24.4")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23", "1.23.4")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-SNAPSHOT", "1.23.4")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-beta")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha.1")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4-56.79")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import java.io.IOException;
|
||||
|
||||
public class SettingsJsonAdapterTest {
|
||||
|
||||
@@ -26,12 +26,12 @@ public class SettingsJsonAdapterTest {
|
||||
|
||||
Settings settings = adapter.fromJson(json);
|
||||
|
||||
Assert.assertTrue(settings.checkForUpdates().get());
|
||||
Assert.assertEquals(2, settings.getDirectories().size());
|
||||
Assert.assertEquals(8080, settings.port().get());
|
||||
Assert.assertEquals(42, settings.numTrayNotifications().get());
|
||||
Assert.assertEquals("dav", settings.preferredGvfsScheme().get());
|
||||
Assert.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
|
||||
Assertions.assertTrue(settings.checkForUpdates().get());
|
||||
Assertions.assertEquals(2, settings.getDirectories().size());
|
||||
Assertions.assertEquals(8080, settings.port().get());
|
||||
Assertions.assertEquals(42, settings.numTrayNotifications().get());
|
||||
Assertions.assertEquals("dav", settings.preferredGvfsScheme().get());
|
||||
Assertions.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class SettingsTest {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -5,15 +5,14 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
|
||||
public class VaultSettingsJsonAdapterTest {
|
||||
|
||||
private final VaultSettingsJsonAdapter adapter = new VaultSettingsJsonAdapter();
|
||||
@@ -24,11 +23,11 @@ public class VaultSettingsJsonAdapterTest {
|
||||
JsonReader jsonReader = new JsonReader(new StringReader(json));
|
||||
|
||||
VaultSettings vaultSettings = adapter.read(jsonReader);
|
||||
Assert.assertEquals("foo", vaultSettings.getId());
|
||||
Assert.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
||||
Assert.assertEquals("test", vaultSettings.mountName().get());
|
||||
Assert.assertEquals("X", vaultSettings.winDriveLetter().get());
|
||||
Assert.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get());
|
||||
Assertions.assertEquals("foo", vaultSettings.getId());
|
||||
Assertions.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
||||
Assertions.assertEquals("test", vaultSettings.mountName().get());
|
||||
Assertions.assertEquals("X", vaultSettings.winDriveLetter().get());
|
||||
Assertions.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class VaultSettingsTest {
|
||||
|
||||
|
||||
@@ -4,12 +4,17 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.3</version>
|
||||
<version>1.4.8</version>
|
||||
</parent>
|
||||
<artifactId>keychain</artifactId>
|
||||
<name>System Keychain Access</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
|
||||
@@ -5,30 +5,6 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
@@ -42,11 +18,36 @@ import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.jni.WinDataProtection;
|
||||
import org.cryptomator.jni.WinFunctions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
@@ -57,24 +58,13 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
.disableHtmlEscaping().create();
|
||||
|
||||
private final Optional<WinFunctions> winFunctions;
|
||||
private final Path keychainPath;
|
||||
private final List<Path> keychainPaths;
|
||||
private Map<String, KeychainEntry> keychainEntries;
|
||||
|
||||
@Inject
|
||||
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions) {
|
||||
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions, Environment environment) {
|
||||
this.winFunctions = winFunctions;
|
||||
String keychainPathProperty = System.getProperty("cryptomator.keychainPath");
|
||||
if (keychainPathProperty == null) {
|
||||
LOG.warn("Windows DataProtection module loaded, but no cryptomator.keychainPath property found.");
|
||||
}
|
||||
if (keychainPathProperty != null) {
|
||||
if (keychainPathProperty.startsWith("~/")) {
|
||||
keychainPathProperty = SystemUtils.USER_HOME + keychainPathProperty.substring(1);
|
||||
}
|
||||
this.keychainPath = FileSystems.getDefault().getPath(keychainPathProperty);
|
||||
} else {
|
||||
this.keychainPath = null;
|
||||
}
|
||||
this.keychainPaths = environment.getKeychainPath().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private WinDataProtection dataProtection() {
|
||||
@@ -124,7 +114,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && keychainPath != null;
|
||||
return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && !keychainPaths.isEmpty();
|
||||
}
|
||||
|
||||
private byte[] generateSalt() {
|
||||
@@ -138,30 +128,44 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
|
||||
private void loadKeychainEntriesIfNeeded() {
|
||||
if (keychainEntries == null) {
|
||||
loadKeychainEntries();
|
||||
}
|
||||
assert keychainEntries != null;
|
||||
}
|
||||
|
||||
private void loadKeychainEntries() {
|
||||
Type type = new TypeToken<Map<String, KeychainEntry>>() {
|
||||
}.getType();
|
||||
try (InputStream in = Files.newInputStream(keychainPath, StandardOpenOption.READ); //
|
||||
Reader reader = new InputStreamReader(in, UTF_8)) {
|
||||
keychainEntries = GSON.fromJson(reader, type);
|
||||
} catch (JsonParseException | NoSuchFileException e) {
|
||||
LOG.info("Creating new keychain at path {}", keychainPath);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
|
||||
for (Path keychainPath : keychainPaths) {
|
||||
Optional<Map<String, KeychainEntry>> keychain = loadKeychainEntries(keychainPath);
|
||||
if (keychain.isPresent()) {
|
||||
keychainEntries = keychain.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (keychainEntries == null) {
|
||||
LOG.info("Unable to load existing keychain file, creating new keychain.");
|
||||
keychainEntries = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Map<String, KeychainEntry>> loadKeychainEntries(Path keychainPath) {
|
||||
LOG.debug("Attempting to load keychain from {}", keychainPath);
|
||||
Type type = new TypeToken<Map<String, KeychainEntry>>() {
|
||||
}.getType();
|
||||
try (InputStream in = Files.newInputStream(keychainPath, StandardOpenOption.READ); //
|
||||
Reader reader = new InputStreamReader(in, UTF_8)) {
|
||||
return Optional.of(GSON.fromJson(reader, type));
|
||||
} catch (NoSuchFileException | JsonParseException e) {
|
||||
return Optional.empty();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveKeychainEntries() {
|
||||
if (keychainPaths.isEmpty()) {
|
||||
throw new IllegalStateException("Can't save keychain if no keychain path is specified.");
|
||||
}
|
||||
saveKeychainEntries(keychainPaths.get(0));
|
||||
}
|
||||
|
||||
private void saveKeychainEntries(Path keychainPath) {
|
||||
try (OutputStream out = Files.newOutputStream(keychainPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
|
||||
Writer writer = new OutputStreamWriter(out, UTF_8)) {
|
||||
Writer writer = new OutputStreamWriter(out, UTF_8)) {
|
||||
GSON.toJson(keychainEntries, writer);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
|
||||
@@ -169,6 +173,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
}
|
||||
|
||||
private static class KeychainEntry {
|
||||
|
||||
@SerializedName("ciphertext")
|
||||
byte[] ciphertext;
|
||||
@SerializedName("salt")
|
||||
|
||||
@@ -5,20 +5,20 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import java.util.Optional;
|
||||
|
||||
public class KeychainModuleTest {
|
||||
|
||||
@Test
|
||||
public void testGetKeychain() {
|
||||
Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess();
|
||||
Assert.assertTrue(keychainAccess.isPresent());
|
||||
Assert.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
|
||||
Assertions.assertTrue(keychainAccess.isPresent());
|
||||
Assertions.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
|
||||
keychainAccess.get().storePassphrase("test", "asd");
|
||||
Assert.assertArrayEquals("asd".toCharArray(), keychainAccess.get().loadPassphrase("test"));
|
||||
Assertions.assertArrayEquals("asd".toCharArray(), keychainAccess.get().loadPassphrase("test"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import java.util.Optional;
|
||||
import dagger.Component;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = KeychainModule.class)
|
||||
|
||||
@@ -5,46 +5,36 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.jni.WinDataProtection;
|
||||
import org.cryptomator.jni.WinFunctions;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class WindowsProtectedKeychainAccessTest {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private Path tmpFile;
|
||||
private WindowsProtectedKeychainAccess keychain;
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException, ReflectiveOperationException {
|
||||
tmpFile = Files.createTempFile("unit-tests", ".tmp");
|
||||
System.setProperty("cryptomator.keychainPath", tmpFile.toAbsolutePath().normalize().toString());
|
||||
@BeforeEach
|
||||
public void setup(@TempDir Path tempDir) {
|
||||
Path keychainPath = tempDir.resolve("keychainfile.tmp");
|
||||
Environment env = Mockito.mock(Environment.class);
|
||||
Mockito.when(env.getKeychainPath()).thenReturn(Stream.of(keychainPath));
|
||||
WinFunctions winFunctions = Mockito.mock(WinFunctions.class);
|
||||
WinDataProtection winDataProtection = Mockito.mock(WinDataProtection.class);
|
||||
Mockito.when(winFunctions.dataProtection()).thenReturn(winDataProtection);
|
||||
Answer<byte[]> answerReturningFirstArg = invocation -> ((byte[]) invocation.getArgument(0)).clone();
|
||||
Mockito.when(winDataProtection.protect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
|
||||
Mockito.when(winDataProtection.unprotect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
|
||||
keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions));
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() throws IOException {
|
||||
Files.deleteIfExists(tmpFile);
|
||||
keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions), env);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -55,11 +45,12 @@ public class WindowsProtectedKeychainAccessTest {
|
||||
keychain.storePassphrase("myOtherPassword", storedPw2);
|
||||
String loadedPw1 = new String(keychain.loadPassphrase("myPassword"));
|
||||
String loadedPw2 = new String(keychain.loadPassphrase("myOtherPassword"));
|
||||
Assert.assertEquals(storedPw1, loadedPw1);
|
||||
Assert.assertEquals(storedPw2, loadedPw2);
|
||||
Assertions.assertEquals(storedPw1, loadedPw1);
|
||||
Assertions.assertEquals(storedPw2, loadedPw2);
|
||||
keychain.deletePassphrase("myPassword");
|
||||
Assert.assertNull(keychain.loadPassphrase("myPassword"));
|
||||
Assert.assertNull(keychain.loadPassphrase("nonExistingPassword"));
|
||||
Assertions.assertNull(keychain.loadPassphrase("myPassword"));
|
||||
Assertions.assertNotNull(keychain.loadPassphrase("myOtherPassword"));
|
||||
Assertions.assertNull(keychain.loadPassphrase("nonExistingPassword"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.3</version>
|
||||
<version>1.4.8</version>
|
||||
</parent>
|
||||
<artifactId>launcher</artifactId>
|
||||
<name>Cryptomator Launcher</name>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ApplicationVersion {
|
||||
|
||||
public static String orElse(String other) {
|
||||
return get().orElse(other);
|
||||
}
|
||||
|
||||
public static Optional<String> get() {
|
||||
return Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,57 +5,115 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.logging.DebugMode;
|
||||
import org.cryptomator.logging.LoggerConfiguration;
|
||||
import org.cryptomator.ui.controllers.MainController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
public class Cryptomator {
|
||||
|
||||
// DaggerCryptomatorComponent gets generated by Dagger.
|
||||
// Run Maven and include target/generated-sources/annotations in your IDE.
|
||||
private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.create();
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
|
||||
static final BlockingQueue<Path> FILE_OPEN_REQUESTS = new ArrayBlockingQueue<>(10);
|
||||
|
||||
private final LoggerConfiguration logConfig;
|
||||
private final DebugMode debugMode;
|
||||
private final IpcFactory ipcFactory;
|
||||
private final Optional<String> applicationVersion;
|
||||
|
||||
@Inject
|
||||
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion) {
|
||||
this.logConfig = logConfig;
|
||||
this.debugMode = debugMode;
|
||||
this.ipcFactory = ipcFactory;
|
||||
this.applicationVersion = applicationVersion;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
LOG.info("Starting Cryptomator {} on {} {} ({})", ApplicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||
int exitCode = CRYPTOMATOR_COMPONENT.application().run(args);
|
||||
System.exit(exitCode); // end remaining non-daemon threads.
|
||||
}
|
||||
|
||||
FileOpenRequestHandler fileOpenRequestHandler = new FileOpenRequestHandler(FILE_OPEN_REQUESTS);
|
||||
try (InterProcessCommunicator communicator = InterProcessCommunicator.start(new IpcProtocolImpl(fileOpenRequestHandler))) {
|
||||
if (communicator.isServer()) {
|
||||
fileOpenRequestHandler.handleLaunchArgs(args);
|
||||
CleanShutdownPerformer.registerShutdownHook();
|
||||
Application.launch(MainApplication.class, args);
|
||||
/**
|
||||
* Main entry point of the application launcher.
|
||||
* @param args The arguments passed to this program via {@link #main(String[])}.
|
||||
* @return Nonzero exit code in case of an error.
|
||||
*/
|
||||
private int run(String[] args) {
|
||||
logConfig.init();
|
||||
LOG.info("Starting Cryptomator {} on {} {} ({})", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||
debugMode.initialize();
|
||||
|
||||
/*
|
||||
* Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args.
|
||||
* If no external process could be reached, the args will be handled by the loopback IPC endpoint.
|
||||
*/
|
||||
try (IpcFactory.IpcEndpoint endpoint = ipcFactory.create()) {
|
||||
endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
|
||||
if (endpoint.isConnectedToRemote()) {
|
||||
LOG.info("Found running application instance. Shutting down...");
|
||||
return 2;
|
||||
} else {
|
||||
communicator.handleLaunchArgs(args);
|
||||
LOG.info("Found running application instance. Shutting down.");
|
||||
LOG.debug("Did not find running application instance. Launching GUI...");
|
||||
return runGuiApplication();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to initiate inter-process communication.", e);
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Error during startup", e);
|
||||
return runGuiApplication();
|
||||
}
|
||||
System.exit(0); // end remaining non-daemon threads.
|
||||
}
|
||||
|
||||
private static class IpcProtocolImpl implements InterProcessCommunicationProtocol {
|
||||
/**
|
||||
* Launches the JavaFX application and waits until shutdown is requested.
|
||||
* @return Nonzero exit code in case of an error.
|
||||
*/
|
||||
private int runGuiApplication() {
|
||||
try {
|
||||
CleanShutdownPerformer.registerShutdownHook();
|
||||
Application.launch(MainApp.class);
|
||||
LOG.info("Shutting down...");
|
||||
return 0;
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Terminating due to error", e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private final FileOpenRequestHandler fileOpenRequestHandler;
|
||||
|
||||
// TODO: inject?
|
||||
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler) {
|
||||
this.fileOpenRequestHandler = fileOpenRequestHandler;
|
||||
// We need a separate FX Application class, until we can use the module system. See https://stackoverflow.com/q/54756176/4014509
|
||||
public static class MainApp extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
LOG.info("JavaFX application started.");
|
||||
primaryStage.setMinWidth(652.0);
|
||||
primaryStage.setMinHeight(440.0);
|
||||
|
||||
FxApplicationComponent fxApplicationComponent = CRYPTOMATOR_COMPONENT.fxApplicationComponent() //
|
||||
.fxApplication(this) //
|
||||
.mainWindow(primaryStage) //
|
||||
.build();
|
||||
|
||||
MainController mainCtrl = fxApplicationComponent.fxmlLoader().load("/fxml/main.fxml");
|
||||
mainCtrl.initStage(primaryStage);
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
LOG.info("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
|
||||
fileOpenRequestHandler.handleLaunchArgs(args);
|
||||
public void stop() {
|
||||
LOG.info("JavaFX application stopped.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import dagger.Component;
|
||||
import org.cryptomator.common.CommonsModule;
|
||||
import org.cryptomator.logging.LoggerModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class})
|
||||
public interface CryptomatorComponent {
|
||||
|
||||
Cryptomator application();
|
||||
|
||||
FxApplicationComponent.Builder fxApplicationComponent();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.SettingsProvider;
|
||||
import org.cryptomator.ui.model.AppLaunchEvent;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@Module
|
||||
class CryptomatorModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Settings provideSettings(SettingsProvider settingsProvider) {
|
||||
return settingsProvider.get();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("launchEventQueue")
|
||||
static BlockingQueue<AppLaunchEvent> provideFileOpenRequests() {
|
||||
return new ArrayBlockingQueue<>(10);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("applicationVersion")
|
||||
static Optional<String> provideApplicationVersion() {
|
||||
return Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,52 +6,69 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.cryptomator.ui.model.AppLaunchEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.desktop.OpenFilesEvent;
|
||||
import java.io.File;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
class FileOpenRequestHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FileOpenRequestHandler.class);
|
||||
private final BlockingQueue<Path> fileOpenRequests;
|
||||
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
|
||||
|
||||
public FileOpenRequestHandler(BlockingQueue<Path> fileOpenRequests) {
|
||||
this.fileOpenRequests = fileOpenRequests;
|
||||
@Inject
|
||||
public FileOpenRequestHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
|
||||
this.launchEventQueue = launchEventQueue;
|
||||
try {
|
||||
Desktop.getDesktop().setOpenFileHandler(e -> {
|
||||
e.getFiles().stream().map(File::toPath).forEach(fileOpenRequests::add);
|
||||
});
|
||||
Desktop.getDesktop().setOpenFileHandler(this::openFiles);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
LOG.info("Unable to setOpenFileHandler, probably not supported on this OS.");
|
||||
}
|
||||
}
|
||||
|
||||
private void openFiles(final OpenFilesEvent evt) {
|
||||
Stream<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath);
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen);
|
||||
tryToEnqueueFileOpenRequest(launchEvent);
|
||||
}
|
||||
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
handleLaunchArgs(FileSystems.getDefault(), args);
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
void handleLaunchArgs(FileSystem fs, String[] args) {
|
||||
for (String arg : args) {
|
||||
Stream<Path> pathsToOpen = Arrays.stream(args).map(str -> {
|
||||
try {
|
||||
Path path = fs.getPath(arg);
|
||||
tryToEnqueueFileOpenRequest(path);
|
||||
return fs.getPath(str);
|
||||
} catch (InvalidPathException e) {
|
||||
LOG.trace("{} not a valid path", arg);
|
||||
LOG.trace("Argument not a valid path: {}", str);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}).filter(Objects::nonNull);
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen);
|
||||
tryToEnqueueFileOpenRequest(launchEvent);
|
||||
}
|
||||
|
||||
private void tryToEnqueueFileOpenRequest(Path path) {
|
||||
if (!fileOpenRequests.offer(path)) {
|
||||
LOG.warn("{} could not be enqueued for opening.", path);
|
||||
|
||||
private void tryToEnqueueFileOpenRequest(AppLaunchEvent launchEvent) {
|
||||
if (!launchEventQueue.offer(launchEvent)) {
|
||||
LOG.warn("Could not enqueue application launch event.", launchEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Subcomponent;
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.ui.controllers.ViewControllerLoader;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
@FxApplicationScoped
|
||||
@Subcomponent(modules = FxApplicationModule.class)
|
||||
interface FxApplicationComponent {
|
||||
|
||||
ViewControllerLoader fxmlLoader();
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
|
||||
@BindsInstance
|
||||
Builder fxApplication(Application application);
|
||||
|
||||
@BindsInstance
|
||||
Builder mainWindow(@Named("mainWindow") Stage mainWindow);
|
||||
|
||||
FxApplicationComponent build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.ui.UiModule;
|
||||
|
||||
import javax.inject.Named;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Module(includes = {UiModule.class})
|
||||
class FxApplicationModule {
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
@Named("shutdownTaskScheduler")
|
||||
Consumer<Runnable> provideShutdownTaskScheduler() {
|
||||
return CleanShutdownPerformer::scheduleShutdownTask;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.rmi.ConnectException;
|
||||
import java.rmi.ConnectIOException;
|
||||
import java.rmi.NotBoundException;
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.RemoteException;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
import java.rmi.registry.Registry;
|
||||
import java.rmi.server.RMIClientSocketFactory;
|
||||
import java.rmi.server.RMIServerSocketFactory;
|
||||
import java.rmi.server.RMISocketFactory;
|
||||
import java.rmi.server.UnicastRemoteObject;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.io.MoreFiles;
|
||||
|
||||
/**
|
||||
* First running application on a machine opens a server socket. Further processes will connect as clients.
|
||||
*/
|
||||
abstract class InterProcessCommunicator implements InterProcessCommunicationProtocol, Closeable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InterProcessCommunicator.class);
|
||||
private static final String RMI_NAME = "Cryptomator";
|
||||
|
||||
public abstract boolean isServer();
|
||||
|
||||
/**
|
||||
* @param endpoint The server-side communication endpoint.
|
||||
* @return Either a client or a server communicator.
|
||||
* @throws IOException In case of communication errors.
|
||||
*/
|
||||
public static InterProcessCommunicator start(InterProcessCommunicationProtocol endpoint) throws IOException {
|
||||
return start(getIpcPortPath(), endpoint);
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
static InterProcessCommunicator start(Path portFilePath, InterProcessCommunicationProtocol endpoint) throws IOException {
|
||||
System.setProperty("java.rmi.server.hostname", "localhost");
|
||||
try {
|
||||
// try to connect to existing server:
|
||||
ClientCommunicator client = new ClientCommunicator(portFilePath);
|
||||
LOG.trace("Connected to running process.");
|
||||
return client;
|
||||
} catch (ConnectException | ConnectIOException | NotBoundException e) {
|
||||
LOG.debug("Could not connect to running process.");
|
||||
// continue
|
||||
}
|
||||
|
||||
// spawn a new server:
|
||||
LOG.trace("Spawning new server...");
|
||||
ServerCommunicator server = new ServerCommunicator(endpoint, portFilePath);
|
||||
LOG.debug("Server listening on port {}.", server.getPort());
|
||||
return server;
|
||||
}
|
||||
|
||||
private static Path getIpcPortPath() {
|
||||
final String settingsPathProperty = System.getProperty("cryptomator.ipcPortPath");
|
||||
if (settingsPathProperty == null) {
|
||||
LOG.warn("System property cryptomator.ipcPortPath not set.");
|
||||
return Paths.get(".ipcPort.tmp");
|
||||
} else {
|
||||
return Paths.get(replaceHomeDir(settingsPathProperty));
|
||||
}
|
||||
}
|
||||
|
||||
private static String replaceHomeDir(String path) {
|
||||
if (path.startsWith("~/")) {
|
||||
return SystemUtils.USER_HOME + path.substring(1);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClientCommunicator extends InterProcessCommunicator {
|
||||
|
||||
private final IpcProtocolRemote remote;
|
||||
|
||||
private ClientCommunicator(Path portFilePath) throws ConnectException, NotBoundException, RemoteException {
|
||||
if (Files.notExists(portFilePath)) {
|
||||
throw new ConnectException("No IPC port file.");
|
||||
}
|
||||
try {
|
||||
int port = ClientCommunicator.readPort(portFilePath);
|
||||
LOG.debug("Connecting to port {}...", port);
|
||||
Registry registry = LocateRegistry.getRegistry("localhost", port, new ClientSocketFactory());
|
||||
this.remote = (IpcProtocolRemote) registry.lookup(RMI_NAME);
|
||||
} catch (IOException e) {
|
||||
throw new ConnectException("Error reading IPC port file.");
|
||||
}
|
||||
}
|
||||
|
||||
private static int readPort(Path portFilePath) throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||
try (ReadableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.READ)) {
|
||||
if (ch.read(buf) == Integer.BYTES) {
|
||||
buf.flip();
|
||||
return buf.getInt();
|
||||
} else {
|
||||
throw new IOException("Invalid IPC port file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
try {
|
||||
remote.handleLaunchArgs(args);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ServerCommunicator extends InterProcessCommunicator {
|
||||
|
||||
private final ServerSocket socket;
|
||||
private final Registry registry;
|
||||
private final IpcProtocolRemoteImpl remote;
|
||||
private final Path portFilePath;
|
||||
|
||||
private ServerCommunicator(InterProcessCommunicationProtocol delegate, Path portFilePath) throws IOException {
|
||||
this.socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost"));
|
||||
RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory();
|
||||
SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket);
|
||||
this.registry = LocateRegistry.createRegistry(0, csf, ssf);
|
||||
this.remote = new IpcProtocolRemoteImpl(delegate);
|
||||
UnicastRemoteObject.exportObject(remote, 0);
|
||||
registry.rebind(RMI_NAME, remote);
|
||||
this.portFilePath = portFilePath;
|
||||
ServerCommunicator.writePort(portFilePath, socket.getLocalPort());
|
||||
}
|
||||
|
||||
private static void writePort(Path portFilePath, int port) throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||
buf.putInt(port);
|
||||
buf.flip();
|
||||
MoreFiles.createParentDirectories(portFilePath);
|
||||
try (WritableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
if (ch.write(buf) != Integer.BYTES) {
|
||||
throw new IOException("Did not write expected number of bytes.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
throw new UnsupportedOperationException("Server doesn't invoke methods.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getPort() {
|
||||
return socket.getLocalPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
registry.unbind(RMI_NAME);
|
||||
UnicastRemoteObject.unexportObject(remote, true);
|
||||
socket.close();
|
||||
Files.deleteIfExists(portFilePath);
|
||||
LOG.debug("Server shut down.");
|
||||
} catch (NotBoundException | IOException e) {
|
||||
LOG.warn("Failed to close IPC Server.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static interface IpcProtocolRemote extends Remote {
|
||||
void handleLaunchArgs(String[] args) throws RemoteException;
|
||||
}
|
||||
|
||||
private static class IpcProtocolRemoteImpl implements IpcProtocolRemote {
|
||||
|
||||
private final InterProcessCommunicationProtocol delegate;
|
||||
|
||||
protected IpcProtocolRemoteImpl(InterProcessCommunicationProtocol delegate) throws RemoteException {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
delegate.handleLaunchArgs(args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns the same pre-constructed server socket.
|
||||
*/
|
||||
private static class SingletonServerSocketFactory implements RMIServerSocketFactory {
|
||||
|
||||
private final ServerSocket socket;
|
||||
|
||||
public SingletonServerSocketFactory(ServerSocket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ServerSocket createServerSocket(int port) throws IOException {
|
||||
if (port != 0) {
|
||||
throw new IllegalArgumentException("This factory doesn't support specific ports.");
|
||||
}
|
||||
return this.socket;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates client sockets with short timeouts.
|
||||
*/
|
||||
private static class ClientSocketFactory implements RMIClientSocketFactory {
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
return new SocketWithFixedTimeout(host, port, 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class SocketWithFixedTimeout extends Socket {
|
||||
|
||||
public SocketWithFixedTimeout(String host, int port, int timeoutInMs) throws UnknownHostException, IOException {
|
||||
super(host, port);
|
||||
super.setSoTimeout(timeoutInMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setSoTimeout(int timeout) throws SocketException {
|
||||
// do nothing, timeout is fixed
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import com.google.common.io.MoreFiles;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.rmi.NotBoundException;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
import java.rmi.registry.Registry;
|
||||
import java.rmi.server.RMIClientSocketFactory;
|
||||
import java.rmi.server.RMIServerSocketFactory;
|
||||
import java.rmi.server.RMISocketFactory;
|
||||
import java.rmi.server.UnicastRemoteObject;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* First running application on a machine opens a server socket. Further processes will connect as clients.
|
||||
*/
|
||||
@Singleton
|
||||
class IpcFactory {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(IpcFactory.class);
|
||||
private static final String RMI_NAME = "Cryptomator";
|
||||
|
||||
private final List<Path> portFilePaths;
|
||||
private final IpcProtocolImpl ipcHandler;
|
||||
|
||||
@Inject
|
||||
public IpcFactory(Environment env, IpcProtocolImpl ipcHandler) {
|
||||
this.portFilePaths = env.getIpcPortPath().collect(Collectors.toUnmodifiableList());
|
||||
this.ipcHandler = ipcHandler;
|
||||
}
|
||||
|
||||
public IpcEndpoint create() {
|
||||
if (portFilePaths.isEmpty()) {
|
||||
LOG.warn("No IPC port file path specified.");
|
||||
return new SelfEndpoint(ipcHandler);
|
||||
} else {
|
||||
System.setProperty("java.rmi.server.hostname", "localhost");
|
||||
return attemptClientConnection().or(this::createServerEndpoint).orElseGet(() -> new SelfEndpoint(ipcHandler));
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<IpcEndpoint> attemptClientConnection() {
|
||||
for (Path portFilePath : portFilePaths) {
|
||||
try {
|
||||
int port = readPort(portFilePath);
|
||||
LOG.debug("[Client] Connecting to port {}...", port);
|
||||
Registry registry = LocateRegistry.getRegistry("localhost", port, new ClientSocketFactory());
|
||||
IpcProtocol remoteInterface = (IpcProtocol) registry.lookup(RMI_NAME);
|
||||
return Optional.of(new ClientEndpoint(remoteInterface));
|
||||
} catch (NotBoundException | IOException e) {
|
||||
LOG.debug("[Client] Failed to connect.");
|
||||
// continue with next portFilePath...
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private int readPort(Path portFilePath) throws IOException {
|
||||
try (ReadableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.READ)) {
|
||||
LOG.debug("[Client] Reading IPC port from {}", portFilePath);
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||
if (ch.read(buf) == Integer.BYTES) {
|
||||
buf.flip();
|
||||
return buf.getInt();
|
||||
} else {
|
||||
throw new IOException("Invalid IPC port file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<IpcEndpoint> createServerEndpoint() {
|
||||
assert !portFilePaths.isEmpty();
|
||||
Path portFilePath = portFilePaths.get(0);
|
||||
try {
|
||||
ServerSocket socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost"));
|
||||
RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory();
|
||||
SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket);
|
||||
Registry registry = LocateRegistry.createRegistry(0, csf, ssf);
|
||||
UnicastRemoteObject.exportObject(ipcHandler, 0);
|
||||
registry.rebind(RMI_NAME, ipcHandler);
|
||||
writePort(portFilePath, socket.getLocalPort());
|
||||
return Optional.of(new ServerEndpoint(ipcHandler, socket, registry, portFilePath));
|
||||
} catch (IOException e) {
|
||||
LOG.warn("[Server] Failed to create IPC server.", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void writePort(Path portFilePath, int port) throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||
buf.putInt(port);
|
||||
buf.flip();
|
||||
MoreFiles.createParentDirectories(portFilePath);
|
||||
try (WritableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
if (ch.write(buf) != Integer.BYTES) {
|
||||
throw new IOException("Did not write expected number of bytes.");
|
||||
}
|
||||
}
|
||||
LOG.debug("[Server] Wrote IPC port {} to {}", port, portFilePath);
|
||||
}
|
||||
|
||||
interface IpcEndpoint extends Closeable {
|
||||
|
||||
boolean isConnectedToRemote();
|
||||
|
||||
IpcProtocol getRemote();
|
||||
|
||||
}
|
||||
|
||||
static class SelfEndpoint implements IpcEndpoint {
|
||||
|
||||
protected final IpcProtocol remoteObject;
|
||||
|
||||
SelfEndpoint(IpcProtocol remoteObject) {
|
||||
this.remoteObject = remoteObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnectedToRemote() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IpcProtocol getRemote() {
|
||||
return remoteObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
static class ClientEndpoint implements IpcEndpoint {
|
||||
|
||||
private final IpcProtocol remoteInterface;
|
||||
|
||||
public ClientEndpoint(IpcProtocol remoteInterface) {
|
||||
this.remoteInterface = remoteInterface;
|
||||
}
|
||||
|
||||
public IpcProtocol getRemote() {
|
||||
return remoteInterface;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnectedToRemote() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ServerEndpoint extends SelfEndpoint {
|
||||
|
||||
private final ServerSocket socket;
|
||||
private final Registry registry;
|
||||
private final Path portFilePath;
|
||||
|
||||
private ServerEndpoint(IpcProtocol remoteObject, ServerSocket socket, Registry registry, Path portFilePath) {
|
||||
super(remoteObject);
|
||||
this.socket = socket;
|
||||
this.registry = registry;
|
||||
this.portFilePath = portFilePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
registry.unbind(RMI_NAME);
|
||||
UnicastRemoteObject.unexportObject(remoteObject, true);
|
||||
socket.close();
|
||||
Files.deleteIfExists(portFilePath);
|
||||
LOG.debug("[Server] Shut down");
|
||||
} catch (NotBoundException | IOException e) {
|
||||
LOG.warn("[Server] Error shutting down:", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns the same pre-constructed server socket.
|
||||
*/
|
||||
private static class SingletonServerSocketFactory implements RMIServerSocketFactory {
|
||||
|
||||
private final ServerSocket socket;
|
||||
|
||||
public SingletonServerSocketFactory(ServerSocket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ServerSocket createServerSocket(int port) throws IOException {
|
||||
if (port != 0) {
|
||||
throw new IllegalArgumentException("This factory doesn't support specific ports.");
|
||||
}
|
||||
return this.socket;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates client sockets with short timeouts.
|
||||
*/
|
||||
private static class ClientSocketFactory implements RMIClientSocketFactory {
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
return new SocketWithFixedTimeout(host, port, 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class SocketWithFixedTimeout extends Socket {
|
||||
|
||||
public SocketWithFixedTimeout(String host, int port, int timeoutInMs) throws UnknownHostException, IOException {
|
||||
super(host, port);
|
||||
super.setSoTimeout(timeoutInMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setSoTimeout(int timeout) throws SocketException {
|
||||
// do nothing, timeout is fixed
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,11 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
public interface InterProcessCommunicationProtocol {
|
||||
void handleLaunchArgs(String[] args);
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.RemoteException;
|
||||
|
||||
interface IpcProtocol extends Remote {
|
||||
|
||||
void handleLaunchArgs(String[] args) throws RemoteException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Singleton
|
||||
class IpcProtocolImpl implements IpcProtocol {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(IpcProtocolImpl.class);
|
||||
|
||||
private final FileOpenRequestHandler fileOpenRequestHandler;
|
||||
|
||||
@Inject
|
||||
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler) {
|
||||
this.fileOpenRequestHandler = fileOpenRequestHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
LOG.info("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
|
||||
fileOpenRequestHandler.handleLaunchArgs(args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.logging.DebugMode;
|
||||
import org.cryptomator.ui.controllers.ViewControllerLoader;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = LauncherModule.class)
|
||||
interface LauncherComponent {
|
||||
|
||||
ViewControllerLoader fxmlLoader();
|
||||
|
||||
DebugMode debugMode();
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.ui.UiModule;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@Module(includes = {UiModule.class})
|
||||
class LauncherModule {
|
||||
|
||||
private final Application application;
|
||||
private final Stage mainWindow;
|
||||
|
||||
public LauncherModule(Application application, Stage mainWindow) {
|
||||
this.application = application;
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Application provideApplication() {
|
||||
return application;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("applicationVersion")
|
||||
Optional<String> provideApplicationVersion() {
|
||||
return ApplicationVersion.get();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("mainWindow")
|
||||
Stage provideMainWindow() {
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("fileOpenRequests")
|
||||
BlockingQueue<Path> provideFileOpenRequests() {
|
||||
return Cryptomator.FILE_OPEN_REQUESTS;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("shutdownTaskScheduler")
|
||||
Consumer<Runnable> provideShutdownTaskScheduler() {
|
||||
return CleanShutdownPerformer::scheduleShutdownTask;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.controllers.MainController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MainApplication extends Application {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainApplication.class);
|
||||
private Stage primaryStage;
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
LOG.info("JavaFX application started.");
|
||||
this.primaryStage = primaryStage;
|
||||
primaryStage.setMinWidth(652.0);
|
||||
primaryStage.setMinHeight(440.0);
|
||||
|
||||
LauncherModule launcherModule = new LauncherModule(this, primaryStage);
|
||||
LauncherComponent launcherComponent = DaggerLauncherComponent.builder() //
|
||||
.launcherModule(launcherModule) //
|
||||
.build();
|
||||
|
||||
launcherComponent.debugMode().initialize();
|
||||
|
||||
MainController mainCtrl = launcherComponent.fxmlLoader().load("/fxml/main.fxml");
|
||||
mainCtrl.initStage(primaryStage);
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
assert primaryStage != null;
|
||||
primaryStage.hide();
|
||||
LOG.info("JavaFX application stopped.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,77 +5,55 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.logging;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.slf4j.ILoggerFactory;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class DebugMode {
|
||||
|
||||
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(DebugMode.class);
|
||||
|
||||
private static final Collection<LoggerUpgrade> LOGGER_UPGRADES = asList( //
|
||||
loggerUpgrade(org.slf4j.Logger.ROOT_LOGGER_NAME, Level.INFO), //
|
||||
loggerUpgrade("org.cryptomator", Level.TRACE), //
|
||||
loggerUpgrade("org.eclipse.jetty.server.HttpChannel", Level.DEBUG) //
|
||||
);
|
||||
|
||||
private final Settings settings;
|
||||
private final LoggerContext context;
|
||||
|
||||
@Inject
|
||||
public DebugMode(Settings settings) {
|
||||
public DebugMode(Settings settings, LoggerContext context) {
|
||||
this.settings = settings;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
if (settings.debugMode().get()) {
|
||||
enable();
|
||||
LOG.debug("Debug mode initialized");
|
||||
}
|
||||
setLogLevels(settings.debugMode().get());
|
||||
settings.debugMode().addListener(this::logLevelChanged);
|
||||
}
|
||||
|
||||
private void enable() {
|
||||
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
||||
if (loggerFactory instanceof LoggerContext) {
|
||||
LoggerContext context = (LoggerContext) loggerFactory;
|
||||
LOGGER_UPGRADES.forEach(loggerUpgrade -> loggerUpgrade.execute(context));
|
||||
private void logLevelChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observable, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) {
|
||||
setLogLevels(newValue);
|
||||
}
|
||||
|
||||
private void setLogLevels(boolean debugMode) {
|
||||
if (debugMode) {
|
||||
setLogLevels(LoggerModule.DEBUG_LOG_LEVELS);
|
||||
LOG.debug("Debug mode enabled");
|
||||
} else {
|
||||
LOG.warn("SLF4J not bound to Logback.");
|
||||
LOG.debug("Debug mode disabled");
|
||||
setLogLevels(LoggerModule.DEFAULT_LOG_LEVELS);
|
||||
}
|
||||
}
|
||||
|
||||
private static LoggerUpgrade loggerUpgrade(String loggerName, Level minLevel) {
|
||||
return new LoggerUpgrade(loggerName, minLevel);
|
||||
}
|
||||
|
||||
private static class LoggerUpgrade {
|
||||
|
||||
private final Level level;
|
||||
private final String loggerName;
|
||||
|
||||
public LoggerUpgrade(String loggerName, Level minLevel) {
|
||||
this.loggerName = loggerName;
|
||||
this.level = minLevel;
|
||||
private void setLogLevels(Map<String, Level> logLevels) {
|
||||
for (Map.Entry<String, Level> loglevel : logLevels.entrySet()) {
|
||||
Logger logger = context.getLogger(loglevel.getKey());
|
||||
logger.setLevel(loglevel.getValue());
|
||||
}
|
||||
|
||||
public void execute(LoggerContext context) {
|
||||
Logger logger = context.getLogger(loggerName);
|
||||
if (logger != null && logger.getEffectiveLevel().isGreaterOrEqual(level)) {
|
||||
logger.setLevel(level);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.cryptomator.logging;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
import ch.qos.logback.core.hook.DelayingShutdownHook;
|
||||
import ch.qos.logback.core.util.Duration;
|
||||
import org.cryptomator.common.Environment;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class LoggerConfiguration {
|
||||
|
||||
private static final double SHUTDOWN_DELAY_MS = 100;
|
||||
|
||||
private final LoggerContext context;
|
||||
private final Environment environment;
|
||||
private final Appender<ILoggingEvent> stdout;
|
||||
private final Appender<ILoggingEvent> upgrade;
|
||||
private final Appender<ILoggingEvent> file;
|
||||
|
||||
@Inject
|
||||
LoggerConfiguration(LoggerContext context, //
|
||||
Environment environment, //
|
||||
@Named("stdoutAppender") Appender<ILoggingEvent> stdout, //
|
||||
@Named("upgradeAppender") Appender<ILoggingEvent> upgrade, //
|
||||
@Named("fileAppender") Appender<ILoggingEvent> file) {
|
||||
this.context = context;
|
||||
this.environment = environment;
|
||||
this.stdout = stdout;
|
||||
this.upgrade = upgrade;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
if (environment.useCustomLogbackConfig()) {
|
||||
Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
root.info("Using external logback configuration file.");
|
||||
} else {
|
||||
context.reset();
|
||||
|
||||
// configure loggers:
|
||||
for (Map.Entry<String, Level> loglevel : LoggerModule.DEFAULT_LOG_LEVELS.entrySet()) {
|
||||
Logger logger = context.getLogger(loglevel.getKey());
|
||||
logger.setLevel(loglevel.getValue());
|
||||
logger.setAdditive(false);
|
||||
logger.addAppender(stdout);
|
||||
logger.addAppender(file);
|
||||
}
|
||||
|
||||
// configure upgrade logger:
|
||||
Logger upgrades = context.getLogger("org.cryptomator.ui.model.upgrade");
|
||||
upgrades.setLevel(Level.DEBUG);
|
||||
upgrades.addAppender(stdout);
|
||||
upgrades.addAppender(upgrade);
|
||||
upgrades.setAdditive(false);
|
||||
|
||||
// add shutdown hook
|
||||
DelayingShutdownHook shutdownHook = new DelayingShutdownHook();
|
||||
shutdownHook.setContext(context);
|
||||
shutdownHook.setDelay(Duration.buildByMilliseconds(SHUTDOWN_DELAY_MS));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package org.cryptomator.logging;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
import ch.qos.logback.core.ConsoleAppender;
|
||||
import ch.qos.logback.core.FileAppender;
|
||||
import ch.qos.logback.core.helpers.NOPAppender;
|
||||
import ch.qos.logback.core.hook.DelayingShutdownHook;
|
||||
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
|
||||
import ch.qos.logback.core.rolling.RollingFileAppender;
|
||||
import ch.qos.logback.core.util.Duration;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.slf4j.ILoggerFactory;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
@Module
|
||||
public class LoggerModule {
|
||||
|
||||
private static final String UPGRADE_FILENAME = "upgrade.log";
|
||||
private static final String LOGFILE_NAME = "cryptomator0.log";
|
||||
private static final String LOGFILE_ROLLING_PATTERN = "cryptomator%i.log";
|
||||
private static final int LOGFILE_ROLLING_MIN = 1;
|
||||
private static final int LOGFILE_ROLLING_MAX = 9;
|
||||
private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
|
||||
static final Map<String, Level> DEFAULT_LOG_LEVELS = Map.of( //
|
||||
Logger.ROOT_LOGGER_NAME, Level.INFO, //
|
||||
"org.cryptomator", Level.INFO //
|
||||
);
|
||||
static final Map<String, Level> DEBUG_LOG_LEVELS = Map.of( //
|
||||
Logger.ROOT_LOGGER_NAME, Level.INFO, //
|
||||
"org.cryptomator", Level.TRACE //
|
||||
);
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static LoggerContext provideLoggerContext() {
|
||||
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
||||
if (loggerFactory instanceof LoggerContext) {
|
||||
return (LoggerContext) loggerFactory;
|
||||
} else {
|
||||
throw new IllegalStateException("SLF4J not bound to Logback.");
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static PatternLayoutEncoder provideLayoutEncoder(LoggerContext context) {
|
||||
PatternLayoutEncoder ple = new PatternLayoutEncoder();
|
||||
ple.setPattern(LOG_PATTERN);
|
||||
ple.setContext(context);
|
||||
ple.start();
|
||||
return ple;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("stdoutAppender")
|
||||
static Appender<ILoggingEvent> provideStdoutAppender(LoggerContext context, PatternLayoutEncoder encoder) {
|
||||
ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>();
|
||||
appender.setContext(context);
|
||||
appender.setEncoder(encoder);
|
||||
appender.start();
|
||||
return appender;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("fileAppender")
|
||||
static Appender<ILoggingEvent> provideFileAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
|
||||
if (environment.getLogDir().isPresent()) {
|
||||
Path logDir = environment.getLogDir().get();
|
||||
RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
|
||||
appender.setContext(context);
|
||||
appender.setFile(logDir.resolve(LOGFILE_NAME).toString());
|
||||
appender.setEncoder(encoder);
|
||||
LaunchBasedTriggeringPolicy triggeringPolicy = new LaunchBasedTriggeringPolicy();
|
||||
triggeringPolicy.setContext(context);
|
||||
triggeringPolicy.start();
|
||||
appender.setTriggeringPolicy(triggeringPolicy);
|
||||
FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
|
||||
rollingPolicy.setContext(context);
|
||||
rollingPolicy.setFileNamePattern(logDir.resolve(LOGFILE_ROLLING_PATTERN).toString());
|
||||
rollingPolicy.setMinIndex(LOGFILE_ROLLING_MIN);
|
||||
rollingPolicy.setMaxIndex(LOGFILE_ROLLING_MAX);
|
||||
rollingPolicy.setParent(appender);
|
||||
rollingPolicy.start();
|
||||
appender.setRollingPolicy(rollingPolicy);
|
||||
appender.start();
|
||||
return appender;
|
||||
} else {
|
||||
NOPAppender appender = new NOPAppender<>();
|
||||
appender.setContext(context);
|
||||
return appender;
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("upgradeAppender")
|
||||
static Appender<ILoggingEvent> provideUpgradeAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
|
||||
if (environment.getLogDir().isPresent()) {
|
||||
FileAppender<ILoggingEvent> appender = new FileAppender<>();
|
||||
appender.setFile(environment.getLogDir().get().resolve(UPGRADE_FILENAME).toString());
|
||||
appender.setContext(context);
|
||||
appender.setEncoder(encoder);
|
||||
appender.start();
|
||||
return appender;
|
||||
} else {
|
||||
NOPAppender appender = new NOPAppender<>();
|
||||
appender.setContext(context);
|
||||
return appender;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -5,72 +5,69 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.cryptomator.ui.model.AppLaunchEvent;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.spi.FileSystemProvider;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class FileOpenRequestHandlerTest {
|
||||
|
||||
@Test
|
||||
public void testOpenArgsWithCorrectPaths() throws IOException {
|
||||
Path p1 = Mockito.mock(Path.class);
|
||||
Path p2 = Mockito.mock(Path.class);
|
||||
FileSystem fs = Mockito.mock(FileSystem.class);
|
||||
FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
|
||||
BasicFileAttributes attrs = Mockito.mock(BasicFileAttributes.class);
|
||||
Mockito.when(p1.getFileSystem()).thenReturn(fs);
|
||||
Mockito.when(p2.getFileSystem()).thenReturn(fs);
|
||||
Mockito.when(fs.provider()).thenReturn(provider);
|
||||
Mockito.when(fs.getPath(Mockito.anyString())).thenReturn(p1, p2);
|
||||
Mockito.when(provider.readAttributes(Mockito.any(), Mockito.eq(BasicFileAttributes.class))).thenReturn(attrs);
|
||||
private FileOpenRequestHandler inTest;
|
||||
private BlockingQueue<AppLaunchEvent> queue;
|
||||
|
||||
BlockingQueue<Path> queue = new ArrayBlockingQueue<>(10);
|
||||
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
|
||||
handler.handleLaunchArgs(fs, new String[] {"foo", "bar"});
|
||||
|
||||
Assert.assertEquals(p1, queue.poll());
|
||||
Assert.assertEquals(p2, queue.poll());
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
queue = new ArrayBlockingQueue<>(1);
|
||||
inTest = new FileOpenRequestHandler(queue);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("./cryptomator.exe foo bar")
|
||||
public void testOpenArgsWithCorrectPaths() throws IOException {
|
||||
inTest.handleLaunchArgs(new String[]{"foo", "bar"});
|
||||
|
||||
AppLaunchEvent evt = queue.poll();
|
||||
Assertions.assertNotNull(evt);
|
||||
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
|
||||
MatcherAssert.assertThat(paths, CoreMatchers.hasItems(Paths.get("foo"), Paths.get("bar")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("./cryptomator.exe foo (with 'foo' being an invalid path)")
|
||||
public void testOpenArgsWithIncorrectPaths() throws IOException {
|
||||
FileSystem fs = Mockito.mock(FileSystem.class);
|
||||
Mockito.when(fs.getPath(Mockito.anyString())).thenThrow(new InvalidPathException("foo", "foo is not a path"));
|
||||
Mockito.when(fs.getPath("foo")).thenThrow(new InvalidPathException("foo", "foo is not a path"));
|
||||
inTest.handleLaunchArgs(fs, new String[]{"foo"});
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
BlockingQueue<Path> queue = Mockito.mock(BlockingQueue.class);
|
||||
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
|
||||
handler.handleLaunchArgs(fs, new String[] {"foo"});
|
||||
|
||||
Mockito.verifyNoMoreInteractions(queue);
|
||||
AppLaunchEvent evt = queue.poll();
|
||||
Assertions.assertNotNull(evt);
|
||||
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
|
||||
Assertions.assertTrue(paths.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("./cryptomator.exe foo (with full event queue)")
|
||||
public void testOpenArgsWithFullQueue() throws IOException {
|
||||
Path p = Mockito.mock(Path.class);
|
||||
FileSystem fs = Mockito.mock(FileSystem.class);
|
||||
FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
|
||||
BasicFileAttributes attrs = Mockito.mock(BasicFileAttributes.class);
|
||||
Mockito.when(p.getFileSystem()).thenReturn(fs);
|
||||
Mockito.when(fs.provider()).thenReturn(provider);
|
||||
Mockito.when(fs.getPath(Mockito.anyString())).thenReturn(p);
|
||||
Mockito.when(provider.readAttributes(Mockito.eq(p), Mockito.eq(BasicFileAttributes.class))).thenReturn(attrs);
|
||||
Mockito.when(attrs.isRegularFile()).thenReturn(true);
|
||||
queue.add(new AppLaunchEvent(Stream.empty()));
|
||||
Assumptions.assumeTrue(queue.remainingCapacity() == 0);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
BlockingQueue<Path> queue = Mockito.mock(BlockingQueue.class);
|
||||
Mockito.when(queue.offer(Mockito.any())).thenReturn(false);
|
||||
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
|
||||
handler.handleLaunchArgs(fs, new String[] {"foo"});
|
||||
inTest.handleLaunchArgs(new String[]{"foo"});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.spi.FileSystemProvider;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class InterProcessCommunicatorTest {
|
||||
|
||||
Path portFilePath = Mockito.mock(Path.class);
|
||||
Path portFileParentPath = Mockito.mock(Path.class);
|
||||
BasicFileAttributes portFileParentPathAttrs = Mockito.mock(BasicFileAttributes.class);
|
||||
FileSystem fs = Mockito.mock(FileSystem.class);
|
||||
FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
|
||||
SeekableByteChannel portFileChannel = Mockito.mock(SeekableByteChannel.class);
|
||||
AtomicInteger port = new AtomicInteger(-1);
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
Mockito.when(portFilePath.getFileSystem()).thenReturn(fs);
|
||||
Mockito.when(portFilePath.toAbsolutePath()).thenReturn(portFilePath);
|
||||
Mockito.when(portFilePath.normalize()).thenReturn(portFilePath);
|
||||
Mockito.when(portFilePath.getParent()).thenReturn(portFileParentPath);
|
||||
Mockito.when(portFileParentPath.getFileSystem()).thenReturn(fs);
|
||||
Mockito.when(fs.provider()).thenReturn(provider);
|
||||
Mockito.when(provider.readAttributes(portFileParentPath, BasicFileAttributes.class)).thenReturn(portFileParentPathAttrs);
|
||||
Mockito.when(portFileParentPathAttrs.isDirectory()).thenReturn(false, true); // Guava's MoreFiles will check if dir exists before attempting to create them.
|
||||
Mockito.when(provider.newByteChannel(Mockito.eq(portFilePath), Mockito.any(), Mockito.any())).thenReturn(portFileChannel);
|
||||
Mockito.when(portFileChannel.read(Mockito.any())).then(invocation -> {
|
||||
ByteBuffer buf = invocation.getArgument(0);
|
||||
buf.putInt(port.get());
|
||||
return Integer.BYTES;
|
||||
});
|
||||
Mockito.when(portFileChannel.write(Mockito.any())).then(invocation -> {
|
||||
ByteBuffer buf = invocation.getArgument(0);
|
||||
port.set(buf.getInt());
|
||||
return Integer.BYTES;
|
||||
});
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testStartWithDummyPort1() throws IOException {
|
||||
port.set(0);
|
||||
InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
|
||||
try (InterProcessCommunicator result = InterProcessCommunicator.start(portFilePath, protocol)) {
|
||||
Assert.assertTrue(result.isServer());
|
||||
Mockito.verify(provider).createDirectory(portFileParentPath);
|
||||
Mockito.verifyZeroInteractions(protocol);
|
||||
result.handleLaunchArgs(new String[] {"foo"});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartWithDummyPort2() throws IOException {
|
||||
Mockito.doThrow(new NoSuchFileException("port file")).when(provider).checkAccess(portFilePath);
|
||||
|
||||
InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
|
||||
try (InterProcessCommunicator result = InterProcessCommunicator.start(portFilePath, protocol)) {
|
||||
Assert.assertTrue(result.isServer());
|
||||
Mockito.verify(provider).createDirectory(portFileParentPath);
|
||||
Mockito.verifyZeroInteractions(protocol);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterProcessCommunication() throws IOException, InterruptedException {
|
||||
port.set(-1);
|
||||
InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
|
||||
try (InterProcessCommunicator result1 = InterProcessCommunicator.start(portFilePath, protocol)) {
|
||||
Assert.assertTrue(result1.isServer());
|
||||
Mockito.verify(provider, Mockito.times(1)).createDirectory(portFileParentPath);
|
||||
Mockito.verifyZeroInteractions(protocol);
|
||||
|
||||
try (InterProcessCommunicator result2 = InterProcessCommunicator.start(portFilePath, null)) {
|
||||
Assert.assertFalse(result2.isServer());
|
||||
Mockito.verify(provider, Mockito.times(1)).createDirectory(portFileParentPath);
|
||||
Assert.assertNotSame(result1, result2);
|
||||
|
||||
result2.handleLaunchArgs(new String[] {"foo"});
|
||||
Mockito.verify(protocol).handleLaunchArgs(new String[] {"foo"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.launcher.IpcFactory.IpcEndpoint;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class IpcFactoryTest {
|
||||
|
||||
private Environment environment = Mockito.mock(Environment.class);
|
||||
private IpcProtocolImpl protocolHandler = Mockito.mock(IpcProtocolImpl.class);
|
||||
|
||||
@Test
|
||||
@DisplayName("Wihout IPC port files")
|
||||
public void testNoIpcWithoutPortFile() throws IOException {
|
||||
IpcFactory inTest = new IpcFactory(environment, protocolHandler);
|
||||
|
||||
Mockito.when(environment.getIpcPortPath()).thenReturn(Stream.empty());
|
||||
try (IpcEndpoint endpoint1 = inTest.create()) {
|
||||
Assertions.assertEquals(IpcFactory.SelfEndpoint.class, endpoint1.getClass());
|
||||
Assertions.assertFalse(endpoint1.isConnectedToRemote());
|
||||
Assertions.assertSame(protocolHandler, endpoint1.getRemote());
|
||||
try (IpcEndpoint endpoint2 = inTest.create()) {
|
||||
Assertions.assertEquals(IpcFactory.SelfEndpoint.class, endpoint2.getClass());
|
||||
Assertions.assertNotSame(endpoint1, endpoint2);
|
||||
Assertions.assertFalse(endpoint2.isConnectedToRemote());
|
||||
Assertions.assertSame(protocolHandler, endpoint2.getRemote());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Start server and client with port shared via file")
|
||||
public void testInterProcessCommunication(@TempDir Path tmpDir) throws IOException {
|
||||
Path portFile = tmpDir.resolve("testPortFile");
|
||||
Mockito.when(environment.getIpcPortPath()).thenReturn(Stream.of(portFile));
|
||||
IpcFactory inTest = new IpcFactory(environment, protocolHandler);
|
||||
|
||||
Assertions.assertFalse(Files.exists(portFile));
|
||||
try (IpcEndpoint endpoint1 = inTest.create()) {
|
||||
Assertions.assertEquals(IpcFactory.ServerEndpoint.class, endpoint1.getClass());
|
||||
Assertions.assertFalse(endpoint1.isConnectedToRemote());
|
||||
Assertions.assertTrue(Files.exists(portFile));
|
||||
Assertions.assertSame(protocolHandler, endpoint1.getRemote());
|
||||
Mockito.verifyZeroInteractions(protocolHandler);
|
||||
try (IpcEndpoint endpoint2 = inTest.create()) {
|
||||
Assertions.assertEquals(IpcFactory.ClientEndpoint.class, endpoint2.getClass());
|
||||
Assertions.assertNotSame(endpoint1, endpoint2);
|
||||
Assertions.assertTrue(endpoint2.isConnectedToRemote());
|
||||
Assertions.assertNotSame(protocolHandler, endpoint2.getRemote());
|
||||
Mockito.verifyZeroInteractions(protocolHandler);
|
||||
endpoint2.getRemote().handleLaunchArgs(new String[] {"foo"});
|
||||
Mockito.verify(protocolHandler).handleLaunchArgs(new String[] {"foo"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,12 +5,12 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.logging;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class LaunchBasedTriggeringPolicyTest {
|
||||
|
||||
@Test
|
||||
@@ -21,15 +21,15 @@ public class LaunchBasedTriggeringPolicyTest {
|
||||
|
||||
// 1st invocation
|
||||
boolean triggered = policy.isTriggeringEvent(activeFile, event);
|
||||
Assert.assertTrue(triggered);
|
||||
Assertions.assertTrue(triggered);
|
||||
|
||||
// 2nd invocation
|
||||
triggered = policy.isTriggeringEvent(activeFile, event);
|
||||
Assert.assertFalse(triggered);
|
||||
Assertions.assertFalse(triggered);
|
||||
|
||||
// 3rd invocation
|
||||
triggered = policy.isTriggeringEvent(activeFile, event);
|
||||
Assert.assertFalse(triggered);
|
||||
Assertions.assertFalse(triggered);
|
||||
|
||||
Mockito.verifyZeroInteractions(activeFile);
|
||||
Mockito.verifyZeroInteractions(event);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE xml>
|
||||
<!-- log config used during unit tests and starts from IDE. For production please specify -Dlogback.configurationFile=/path/to/config -->
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.cryptomator" level="DEBUG" />
|
||||
<logger name="org.eclipse.jetty" level="INFO" />
|
||||
<logger name="org.eclipse.jetty.server.Server" level="DEBUG" />
|
||||
<logger name="org.apache" level="INFO" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
102
main/pom.xml
102
main/pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.3</version>
|
||||
<version>1.4.8</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
@@ -25,28 +25,29 @@
|
||||
|
||||
<!-- dependency versions -->
|
||||
<cryptomator.cryptolib.version>1.2.1</cryptomator.cryptolib.version>
|
||||
<cryptomator.cryptofs.version>1.7.0</cryptomator.cryptofs.version>
|
||||
<cryptomator.cryptofs.version>1.8.1</cryptomator.cryptofs.version>
|
||||
<cryptomator.jni.version>2.0.0</cryptomator.jni.version>
|
||||
<cryptomator.fuse.version>1.1.0</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.1.3</cryptomator.dokany.version>
|
||||
<cryptomator.webdav.version>1.0.6</cryptomator.webdav.version>
|
||||
<cryptomator.fuse.version>1.1.2</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.1.7</cryptomator.dokany.version>
|
||||
<cryptomator.webdav.version>1.0.9</cryptomator.webdav.version>
|
||||
|
||||
<javafx.version>12</javafx.version>
|
||||
|
||||
<commons-io.version>2.6</commons-io.version>
|
||||
<commons-lang3.version>3.8.1</commons-lang3.version>
|
||||
|
||||
<easybind.version>1.0.3</easybind.version>
|
||||
|
||||
<guava.version>27.0-jre</guava.version>
|
||||
<dagger.version>2.20</dagger.version>
|
||||
<guava.version>27.1-jre</guava.version>
|
||||
<dagger.version>2.22.1</dagger.version>
|
||||
<gson.version>2.8.5</gson.version>
|
||||
|
||||
<slf4j.version>1.7.25</slf4j.version>
|
||||
<slf4j.version>1.7.26</slf4j.version>
|
||||
<logback.version>1.2.3</logback.version>
|
||||
|
||||
<junit.version>4.12</junit.version>
|
||||
<junit.hierarchicalrunner.version>4.12.1</junit.hierarchicalrunner.version>
|
||||
<mockito.version>2.23.0</mockito.version>
|
||||
<hamcrest.version>1.3</hamcrest.version> <!-- keep in sync with version required by JUnit -->
|
||||
<junit.jupiter.version>5.4.2</junit.jupiter.version>
|
||||
<mockito.version>2.27.0</mockito.version>
|
||||
<hamcrest.version>2.1</hamcrest.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
@@ -122,6 +123,23 @@
|
||||
<version>${cryptomator.jni.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JavaFX -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-base</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
@@ -182,20 +200,10 @@
|
||||
|
||||
<!-- JUnit / Mockito / Hamcrest -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.bechte.junit</groupId>
|
||||
<artifactId>junit-hierarchicalcontextrunner</artifactId>
|
||||
<version>${junit.hierarchicalrunner.version}</version>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
@@ -204,9 +212,15 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<artifactId>hamcrest</artifactId>
|
||||
<version>${hamcrest.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-swing</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -217,13 +231,12 @@
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<artifactId>hamcrest</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -231,11 +244,6 @@
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.bechte.junit</groupId>
|
||||
<artifactId>junit-hierarchicalcontextrunner</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<modules>
|
||||
@@ -249,8 +257,7 @@
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<modules>
|
||||
<module>uber-jar</module>
|
||||
<module>ant-kit</module>
|
||||
<module>buildkit</module>
|
||||
</modules>
|
||||
</profile>
|
||||
<profile>
|
||||
@@ -285,6 +292,18 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<!-- adds Implementation-Version which can be read during runtime -->
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
@@ -317,7 +336,7 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<release>9</release>
|
||||
<release>11</release>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>com.google.dagger</groupId>
|
||||
@@ -327,6 +346,11 @@
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.3</version>
|
||||
</parent>
|
||||
<artifactId>uber-jar</artifactId>
|
||||
<name>Single über jar with all dependencies</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>launcher</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<finalName>Cryptomator-${project.version}</finalName>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<manifestEntries>
|
||||
<Main-Class>org.cryptomator.launcher.Cryptomator</Main-Class>
|
||||
<Implementation-Version>${project.version}</Implementation-Version>
|
||||
</manifestEntries>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE xml>
|
||||
<!-- log config used during unit tests and starts from IDE. For production please specify -Dlogback.configurationFile=/path/to/config -->
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.cryptomator" level="DEBUG" />
|
||||
<logger name="org.eclipse.jetty" level="INFO" />
|
||||
<logger name="org.eclipse.jetty.server.Server" level="INFO" />
|
||||
<logger name="org.apache" level="INFO" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.3</version>
|
||||
<version>1.4.8</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
@@ -46,6 +46,16 @@
|
||||
<artifactId>cryptolib</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JavaFx -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
@@ -99,5 +109,10 @@
|
||||
<version>1.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-swing</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -9,6 +9,24 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.jni.JniException;
|
||||
import org.cryptomator.jni.MacApplicationUiState;
|
||||
import org.cryptomator.jni.MacFunctions;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
import javax.swing.SwingUtilities;
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Image;
|
||||
import java.awt.MenuItem;
|
||||
@@ -24,27 +42,7 @@ import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.jni.JniException;
|
||||
import org.cryptomator.jni.MacApplicationUiState;
|
||||
import org.cryptomator.jni.MacFunctions;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
public class ExitUtil {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ExitUtil.class);
|
||||
|
||||
@@ -8,6 +8,19 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.beans.binding.Binding;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.cryptomator.keychain.KeychainModule;
|
||||
import org.cryptomator.ui.controllers.ViewControllerModule;
|
||||
import org.cryptomator.ui.model.VaultComponent;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
import javax.inject.Named;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@@ -15,35 +28,13 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.beans.binding.Binding;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.CommonsModule;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.SettingsProvider;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.cryptomator.keychain.KeychainModule;
|
||||
import org.cryptomator.ui.controllers.ViewControllerModule;
|
||||
import org.cryptomator.ui.model.VaultComponent;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
@Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class})
|
||||
@Module(includes = {ViewControllerModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class})
|
||||
public class UiModule {
|
||||
|
||||
private static final int NUM_SCHEDULER_THREADS = 4;
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Settings provideSettings(SettingsProvider settingsProvider) {
|
||||
return settingsProvider.get();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
||||
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
|
||||
@@ -57,7 +48,7 @@ public class UiModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
||||
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
ExecutorService executorService = Executors.newCachedThreadPool(r -> {
|
||||
@@ -71,7 +62,7 @@ public class UiModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
Binding<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
|
||||
return EasyBind.map(settings.port(), (Number port) -> {
|
||||
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
|
||||
@@ -80,7 +71,7 @@ public class UiModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
WebDavServer provideWebDavServer(Binding<InetSocketAddress> serverSocketAddressBinding) {
|
||||
WebDavServer server = WebDavServer.create();
|
||||
// no need to unsubscribe eventually, because server is a singleton
|
||||
|
||||
@@ -9,27 +9,9 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.util.PasswordStrengthUtil;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
@@ -41,6 +23,21 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.text.Text;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.util.PasswordStrengthUtil;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ChangePasswordController implements ViewController {
|
||||
|
||||
@@ -113,7 +110,7 @@ public class ChangePasswordController implements ViewController {
|
||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||
}
|
||||
|
||||
private void passwordsChanged(Observable observable) {
|
||||
private void passwordsChanged(@SuppressWarnings("unused") Observable observable) {
|
||||
boolean oldPasswordEmpty = oldPasswordField.getCharacters().length() == 0;
|
||||
boolean newPasswordEmpty = newPasswordField.getCharacters().length() == 0;
|
||||
boolean passwordsEqual = newPasswordField.getCharacters().equals(retypePasswordField.getCharacters());
|
||||
|
||||
@@ -9,16 +9,17 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.value.ObservableIntegerValue;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
@@ -27,17 +28,11 @@ import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class InitializeController implements ViewController {
|
||||
|
||||
@@ -101,7 +96,7 @@ public class InitializeController implements ViewController {
|
||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||
}
|
||||
|
||||
private void passwordsChanged(Observable observable) {
|
||||
private void passwordsChanged(@SuppressWarnings("unused") Observable observable) {
|
||||
boolean passwordsEmpty = passwordField.getCharacters().length() == 0;
|
||||
boolean passwordsEqual = passwordField.getCharacters().equals(retypePasswordField.getCharacters());
|
||||
okButton.setDisable(passwordsEmpty || !passwordsEqual);
|
||||
|
||||
@@ -9,24 +9,6 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Binding;
|
||||
@@ -38,7 +20,6 @@ import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Parent;
|
||||
@@ -61,27 +42,47 @@ import javafx.scene.layout.Pane;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.ui.ExitUtil;
|
||||
import org.cryptomator.ui.controls.DirectoryListCell;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.AppLaunchEvent;
|
||||
import org.cryptomator.ui.model.AutoUnlocker;
|
||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.UpgradeStrategy;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.model.VaultFactory;
|
||||
import org.cryptomator.ui.model.VaultList;
|
||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategy;
|
||||
import org.cryptomator.ui.util.DialogBuilderUtil;
|
||||
import org.cryptomator.ui.util.Tasks;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.Subscription;
|
||||
import org.fxmisc.easybind.monadic.MonadicBinding;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.awt.Desktop;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.cryptomator.ui.util.DialogBuilderUtil.buildErrorDialog;
|
||||
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
public class MainController implements ViewController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
|
||||
@@ -92,7 +93,7 @@ public class MainController implements ViewController {
|
||||
private final ExitUtil exitUtil;
|
||||
private final Localization localization;
|
||||
private final ExecutorService executorService;
|
||||
private final BlockingQueue<Path> fileOpenRequests;
|
||||
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
|
||||
private final VaultFactory vaultFactoy;
|
||||
private final ViewControllerLoader viewControllerLoader;
|
||||
private final ObjectProperty<ViewController> activeController = new SimpleObjectProperty<>();
|
||||
@@ -109,11 +110,11 @@ public class MainController implements ViewController {
|
||||
private Subscription subs = Subscription.EMPTY;
|
||||
|
||||
@Inject
|
||||
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("fileOpenRequests") BlockingQueue<Path> fileOpenRequests, ExitUtil exitUtil, Localization localization,
|
||||
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExitUtil exitUtil, Localization localization,
|
||||
VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.executorService = executorService;
|
||||
this.fileOpenRequests = fileOpenRequests;
|
||||
this.launchEventQueue = launchEventQueue;
|
||||
this.exitUtil = exitUtil;
|
||||
this.localization = localization;
|
||||
this.vaultFactoy = vaultFactoy;
|
||||
@@ -211,19 +212,19 @@ public class MainController implements ViewController {
|
||||
stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon_32.png")));
|
||||
Application.setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString());
|
||||
}
|
||||
exitUtil.initExitHandler(this::gracefulShutdown);
|
||||
exitUtil.initExitHandler(() -> Platform.runLater(this::gracefulShutdown));
|
||||
listenToFileOpenRequests(stage);
|
||||
}
|
||||
|
||||
private void gracefulShutdown() {
|
||||
vaults.filtered(Vault.NOT_LOCKED).forEach(Vault::prepareForShutdown);
|
||||
if (!vaults.filtered(Vault.NOT_LOCKED).isEmpty()) {
|
||||
mainWindow.show(); // to keep the application open
|
||||
ButtonType tryAgainButtonType = new ButtonType(localization.getString("main.gracefulShutdown.button.tryAgain"));
|
||||
ButtonType forceShutdownButtonType = new ButtonType(localization.getString("main.gracefulShutdown.button.forceShutdown"));
|
||||
Alert gracefulShutdownDialog = DialogBuilderUtil.buildGracefulShutdownDialog(
|
||||
localization.getString("main.gracefulShutdown.dialog.title"), localization.getString("main.gracefulShutdown.dialog.header"), localization.getString("main.gracefulShutdown.dialog.content"),
|
||||
forceShutdownButtonType, ButtonType.CANCEL, forceShutdownButtonType, tryAgainButtonType);
|
||||
|
||||
Optional<ButtonType> choice = gracefulShutdownDialog.showAndWait();
|
||||
choice.ifPresent(btnType -> {
|
||||
if (tryAgainButtonType.equals(btnType)) {
|
||||
@@ -231,7 +232,11 @@ public class MainController implements ViewController {
|
||||
} else if (forceShutdownButtonType.equals(btnType)) {
|
||||
Platform.runLater(Platform::exit);
|
||||
} else {
|
||||
return;
|
||||
if (!vaults.filtered(Vault.NOT_LOCKED).isEmpty()) {
|
||||
showUnlockedView(vaults.get(0), false); //if there are still unlocked vaults, show one of them
|
||||
} else {
|
||||
showUnlockView(UnlockController.State.UNLOCKING); //otherwise show any vault
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -248,22 +253,13 @@ public class MainController implements ViewController {
|
||||
}
|
||||
|
||||
private void listenToFileOpenRequests(Stage stage) {
|
||||
executorService.submit(() -> {
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
final Path path = fileOpenRequests.take();
|
||||
Platform.runLater(() -> {
|
||||
addVault(path, true);
|
||||
stage.setIconified(false);
|
||||
stage.show();
|
||||
stage.toFront();
|
||||
stage.requestFocus();
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
});
|
||||
Tasks.create(launchEventQueue::take).onSuccess(event -> {
|
||||
stage.setIconified(false);
|
||||
stage.show();
|
||||
stage.toFront();
|
||||
stage.requestFocus();
|
||||
event.getPathsToOpen().forEach(path -> addVault(path, true));
|
||||
}).schedulePeriodically(executorService, Duration.ZERO, Duration.ZERO);
|
||||
}
|
||||
|
||||
private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
|
||||
@@ -278,7 +274,7 @@ public class MainController implements ViewController {
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
private void didClickAddVault(ActionEvent event) {
|
||||
private void didClickAddVault() {
|
||||
if (addVaultContextMenu.isShowing()) {
|
||||
addVaultContextMenu.hide();
|
||||
} else {
|
||||
@@ -287,7 +283,7 @@ public class MainController implements ViewController {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickCreateNewVault(ActionEvent event) {
|
||||
private void didClickCreateNewVault() {
|
||||
final FileChooser fileChooser = new FileChooser();
|
||||
final File file = fileChooser.showSaveDialog(mainWindow);
|
||||
if (file == null) {
|
||||
@@ -320,7 +316,7 @@ public class MainController implements ViewController {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickAddExistingVaults(ActionEvent event) {
|
||||
private void didClickAddExistingVaults() {
|
||||
final FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator"));
|
||||
final List<File> files = fileChooser.showOpenMultipleDialog(mainWindow);
|
||||
@@ -363,7 +359,7 @@ public class MainController implements ViewController {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickRemoveSelectedEntry(ActionEvent e) {
|
||||
private void didClickRemoveSelectedEntry() {
|
||||
Alert confirmDialog = DialogBuilderUtil.buildConfirmationDialog( //
|
||||
localization.getString("main.directoryList.remove.confirmation.title"), //
|
||||
localization.getString("main.directoryList.remove.confirmation.header"), //
|
||||
@@ -382,12 +378,12 @@ public class MainController implements ViewController {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickChangePassword(ActionEvent e) {
|
||||
private void didClickChangePassword() {
|
||||
showChangePasswordView();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickShowSettings(ActionEvent e) {
|
||||
private void didClickShowSettings() {
|
||||
toggleShowSettings();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
|
||||
@Singleton
|
||||
import javax.inject.Inject;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class NotFoundController implements ViewController {
|
||||
|
||||
@Inject
|
||||
|
||||
@@ -25,6 +25,7 @@ import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.StringConverter;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
@@ -32,10 +33,9 @@ import org.cryptomator.ui.model.Volume;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
public class SettingsController implements ViewController {
|
||||
|
||||
private static final CharMatcher DIGITS_MATCHER = CharMatcher.inRange('0', '9');
|
||||
@@ -130,7 +130,7 @@ public class SettingsController implements ViewController {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void changePort(ActionEvent evt) {
|
||||
private void changePort() {
|
||||
assert isPortValid() : "Button must be disabled, if port is invalid.";
|
||||
try {
|
||||
int port = Integer.parseInt(portField.getText());
|
||||
@@ -143,11 +143,8 @@ public class SettingsController implements ViewController {
|
||||
private boolean isPortValid() {
|
||||
try {
|
||||
int port = Integer.parseInt(portField.getText());
|
||||
if (port == 0 || port >= Settings.MIN_PORT && port <= Settings.MAX_PORT) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return port == 0 // choose port automatically
|
||||
|| port >= Settings.MIN_PORT && port <= Settings.MAX_PORT; // port within range
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
@@ -159,7 +156,7 @@ public class SettingsController implements ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private void setVisibilityGvfsElements(Observable obs, Object oldValue, Object newValue) {
|
||||
private void setVisibilityGvfsElements(@SuppressWarnings("unused") Observable obs, @SuppressWarnings("unused")Object oldValue, Object newValue) {
|
||||
prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX && ((VolumeImpl) newValue).getDisplayName().equals("WebDAV"));
|
||||
prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX && ((VolumeImpl) newValue).getDisplayName().equals("WebDAV"));
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import com.google.common.base.Strings;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Alert;
|
||||
@@ -106,7 +105,7 @@ public class UnlockController implements ViewController {
|
||||
private Button unlockButton;
|
||||
|
||||
@FXML
|
||||
private Label successMessage;
|
||||
private Text messageText;
|
||||
|
||||
@FXML
|
||||
private CheckBox savePassword;
|
||||
@@ -136,7 +135,7 @@ public class UnlockController implements ViewController {
|
||||
private ProgressIndicator progressIndicator;
|
||||
|
||||
@FXML
|
||||
private Text messageText;
|
||||
private Text progressText;
|
||||
|
||||
@FXML
|
||||
private Hyperlink downloadsPageLink;
|
||||
@@ -200,8 +199,8 @@ public class UnlockController implements ViewController {
|
||||
advancedOptions.setVisible(false);
|
||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
||||
progressIndicator.setVisible(false);
|
||||
successMessage.setVisible(state.successMessage().isPresent());
|
||||
state.successMessage().map(localization::getString).ifPresent(successMessage::setText);
|
||||
progressText.setText(null);
|
||||
state.successMessage().map(localization::getString).ifPresent(messageText::setText);
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
winDriveLetter.valueProperty().removeListener(driveLetterChangeListener);
|
||||
winDriveLetter.getItems().clear();
|
||||
@@ -212,7 +211,6 @@ public class UnlockController implements ViewController {
|
||||
chooseSelectedDriveLetter();
|
||||
}
|
||||
downloadsPageLink.setVisible(false);
|
||||
messageText.setText(null);
|
||||
mountName.setText(vault.getMountName());
|
||||
savePassword.setSelected(false);
|
||||
// auto-fill pw from keychain:
|
||||
@@ -288,7 +286,7 @@ public class UnlockController implements ViewController {
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
public void didClickDownloadsLink(ActionEvent event) {
|
||||
public void didClickDownloadsLink() {
|
||||
app.getHostServices().showDocument("https://cryptomator.org/downloads/#allVersions");
|
||||
}
|
||||
|
||||
@@ -297,8 +295,8 @@ public class UnlockController implements ViewController {
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
private void didClickAdvancedOptionsButton(ActionEvent event) {
|
||||
successMessage.setVisible(false);
|
||||
private void didClickAdvancedOptionsButton() {
|
||||
messageText.setText(null);
|
||||
advancedOptions.setVisible(!advancedOptions.isVisible());
|
||||
if (advancedOptions.isVisible()) {
|
||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.hide"));
|
||||
@@ -313,7 +311,7 @@ public class UnlockController implements ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private void mountNameDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
|
||||
private void mountNameDidChange(@SuppressWarnings("unused") ObservableValue<? extends String> property, @SuppressWarnings("unused")String oldValue, String newValue) {
|
||||
// newValue is guaranteed to be a-z0-9_, see #filterAlphanumericKeyEvents
|
||||
if (newValue.isEmpty()) {
|
||||
mountName.setText(vault.getMountName());
|
||||
@@ -322,7 +320,8 @@ public class UnlockController implements ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
public void didClickChooseCustomMountPoint(ActionEvent actionEvent) {
|
||||
@FXML
|
||||
public void didClickChooseCustomMountPoint() {
|
||||
DirectoryChooser dirChooser = new DirectoryChooser();
|
||||
File file = dirChooser.showDialog(mainWindow);
|
||||
if (file != null) {
|
||||
@@ -373,7 +372,7 @@ public class UnlockController implements ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private void winDriveLetterDidChange(ObservableValue<? extends Character> property, Character oldValue, Character newValue) {
|
||||
private void winDriveLetterDidChange(@SuppressWarnings("unused") ObservableValue<? extends Character> property, @SuppressWarnings("unused") Character oldValue, Character newValue) {
|
||||
vault.setWinDriveLetter(newValue);
|
||||
}
|
||||
|
||||
@@ -397,7 +396,7 @@ public class UnlockController implements ViewController {
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
private void didClickSavePasswordCheckbox(ActionEvent event) {
|
||||
private void didClickSavePasswordCheckbox() {
|
||||
if (!savePassword.isSelected() && hasStoredPassword()) {
|
||||
Alert confirmDialog = DialogBuilderUtil.buildConfirmationDialog( //
|
||||
localization.getString("unlock.savePassword.delete.confirmation.title"), //
|
||||
@@ -428,22 +427,24 @@ public class UnlockController implements ViewController {
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
private void didClickUnlockButton(ActionEvent event) {
|
||||
private void didClickUnlockButton() {
|
||||
advancedOptions.setDisable(true);
|
||||
advancedOptions.setVisible(false);
|
||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
||||
progressIndicator.setVisible(true);
|
||||
|
||||
CharSequence password = passwordField.getCharacters();
|
||||
Tasks.create(() -> {
|
||||
messageText.setText(localization.getString("unlock.pendingMessage.unlocking"));
|
||||
progressText.setText(localization.getString("unlock.pendingMessage.unlocking"));
|
||||
vault.unlock(password);
|
||||
if (keychainAccess.isPresent() && savePassword.isSelected()) {
|
||||
keychainAccess.get().storePassphrase(vault.getId(), password);
|
||||
}
|
||||
}).onSuccess(() -> {
|
||||
messageText.setText(null);
|
||||
messageText.setText("");
|
||||
downloadsPageLink.setVisible(false);
|
||||
listener.ifPresent(lstnr -> lstnr.didUnlock(vault));
|
||||
passwordField.swipe();
|
||||
}).onError(InvalidPassphraseException.class, e -> {
|
||||
messageText.setText(localization.getString("unlock.errorMessage.wrongPassword"));
|
||||
passwordField.selectAll();
|
||||
@@ -473,11 +474,9 @@ public class UnlockController implements ViewController {
|
||||
LOG.error("Unlock failed for technical reasons.", e);
|
||||
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
|
||||
}).andFinally(() -> {
|
||||
if (!savePassword.isSelected()) {
|
||||
passwordField.swipe();
|
||||
}
|
||||
advancedOptions.setDisable(false);
|
||||
progressIndicator.setVisible(false);
|
||||
progressText.setText(null);
|
||||
}).runOnce(executor);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,16 +2,12 @@
|
||||
* 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 javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
@@ -42,6 +38,10 @@ import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class UnlockedController implements ViewController {
|
||||
@@ -49,7 +49,7 @@ public class UnlockedController implements ViewController {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UnlockedController.class);
|
||||
|
||||
private static final int IO_SAMPLING_STEPS = 100;
|
||||
private static final double IO_SAMPLING_INTERVAL = 0.25;
|
||||
private static final double IO_SAMPLING_INTERVAL = 0.5;
|
||||
|
||||
private final Localization localization;
|
||||
private final ExecutorService executor;
|
||||
@@ -103,7 +103,7 @@ public class UnlockedController implements ViewController {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickLockVault(ActionEvent event) {
|
||||
private void didClickLockVault() {
|
||||
regularLockVault(this::lockVaultSucceeded);
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ public class UnlockedController implements ViewController {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickMoreOptions(ActionEvent event) {
|
||||
private void didClickMoreOptions() {
|
||||
if (moreOptionsMenu.isShowing()) {
|
||||
moreOptionsMenu.hide();
|
||||
} else {
|
||||
@@ -166,7 +166,7 @@ public class UnlockedController implements ViewController {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickRevealVault(ActionEvent event) {
|
||||
private void didClickRevealVault() {
|
||||
revealVault(vault.get());
|
||||
}
|
||||
|
||||
@@ -211,43 +211,42 @@ public class UnlockedController implements ViewController {
|
||||
private class IoSamplingAnimationHandler implements EventHandler<ActionEvent> {
|
||||
|
||||
private static final double BYTES_TO_MEGABYTES_FACTOR = 1.0 / IO_SAMPLING_INTERVAL / 1024.0 / 1024.0;
|
||||
private static final double SMOOTHING_FACTOR = 0.3;
|
||||
private static final long EFFECTIVELY_ZERO = 100000; // 100kb
|
||||
private final Series<Number, Number> decryptedBytes;
|
||||
private final Series<Number, Number> encryptedBytes;
|
||||
private int step = 0;
|
||||
private long oldDecBytes = 0;
|
||||
private long oldEncBytes = 0;
|
||||
|
||||
public IoSamplingAnimationHandler(Series<Number, Number> decryptedBytes, Series<Number, Number> encryptedBytes) {
|
||||
this.decryptedBytes = decryptedBytes;
|
||||
this.encryptedBytes = encryptedBytes;
|
||||
|
||||
// initialize data once and change value of datapoints later:
|
||||
for (int i = 0; i < IO_SAMPLING_STEPS; i++) {
|
||||
decryptedBytes.getData().add(new Data<>(i, 0));
|
||||
encryptedBytes.getData().add(new Data<>(i, 0));
|
||||
}
|
||||
|
||||
xAxis.setLowerBound(0);
|
||||
xAxis.setUpperBound(IO_SAMPLING_STEPS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
step++;
|
||||
// move all values one step:
|
||||
for (int i = 0; i < IO_SAMPLING_STEPS - 1; i++) {
|
||||
int j = i + 1;
|
||||
Number tmp = decryptedBytes.getData().get(j).getYValue();
|
||||
decryptedBytes.getData().get(i).setYValue(tmp);
|
||||
|
||||
tmp = encryptedBytes.getData().get(j).getYValue();
|
||||
encryptedBytes.getData().get(i).setYValue(tmp);
|
||||
}
|
||||
|
||||
// add latest value:
|
||||
final long decBytes = vault.get().pollBytesRead();
|
||||
final double smoothedDecBytes = oldDecBytes + SMOOTHING_FACTOR * (decBytes - oldDecBytes);
|
||||
final double smoothedDecMb = smoothedDecBytes * BYTES_TO_MEGABYTES_FACTOR;
|
||||
oldDecBytes = smoothedDecBytes > EFFECTIVELY_ZERO ? (long) smoothedDecBytes : 0l;
|
||||
decryptedBytes.getData().add(new Data<Number, Number>(step, smoothedDecMb));
|
||||
if (decryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
|
||||
decryptedBytes.getData().remove(0);
|
||||
}
|
||||
|
||||
final double decMb = decBytes * BYTES_TO_MEGABYTES_FACTOR;
|
||||
final long encBytes = vault.get().pollBytesWritten();
|
||||
final double smoothedEncBytes = oldEncBytes + SMOOTHING_FACTOR * (encBytes - oldEncBytes);
|
||||
final double smoothedEncMb = smoothedEncBytes * BYTES_TO_MEGABYTES_FACTOR;
|
||||
oldEncBytes = smoothedEncBytes > EFFECTIVELY_ZERO ? (long) smoothedEncBytes : 0l;
|
||||
encryptedBytes.getData().add(new Data<Number, Number>(step, smoothedEncMb));
|
||||
if (encryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
|
||||
encryptedBytes.getData().remove(0);
|
||||
}
|
||||
|
||||
xAxis.setLowerBound(step - IO_SAMPLING_STEPS);
|
||||
xAxis.setUpperBound(step);
|
||||
final double encMb = encBytes * BYTES_TO_MEGABYTES_FACTOR;
|
||||
decryptedBytes.getData().get(IO_SAMPLING_STEPS - 1).setYValue(decMb);
|
||||
encryptedBytes.getData().get(IO_SAMPLING_STEPS - 1).setYValue(encMb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,6 +268,7 @@ public class UnlockedController implements ViewController {
|
||||
|
||||
@FunctionalInterface
|
||||
interface LockListener {
|
||||
|
||||
void didLock(UnlockedController ctrl);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.UpgradeStrategy;
|
||||
import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException;
|
||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategy;
|
||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategy.UpgradeFailedException;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.util.Tasks;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
@@ -5,20 +5,18 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
|
||||
import javafx.fxml.FXMLLoader;
|
||||
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
public class ViewControllerLoader {
|
||||
|
||||
private final Map<Class<? extends ViewController>, Provider<ViewController>> controllerProviders;
|
||||
|
||||
@@ -8,22 +8,6 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
@@ -39,15 +23,31 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.util.Tasks;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.cryptomator.ui.util.DialogBuilderUtil.buildYesNoDialog;
|
||||
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
public class WelcomeController implements ViewController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class);
|
||||
|
||||
@@ -9,12 +9,27 @@
|
||||
package org.cryptomator.ui.controls;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.OverrunStyle;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.Dragboard;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
import java.awt.Toolkit;
|
||||
import java.nio.CharBuffer;
|
||||
import java.text.Normalizer;
|
||||
import java.text.Normalizer.Form;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
@@ -28,13 +43,44 @@ public class SecPasswordField extends PasswordField {
|
||||
private static final int INITIAL_BUFFER_SIZE = 50;
|
||||
private static final int GROW_BUFFER_SIZE = 50;
|
||||
private static final String PLACEHOLDER = "*";
|
||||
private static final double PADDING = 2.0;
|
||||
private static final double INDICATOR_PADDING = 4.0;
|
||||
private static final Color INDICATOR_COLOR = new Color(0.901, 0.494, 0.133, 1.0);
|
||||
|
||||
private final Tooltip tooltip = new Tooltip();
|
||||
private final Label indicator = new Label();
|
||||
private final String nonPrintableCharsWarning;
|
||||
private final String capslockWarning;
|
||||
|
||||
private char[] content = new char[INITIAL_BUFFER_SIZE];
|
||||
private int length = 0;
|
||||
|
||||
public SecPasswordField() {
|
||||
this.onDragOverProperty().set(this::handleDragOver);
|
||||
this.onDragDroppedProperty().set(this::handleDragDropped);
|
||||
this("", "");
|
||||
}
|
||||
|
||||
public SecPasswordField(@NamedArg("nonPrintableCharsWarning") String nonPrintableCharsWarning, @NamedArg("capslockWarning") String capslockWarning) {
|
||||
this.nonPrintableCharsWarning = nonPrintableCharsWarning;
|
||||
this.capslockWarning = capslockWarning;
|
||||
indicator.setPadding(new Insets(PADDING, INDICATOR_PADDING, PADDING, INDICATOR_PADDING));
|
||||
indicator.setAlignment(Pos.CENTER_RIGHT);
|
||||
indicator.setMouseTransparent(true);
|
||||
indicator.setTextOverrun(OverrunStyle.CLIP);
|
||||
indicator.setTextFill(INDICATOR_COLOR);
|
||||
indicator.setFont(Font.font(indicator.getFont().getFamily(), 15.0));
|
||||
this.getChildren().add(indicator);
|
||||
this.setTooltip(tooltip);
|
||||
this.addEventHandler(DragEvent.DRAG_OVER, this::handleDragOver);
|
||||
this.addEventHandler(DragEvent.DRAG_DROPPED, this::handleDragDropped);
|
||||
this.addEventHandler(KeyEvent.ANY, this::handleKeyEvent);
|
||||
this.focusedProperty().addListener(this::focusedChanged);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
indicator.relocate(0.0, 0.0);
|
||||
indicator.resize(getWidth(), getHeight());
|
||||
}
|
||||
|
||||
private void handleDragOver(DragEvent event) {
|
||||
@@ -53,23 +99,101 @@ public class SecPasswordField extends PasswordField {
|
||||
event.consume();
|
||||
}
|
||||
|
||||
private void handleKeyEvent(KeyEvent e) {
|
||||
if (e.getCode() == KeyCode.CAPS) {
|
||||
updateVisualHints(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void focusedChanged(@SuppressWarnings("unused") Observable observable) {
|
||||
updateVisualHints(isFocused());
|
||||
}
|
||||
|
||||
private void updateVisualHints(boolean focused) {
|
||||
StringBuilder tooltipSb = new StringBuilder();
|
||||
StringBuilder indicatorSb = new StringBuilder();
|
||||
if (containsNonPrintableCharacters()) {
|
||||
indicatorSb.append('⚠');
|
||||
tooltipSb.append("- ").append(nonPrintableCharsWarning).append('\n');
|
||||
}
|
||||
// AWT code needed until https://bugs.openjdk.java.net/browse/JDK-8090882 is closed:
|
||||
if (focused && Toolkit.getDefaultToolkit().getLockingKeyState(java.awt.event.KeyEvent.VK_CAPS_LOCK)) {
|
||||
indicatorSb.append('⇪');
|
||||
tooltipSb.append("- ").append(capslockWarning).append('\n');
|
||||
}
|
||||
indicator.setText(indicatorSb.toString());
|
||||
if (!indicator.getText().isEmpty()) {
|
||||
setPadding(new Insets(PADDING, getIndicatorWidth(), PADDING, PADDING));
|
||||
} else {
|
||||
setPadding(new Insets(PADDING));
|
||||
}
|
||||
tooltip.setText(tooltipSb.toString());
|
||||
if (tooltip.getText().isEmpty()) {
|
||||
setTooltip(null);
|
||||
} else {
|
||||
setTooltip(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
private double getIndicatorWidth() {
|
||||
return new Text(indicator.getText()).getLayoutBounds().getWidth() + INDICATOR_PADDING * 2.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if any {@link Character#isISOControl(char) control character} is present in the current value of this password field.
|
||||
* @implNote runs in O(n)
|
||||
*/
|
||||
boolean containsNonPrintableCharacters() {
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (Character.isISOControl(content[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a range of characters with the given text.
|
||||
* The text will be normalized to <a href="https://www.unicode.org/glossary/#normalization_form_c">NFC</a>.
|
||||
*
|
||||
* @param start The starting index in the range, inclusive. This must be >= 0 and < the end.
|
||||
* @param end The ending index in the range, exclusive. This is one-past the last character to
|
||||
* delete (consistent with the String manipulation methods). This must be > the start,
|
||||
* and <= the length of the text.
|
||||
* @param text The text that is to replace the range. This must not be null.
|
||||
* @implNote Internally calls {@link PasswordField#replaceText(int, int, String)} with a dummy String for visual purposes.
|
||||
*/
|
||||
@Override
|
||||
public void replaceText(int start, int end, String text) {
|
||||
String normalizedText = Normalizer.normalize(text, Form.NFC);
|
||||
int removed = end - start;
|
||||
int added = text.length();
|
||||
this.length += added - removed;
|
||||
growContentIfNeeded();
|
||||
text.getChars(0, text.length(), content, start);
|
||||
int added = normalizedText.length();
|
||||
int delta = added - removed;
|
||||
|
||||
String placeholderString = Strings.repeat(PLACEHOLDER, text.length());
|
||||
// ensure sufficient content buffer size
|
||||
int oldLength = length;
|
||||
this.length += delta;
|
||||
growContentIfNeeded();
|
||||
|
||||
// shift existing content
|
||||
if (delta != 0 && start < oldLength) {
|
||||
System.arraycopy(content, end, content, end + delta, oldLength - end);
|
||||
}
|
||||
|
||||
// copy new text to content buffer
|
||||
normalizedText.getChars(0, normalizedText.length(), content, start);
|
||||
|
||||
// trigger visual hints
|
||||
updateVisualHints(true);
|
||||
String placeholderString = Strings.repeat(PLACEHOLDER, normalizedText.length());
|
||||
super.replaceText(start, end, placeholderString);
|
||||
}
|
||||
|
||||
private void growContentIfNeeded() {
|
||||
if (this.length > content.length) {
|
||||
char[] newContent = new char[content.length + GROW_BUFFER_SIZE];
|
||||
if (length > content.length) {
|
||||
char[] newContent = new char[length + GROW_BUFFER_SIZE];
|
||||
System.arraycopy(content, 0, newContent, 0, content.length);
|
||||
swipe();
|
||||
swipe(content);
|
||||
this.content = newContent;
|
||||
}
|
||||
}
|
||||
@@ -80,6 +204,7 @@ public class SecPasswordField extends PasswordField {
|
||||
* @return A character sequence backed by the SecPasswordField's buffer (not a copy).
|
||||
* @implNote The CharSequence will not copy the backing char[].
|
||||
* Therefore any mutation to the SecPasswordField's content will mutate or eventually swipe the returned CharSequence.
|
||||
* @implSpec The CharSequence is usually in <a href="https://www.unicode.org/glossary/#normalization_form_c">NFC</a> representation (unless NFD-encoded char[] is set via {@link #setPassword(char[])}).
|
||||
* @see #swipe()
|
||||
*/
|
||||
@Override
|
||||
@@ -87,6 +212,28 @@ public class SecPasswordField extends PasswordField {
|
||||
return CharBuffer.wrap(content, 0, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method wrapper for {@link #setPassword(char[])}.
|
||||
*
|
||||
* @param password
|
||||
* @see #setPassword(char[])
|
||||
*/
|
||||
public void setPassword(CharSequence password) {
|
||||
char[] buf = new char[password.length()];
|
||||
for (int i = 0; i < password.length(); i++) {
|
||||
buf[i] = password.charAt(i);
|
||||
}
|
||||
setPassword(buf);
|
||||
Arrays.fill(buf, SWIPE_CHAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly sets the content of this password field to a copy of the given password.
|
||||
* No conversion whatsoever happens. If you want to normalize the unicode representation of the password,
|
||||
* do it before calling this method.
|
||||
*
|
||||
* @param password
|
||||
*/
|
||||
public void setPassword(char[] password) {
|
||||
swipe();
|
||||
content = Arrays.copyOf(password, password.length);
|
||||
@@ -100,7 +247,12 @@ public class SecPasswordField extends PasswordField {
|
||||
* Destroys the stored password by overriding each character with a different character.
|
||||
*/
|
||||
public void swipe() {
|
||||
Arrays.fill(content, SWIPE_CHAR);
|
||||
swipe(content);
|
||||
length = 0;
|
||||
}
|
||||
|
||||
private void swipe(char[] buffer) {
|
||||
Arrays.fill(buffer, SWIPE_CHAR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.l10n;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -19,16 +26,7 @@ import java.util.Objects;
|
||||
import java.util.PropertyResourceBundle;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
public class Localization extends ResourceBundle {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Localization.class);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class AppLaunchEvent {
|
||||
|
||||
private final Stream<Path> pathsToOpen;
|
||||
|
||||
public AppLaunchEvent(Stream<Path> pathsToOpen) {this.pathsToOpen = pathsToOpen;}
|
||||
|
||||
public Stream<Path> getPathsToOpen() {
|
||||
return pathsToOpen;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,13 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.keychain.KeychainAccess;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.Arrays;
|
||||
@@ -14,15 +21,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.keychain.KeychainAccess;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
public class AutoUnlocker {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AutoUnlocker.class);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
|
||||
@@ -15,7 +16,6 @@ import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryNotEmptyException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NotDirectoryException;
|
||||
import java.nio.file.Path;
|
||||
@@ -25,21 +25,20 @@ import java.util.Optional;
|
||||
public class FuseVolume implements Volume {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
|
||||
|
||||
// TODO: dont use fixed Strings and rather set them in some system environment variables in the cryptomator installer and load those!
|
||||
private static final String DEFAULT_MOUNTROOTPATH_MAC = System.getProperty("user.home") + "/Library/Application Support/Cryptomator";
|
||||
private static final String DEFAULT_MOUNTROOTPATH_LINUX = System.getProperty("user.home") + "/.Cryptomator";
|
||||
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
|
||||
private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase().contains("mac");
|
||||
|
||||
private final VaultSettings vaultSettings;
|
||||
private final Environment environment;
|
||||
|
||||
private Mount fuseMnt;
|
||||
private Path mountPoint;
|
||||
private boolean createdTemporaryMountPoint;
|
||||
|
||||
@Inject
|
||||
public FuseVolume(VaultSettings vaultSettings) {
|
||||
public FuseVolume(VaultSettings vaultSettings, Environment environment) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,11 +48,9 @@ public class FuseVolume implements Volume {
|
||||
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
||||
checkProvidedMountPoint(customMountPoint);
|
||||
this.mountPoint = customMountPoint;
|
||||
this.createdTemporaryMountPoint = false;
|
||||
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
|
||||
} else {
|
||||
this.mountPoint = createTemporaryMountPoint();
|
||||
this.createdTemporaryMountPoint = true;
|
||||
this.mountPoint = prepareTemporaryMountPoint();
|
||||
LOG.debug("Successfully created mount point: {}", mountPoint);
|
||||
}
|
||||
mount(fs.getPath("/"));
|
||||
@@ -70,20 +67,31 @@ public class FuseVolume implements Volume {
|
||||
}
|
||||
}
|
||||
|
||||
private Path createTemporaryMountPoint() throws IOException {
|
||||
Path parent = Paths.get(SystemUtils.IS_OS_MAC ? DEFAULT_MOUNTROOTPATH_MAC : DEFAULT_MOUNTROOTPATH_LINUX);
|
||||
private Path prepareTemporaryMountPoint() throws IOException, VolumeException {
|
||||
Path mountPoint = chooseNonExistingTemporaryMountPoint();
|
||||
// https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592:
|
||||
// In order to allow non-admin users to mount FUSE volumes in `/Volumes`,
|
||||
// starting with version 3.5.0, FUSE will create non-existent mount points automatically.
|
||||
if (IS_MAC && mountPoint.getParent().equals(Paths.get("/Volumes"))) {
|
||||
return mountPoint;
|
||||
} else {
|
||||
Files.createDirectories(mountPoint);
|
||||
this.createdTemporaryMountPoint = true;
|
||||
return mountPoint;
|
||||
}
|
||||
}
|
||||
|
||||
private Path chooseNonExistingTemporaryMountPoint() throws VolumeException {
|
||||
Path parent = environment.getMountPointsDir().orElseThrow();
|
||||
String basename = vaultSettings.getId();
|
||||
for (int i = 0; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
|
||||
try {
|
||||
Path mountPath = parent.resolve(basename + "_" + i);
|
||||
Files.createDirectory(mountPath);
|
||||
return mountPath;
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
continue;
|
||||
Path mountPoint = parent.resolve(basename + "_" + i);
|
||||
if (Files.notExists(mountPoint)) {
|
||||
return mountPoint;
|
||||
}
|
||||
}
|
||||
LOG.error("Failed to create mount path at {}/{}_x. Giving up after {} attempts.", parent, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
|
||||
throw new FileAlreadyExistsException(parent.toString() + "/" + basename);
|
||||
LOG.error("Failed to find feasible mountpoint at {}/{}_x. Giving up after {} attempts.", parent, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
|
||||
throw new VolumeException("Did not find feasible mount point.");
|
||||
}
|
||||
|
||||
private void mount(Path root) throws VolumeException {
|
||||
@@ -121,7 +129,7 @@ public class FuseVolume implements Volume {
|
||||
} catch (CommandFailedException e) {
|
||||
throw new VolumeException(e);
|
||||
}
|
||||
deleteTemporaryMountPoint();
|
||||
cleanupTemporaryMountPoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,10 +140,10 @@ public class FuseVolume implements Volume {
|
||||
} catch (CommandFailedException e) {
|
||||
throw new VolumeException(e);
|
||||
}
|
||||
deleteTemporaryMountPoint();
|
||||
cleanupTemporaryMountPoint();
|
||||
}
|
||||
|
||||
private void deleteTemporaryMountPoint() {
|
||||
private void cleanupTemporaryMountPoint() {
|
||||
if (createdTemporaryMountPoint) {
|
||||
try {
|
||||
Files.delete(mountPoint);
|
||||
|
||||
@@ -18,7 +18,6 @@ import javafx.beans.property.SimpleObjectProperty;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.LazyInitializer;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
@@ -52,9 +51,7 @@ public class Vault {
|
||||
public static final Predicate<Vault> NOT_LOCKED = hasState(State.LOCKED).negate();
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
|
||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
||||
private static final String LOCALHOST_ALIAS = "cryptomator-vault";
|
||||
|
||||
private final Settings settings;
|
||||
private final VaultSettings vaultSettings;
|
||||
private final Provider<Volume> volumeProvider;
|
||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem = new AtomicReference<>();
|
||||
@@ -67,8 +64,7 @@ public class Vault {
|
||||
}
|
||||
|
||||
@Inject
|
||||
Vault(Settings settings, VaultSettings vaultSettings, Provider<Volume> volumeProvider) {
|
||||
this.settings = settings;
|
||||
Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.volumeProvider = volumeProvider;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.ui.model.VaultModule.PerVault;
|
||||
|
||||
import dagger.Subcomponent;
|
||||
@@ -17,7 +19,9 @@ public interface VaultComponent {
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
Builder vaultModule(VaultModule module);
|
||||
|
||||
@BindsInstance
|
||||
Builder vaultSettings(VaultSettings vaultSettings);
|
||||
|
||||
VaultComponent build();
|
||||
}
|
||||
|
||||
@@ -8,15 +8,14 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
public class VaultFactory {
|
||||
|
||||
private final VaultComponent.Builder vaultComponentBuilder;
|
||||
@@ -32,8 +31,7 @@ public class VaultFactory {
|
||||
}
|
||||
|
||||
private Vault create(VaultSettings vaultSettings) {
|
||||
VaultModule module = new VaultModule(vaultSettings);
|
||||
VaultComponent comp = vaultComponentBuilder.vaultModule(module).build();
|
||||
VaultComponent comp = vaultComponentBuilder.vaultSettings(vaultSettings).build();
|
||||
return comp.vault();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,22 +5,19 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.TransformationList;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
|
||||
@Singleton
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class VaultList extends TransformationList<Vault, VaultSettings> {
|
||||
|
||||
private final VaultFactory vaultFactory;
|
||||
|
||||
@@ -5,37 +5,22 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.inject.Scope;
|
||||
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Module
|
||||
public class VaultModule {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class);
|
||||
private final VaultSettings vaultSettings;
|
||||
|
||||
public VaultModule(VaultSettings vaultSettings) {
|
||||
this.vaultSettings = Objects.requireNonNull(vaultSettings);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@PerVault
|
||||
public VaultSettings provideVaultSettings() {
|
||||
return vaultSettings;
|
||||
}
|
||||
|
||||
@Scope
|
||||
@Documented
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
@@ -13,13 +18,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
public final class WindowsDriveLetters {
|
||||
|
||||
private static final Set<Character> D_TO_Z;
|
||||
|
||||
@@ -3,17 +3,18 @@
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.ui.model.upgrade;
|
||||
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
public class UpgradeStrategies {
|
||||
|
||||
private final Collection<UpgradeStrategy> strategies;
|
||||
@@ -3,7 +3,7 @@
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.ui.model.upgrade;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -19,6 +19,7 @@ import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.api.KeyFile;
|
||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -3,25 +3,24 @@
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.ui.model.upgrade;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3DropBundleExtension.class);
|
||||
@@ -3,10 +3,20 @@
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.ui.model.upgrade;
|
||||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.common.MessageDigestSupplier;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.FileVisitResult;
|
||||
@@ -19,22 +29,13 @@ import java.util.EnumSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.common.MessageDigestSupplier;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Contains the collective knowledge of all creatures who were alive during the development of vault format 3.
|
||||
* This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these.
|
||||
*/
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
class UpgradeVersion3to4 extends UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3to4.class);
|
||||
@@ -3,8 +3,18 @@
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.ui.model.upgrade;
|
||||
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.api.FileHeader;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
@@ -18,21 +28,11 @@ import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.EnumSet;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.api.FileHeader;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Contains the collective knowledge of all creatures who were alive during the development of vault format 3.
|
||||
* This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these.
|
||||
*/
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
class UpgradeVersion4to5 extends UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion4to5.class);
|
||||
@@ -3,23 +3,23 @@
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
package org.cryptomator.ui.model.upgrade;
|
||||
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.cryptofs.migration.Migrators;
|
||||
import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException;
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
|
||||
@FxApplicationScoped
|
||||
class UpgradeVersion5toX extends UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion5toX.class);
|
||||
@@ -8,24 +8,21 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
|
||||
@Singleton
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class PasswordStrengthUtil {
|
||||
|
||||
private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length
|
||||
|
||||
@@ -38,15 +38,15 @@
|
||||
<children>
|
||||
<!-- Row 0 -->
|
||||
<Label text="%changePassword.label.oldPassword" GridPane.rowIndex="0" GridPane.columnIndex="0" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="oldPasswordField" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="oldPasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 1 -->
|
||||
<Label text="%changePassword.label.newPassword" GridPane.rowIndex="1" GridPane.columnIndex="0" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="newPasswordField" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="newPasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 2 -->
|
||||
<Label text="%changePassword.label.retypePassword" GridPane.rowIndex="2" GridPane.columnIndex="0" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="retypePasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 3 -->
|
||||
<VBox GridPane.columnIndex="1" GridPane.rowIndex="3" spacing="6.0">
|
||||
|
||||
@@ -36,11 +36,11 @@
|
||||
<children>
|
||||
<!-- Row 0 -->
|
||||
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.password" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="0" GridPane.columnIndex="1" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="0" GridPane.columnIndex="1" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 1 -->
|
||||
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%initialize.label.retypePassword" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="1" GridPane.columnIndex="1" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="retypePasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="1" GridPane.columnIndex="1" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 2 -->
|
||||
<VBox GridPane.columnIndex="1" GridPane.rowIndex="2" spacing="6.0">
|
||||
|
||||
@@ -56,5 +56,4 @@
|
||||
|
||||
</children>
|
||||
</GridPane>
|
||||
<Label VBox.vgrow="NEVER" text="%settings.requiresRestartLabel" alignment="CENTER" cacheShape="true" cache="true" />
|
||||
</VBox>
|
||||
@@ -34,7 +34,7 @@
|
||||
<children>
|
||||
<!-- Row 0 -->
|
||||
<Label text="%unlock.label.password" GridPane.rowIndex="0" GridPane.columnIndex="0" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
<SecPasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||
|
||||
<!-- Row 1 -->
|
||||
<HBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="12.0" alignment="CENTER_RIGHT" cacheShape="true" cache="true">
|
||||
@@ -43,7 +43,7 @@
|
||||
</HBox>
|
||||
|
||||
<!-- Row 3 -->
|
||||
<Label fx:id="successMessage" cacheShape="true" cache="true" visible="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2"/>
|
||||
<Text fx:id="messageText" cache="true" visible="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2"/>
|
||||
|
||||
<!-- Row 3 -->
|
||||
<GridPane fx:id="advancedOptions" vgap="12.0" hgap="12.0" prefWidth="400.0" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" visible="false" cacheShape="true" cache="true">
|
||||
@@ -112,7 +112,7 @@
|
||||
<!-- Row 5 -->
|
||||
<VBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="12.0" alignment="CENTER" cacheShape="true" cache="true">
|
||||
<ProgressIndicator progress="-1" fx:id="progressIndicator" cacheShape="true" cache="true" cacheHint="SPEED" />
|
||||
<Text fx:id="messageText" cache="true" />
|
||||
<Text fx:id="progressText" cache="true" />
|
||||
</VBox>
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
@@ -7,22 +7,17 @@
|
||||
Contributors:
|
||||
Sebastian Stenzel - initial API and implementation
|
||||
-->
|
||||
<?import java.net.URL?>
|
||||
<?import java.lang.String?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
|
||||
<?import javafx.scene.chart.LineChart?>
|
||||
<?import javafx.scene.chart.NumberAxis?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.control.ContextMenu?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import javafx.scene.control.ToggleButton?>
|
||||
<?import javafx.scene.control.ContextMenu?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox fx:controller="org.cryptomator.ui.controllers.UnlockedController" fx:id="root" prefWidth="400.0" prefHeight="400.0" spacing="6.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
|
||||
<fx:define>
|
||||
|
||||
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = دخول تلقائي
|
||||
unlock.errorMessage.wrongPassword = كلمة مرور خاطئة
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = محفظة غير مدعومة . هذه المحفظة تم انشاؤها بواسطة اصدار اقدم من كريبتوماتور
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = محفظة غير مدعومة . هذه المحفظة تم انشاؤها بواسطة اصدار احدث من كريبتوماتور
|
||||
unlock.messageLabel.startServerFailed = Starting WebDAV server failed.
|
||||
# change_password.fxml
|
||||
changePassword.label.oldPassword = كلمة المرور القديمة
|
||||
changePassword.label.newPassword = كلمة المرور الجديدة
|
||||
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Throughput (MiB/s)
|
||||
# settings.fxml
|
||||
settings.version.label = الاصدار %s
|
||||
settings.checkForUpdates.label = افحص التحديثات
|
||||
settings.requiresRestartLabel = * Cryptomator needs to restart
|
||||
# tray icon
|
||||
tray.menu.open = فتح
|
||||
tray.menu.quit = اغلاق
|
||||
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Migration failed due to an I/O Exception. See log f
|
||||
upgrade.confirmation.label = Yes, I've made sure that synchronization has finished
|
||||
unlock.label.savePassword = حفظ كلمة المرور
|
||||
unlock.errorMessage.unauthenticVersionMac = Could not authenticate version MAC.
|
||||
unlocked.label.mountFailed = Connecting drive failed
|
||||
unlock.savePassword.delete.confirmation.title = حذف كلمة المرور المحفوظة
|
||||
unlock.savePassword.delete.confirmation.header = Do you really want to delete the saved password of this vault?
|
||||
unlock.savePassword.delete.confirmation.content = The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled.
|
||||
settings.debugMode.label = Debug Mode *
|
||||
settings.debugMode.label = Debug Mode
|
||||
upgrade.version3dropBundleExtension.title = Vault Version 3 Upgrade (Drop Bundle Extension)
|
||||
upgrade.version3to4.title = Vault Version 3 to 4 Upgrade
|
||||
upgrade.version4to5.title = Vault Version 4 to 5 Upgrade
|
||||
@@ -105,7 +102,7 @@ settings.volume.webdav = WebDAV
|
||||
settings.volume.fuse = FUSE
|
||||
unlock.successLabel.vaultCreated = Vault was successfully created.
|
||||
unlock.successLabel.passwordChanged = Password was successfully changed.
|
||||
unlock.successLabel.upgraded = Cryptomator was successfully upgraded.
|
||||
unlock.successLabel.upgraded = Vault was successfully upgraded.
|
||||
unlock.label.useOwnMountPath = Use Custom Mount Point
|
||||
welcome.askForUpdateCheck.dialog.title = Update check
|
||||
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
|
||||
@@ -114,12 +111,14 @@ settings.volume.dokany = Dokany
|
||||
main.gracefulShutdown.dialog.title = Locking vault(s) failed
|
||||
main.gracefulShutdown.dialog.header = Vault(s) in use
|
||||
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
|
||||
main.gracefulShutdown.button.tryAgain = Try again
|
||||
main.gracefulShutdown.button.forceShutdown = Force shutdown
|
||||
main.gracefulShutdown.button.tryAgain = Try Again
|
||||
main.gracefulShutdown.button.forceShutdown = Force Shutdown
|
||||
unlock.pendingMessage.unlocking = Unlocking vault...
|
||||
unlock.failedDialog.title = Unlock failed
|
||||
unlock.failedDialog.header = Unlock failed
|
||||
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
|
||||
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
|
||||
unlock.label.useReadOnlyMode = Read-Only
|
||||
unlock.label.chooseMountPath = Choose empty directory…
|
||||
unlock.label.chooseMountPath = Choose empty directory…
|
||||
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
|
||||
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
|
||||
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Автоматично наименова
|
||||
unlock.errorMessage.wrongPassword = Неправилна парола
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Неподържана версия. Този сейф е бил създаден със стара версия на Криптоматор.
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Неподържана версия. Този сейф е бил създаден с по-нова версия на Криптоматор.
|
||||
unlock.messageLabel.startServerFailed = Неуспешно стартиране на WebDAV сървъра.
|
||||
# change_password.fxml
|
||||
changePassword.label.oldPassword = Стара парола
|
||||
changePassword.label.newPassword = Нова парола
|
||||
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Скорост (MB/s)
|
||||
# settings.fxml
|
||||
settings.version.label = Версия %s
|
||||
settings.checkForUpdates.label = Проверка за обновления
|
||||
settings.requiresRestartLabel = * Криптоматор трябва да се рестартира
|
||||
# tray icon
|
||||
tray.menu.open = Отворяне
|
||||
tray.menu.quit = Изход
|
||||
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Преместването е отменено по
|
||||
upgrade.confirmation.label = Да, сигурен съм, че сихронизацията е приключила
|
||||
unlock.label.savePassword = Запазване на парола
|
||||
unlock.errorMessage.unauthenticVersionMac = Неуспешна оторизация на MAC версията
|
||||
unlocked.label.mountFailed = Връзката с диска неуспешна
|
||||
unlock.savePassword.delete.confirmation.title = Изтриване на запазената парола
|
||||
unlock.savePassword.delete.confirmation.header = Неистина ли искате да изтриете запазената парола за този сейф?
|
||||
unlock.savePassword.delete.confirmation.content = Запазената парола за този сейф ще бъде незабавно премахната от Вашата система. Ако желаете да запазите паролата отново, трябва да отключите сейса с пусната опция "Запазване на павола".
|
||||
settings.debugMode.label = Режим за отстраняване на грешки *
|
||||
settings.debugMode.label = Режим за отстраняване на грешки
|
||||
upgrade.version3dropBundleExtension.title = Обновяване до сейф версия 3
|
||||
upgrade.version3to4.title = Обновяване на сейф от 3-та до 4-та версия
|
||||
upgrade.version4to5.title = Обновяване на сейф от 4-та до 5-та версия
|
||||
@@ -114,12 +111,14 @@ settings.volume.dokany = Dokany
|
||||
main.gracefulShutdown.dialog.title = Locking vault(s) failed
|
||||
main.gracefulShutdown.dialog.header = Vault(s) in use
|
||||
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
|
||||
main.gracefulShutdown.button.tryAgain = Try again
|
||||
main.gracefulShutdown.button.forceShutdown = Force shutdown
|
||||
main.gracefulShutdown.button.tryAgain = Try Again
|
||||
main.gracefulShutdown.button.forceShutdown = Force Shutdown
|
||||
unlock.pendingMessage.unlocking = Unlocking vault...
|
||||
unlock.failedDialog.title = Unlock failed
|
||||
unlock.failedDialog.header = Unlock failed
|
||||
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
|
||||
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
|
||||
unlock.label.useReadOnlyMode = Read-Only
|
||||
unlock.label.chooseMountPath = Choose empty directory…
|
||||
unlock.label.chooseMountPath = Choose empty directory…
|
||||
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
|
||||
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
|
||||
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Assigna automàticament
|
||||
unlock.errorMessage.wrongPassword = Contrasenya incorrecta
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = La caixa forta no és compatible. Aquesta caixa forta s'ha creat amb una versió anterior de Cryptomator.
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = La caixa forta no és compatible. Aquesta caixa forta s'ha creat amb una versió més nova de Cryptomator.
|
||||
unlock.messageLabel.startServerFailed = S'ha produït un error en iniciar el servidor WebDAV.
|
||||
# change_password.fxml
|
||||
changePassword.label.oldPassword = Contrasenya antiga
|
||||
changePassword.label.newPassword = Contrasenya nova
|
||||
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Velocitat de transferència de dades (MiB/s)
|
||||
# settings.fxml
|
||||
settings.version.label = Versió %s
|
||||
settings.checkForUpdates.label = Comprova si hi ha actualitzacions
|
||||
settings.requiresRestartLabel = És necessari reiniciar * Cryptomator
|
||||
# tray icon
|
||||
tray.menu.open = Obri
|
||||
tray.menu.quit = Surt
|
||||
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Error en la migració degut a una excepció de E/S.
|
||||
upgrade.confirmation.label = Sí, m'he assegurat que la sincronització hagi acabat
|
||||
unlock.label.savePassword = Desa la contrasenya
|
||||
unlock.errorMessage.unauthenticVersionMac = No s'ha pogut autenticar la versió de MAC.
|
||||
unlocked.label.mountFailed = Ha fallat el muntatge de la unitat
|
||||
unlock.savePassword.delete.confirmation.title = Elimina la contrasenya desada
|
||||
unlock.savePassword.delete.confirmation.header = Esteu segur que voleu eliminar la contrasenya desada d'aquesta unitat?
|
||||
unlock.savePassword.delete.confirmation.content = La contrasenya desada d'aquesta caixa forta va a ser eliminada inmediatament del clauer del seu sistema. Si voleu tornar a desar la contrasenya haureu de tornar a desbloquejar la vostra caixa forta i activar l'opció "Desa la contrasenya".
|
||||
settings.debugMode.label = Mode de depuració *
|
||||
settings.debugMode.label = Mode de depuració
|
||||
upgrade.version3dropBundleExtension.title = Actualitza la caixa forta a la versió 3 (Drop Bundle Extension)
|
||||
upgrade.version3to4.title = Actualitza la caixa forta de la versió 3 a la 4
|
||||
upgrade.version4to5.title = Actualitza la caixa forta de la versió 4 a la 5
|
||||
@@ -117,9 +114,11 @@ main.gracefulShutdown.dialog.content = Hi ha programes encara estan utilitzant u
|
||||
main.gracefulShutdown.button.tryAgain = Torna-ho a intentar
|
||||
main.gracefulShutdown.button.forceShutdown = Força l'aturada
|
||||
unlock.pendingMessage.unlocking = La caixa forta s'està desbloquejant...
|
||||
unlock.failedDialog.title = Unlock failed
|
||||
unlock.failedDialog.header = Unlock failed
|
||||
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
|
||||
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
|
||||
unlock.label.useReadOnlyMode = Read-Only
|
||||
unlock.label.chooseMountPath = Choose empty directory…
|
||||
unlock.failedDialog.title = El desbloqueig ha fallat
|
||||
unlock.failedDialog.header = El desbloqueig ha fallat
|
||||
unlock.failedDialog.content.mountPathNonExisting = El punt de muntatge no existeix.
|
||||
unlock.failedDialog.content.mountPathNotEmpty = El punt de muntatge no és buit
|
||||
unlock.label.useReadOnlyMode = Només de lectura
|
||||
unlock.label.chooseMountPath = Trieu un directori buit...
|
||||
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
|
||||
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
|
||||
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Přiřadit automaticky
|
||||
unlock.errorMessage.wrongPassword = Chybné heslo
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Nepodporovaná verze trezoru. Byl vytvořen ve starším Cryptomator.
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Nepodporovaná verze trezoru. Byl vytvořen v novějším Cryptomator.
|
||||
unlock.messageLabel.startServerFailed = Spuštění WebDAV serveru se nezdařílo.
|
||||
# change_password.fxml
|
||||
changePassword.label.oldPassword = Původní heslo
|
||||
changePassword.label.newPassword = Nové heslo
|
||||
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Propustnost (MiB/s)
|
||||
# settings.fxml
|
||||
settings.version.label = Verze %s
|
||||
settings.checkForUpdates.label = Zjistit případné aktualizace
|
||||
settings.requiresRestartLabel = * Vyžaduje restart Cryptomator
|
||||
# tray icon
|
||||
tray.menu.open = Otevřít
|
||||
tray.menu.quit = Ukončit
|
||||
@@ -76,11 +74,10 @@ upgrade.version3to4.err.io = Převod se nezdařil kvůli výjimce na vst./výst.
|
||||
upgrade.confirmation.label = Ano, je ověřeno, že synchronizace byla dokončena
|
||||
unlock.label.savePassword = Uložit heslo
|
||||
unlock.errorMessage.unauthenticVersionMac = Nedaří se ověřit MAC funkci verze.
|
||||
unlocked.label.mountFailed = Připojení jednotky se nezdařilo
|
||||
unlock.savePassword.delete.confirmation.title = Smazat uložené heslo
|
||||
unlock.savePassword.delete.confirmation.header = Opravdu chcete smazat uložené heslo pro tento trezor?
|
||||
unlock.savePassword.delete.confirmation.content = Uložené heslo k tomuto trezoru bude okamžitě vymazáno ze systémové klíčenky. Pokud ho tam budete chtít znovu uložit, bude třeba trezor odemknout se zapnutou volbou „Uložit heslo“.
|
||||
settings.debugMode.label = Ladící režim *
|
||||
settings.debugMode.label = Ladící režim
|
||||
# Extension of what please? File, protocol, aplication extension for example? And bundle of what with what? Thanks :)
|
||||
upgrade.version3dropBundleExtension.title = Přechod z verze 3 trezoru na novější (odebrat příp. .cryptomator a registraci bundle v macOS)
|
||||
upgrade.version3to4.title = Aktualizace trezoru z verze 3 na 4
|
||||
@@ -119,9 +116,11 @@ main.gracefulShutdown.dialog.content = Jeden nebo více trezorů je stále v pou
|
||||
main.gracefulShutdown.button.tryAgain = Zkusit znovu
|
||||
main.gracefulShutdown.button.forceShutdown = Vynutit vypnutí
|
||||
unlock.pendingMessage.unlocking = Odemykání trezoru…
|
||||
unlock.failedDialog.title = Unlock failed
|
||||
unlock.failedDialog.header = Unlock failed
|
||||
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
|
||||
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
|
||||
unlock.label.useReadOnlyMode = Read-Only
|
||||
unlock.label.chooseMountPath = Choose empty directory…
|
||||
unlock.failedDialog.title = Odemknutí se nezdařilo
|
||||
unlock.failedDialog.header = Odemknutí se nezdařilo
|
||||
unlock.failedDialog.content.mountPathNonExisting = Přípojný bod neexistuje.
|
||||
unlock.failedDialog.content.mountPathNotEmpty = Přípojný bod není prázdný.
|
||||
unlock.label.useReadOnlyMode = Pouze pro čtení
|
||||
unlock.label.chooseMountPath = Zvolte prázdnou složku…
|
||||
ctrl.secPasswordField.nonPrintableChars = Heslo obsahuje řídící znaky.\nDoporučení\: Odeberete je kvůli kompatibilitě s ostatními klienty.
|
||||
ctrl.secPasswordField.capsLocked = Je zapnutá klávesa Caps Lock.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user