mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 16:04:28 +00:00
Compare commits
82
Commits
1.4.2
..
1.4.6-beta1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
207bfee6e5 | ||
|
|
d08f3d03d0 | ||
|
|
a88bd81347 | ||
|
|
8e2d2b899e | ||
|
|
3b4870a98a | ||
|
|
ef5eabdb79 | ||
|
|
f52b2f323a | ||
|
|
7e60e5606c | ||
|
|
8e2fa082cc | ||
|
|
d8ef402607 | ||
|
|
748f1be0c5 | ||
|
|
608d54a8f2 | ||
|
|
50b167e28f | ||
|
|
b7d06783dd | ||
|
|
5ef3d23970 | ||
|
|
200a195f3b | ||
|
|
db0aceefdf | ||
|
|
492e986608 | ||
|
|
98cab7e4d8 | ||
|
|
384c9de7aa | ||
|
|
528005a623 | ||
|
|
c78d4c2d0e | ||
|
|
c8e9201692 | ||
|
|
86d8599d07 | ||
|
|
744225cf7a | ||
|
|
54f2667e45 | ||
|
|
fccd02a7e8 | ||
|
|
7e46957bcb |
Generated
+3
@@ -29,5 +29,8 @@
|
|||||||
<module name="ui" />
|
<module name="ui" />
|
||||||
</profile>
|
</profile>
|
||||||
</annotationProcessing>
|
</annotationProcessing>
|
||||||
|
<bytecodeTargetLevel>
|
||||||
|
<module name="buildkit" target="11" />
|
||||||
|
</bytecodeTargetLevel>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
Generated
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<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" 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/commons" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/main/keychain" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/main/keychain" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/main/launcher" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/main/launcher" charset="UTF-8" />
|
||||||
|
|||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
Generated
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</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" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
Generated
+9
@@ -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>
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
<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/"" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$MAVEN_REPOSITORY$" />
|
||||||
|
<extension name="coverage">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="org.cryptomator.launcher.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
+10
-29
@@ -1,7 +1,7 @@
|
|||||||
language: java
|
language: java
|
||||||
sudo: false
|
sudo: false
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk9
|
- openjdk11
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- $HOME/.m2
|
- $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)
|
mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=SNAPSHOT-$(echo $TRAVIS_COMMIT | head -c7)
|
||||||
fi
|
fi
|
||||||
- mvn -fmain/pom.xml clean package -Prelease -DskipTests
|
- 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:
|
deploy:
|
||||||
- provider: script # SNAPSHOTS
|
- provider: bintray # SNAPSHOTS
|
||||||
|
file: .travis-deploy-snapshot.json
|
||||||
|
user: cryptobot
|
||||||
|
key: $BINTRAY_API_KEY
|
||||||
skip_cleanup: true
|
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:
|
on:
|
||||||
repo: cryptomator/cryptomator
|
repo: cryptomator/cryptomator
|
||||||
branch: develop
|
branch: develop
|
||||||
condition: $TRAVIS_TAG = ''
|
- provider: bintray # RELEASES
|
||||||
- provider: releases # RELEASE
|
file: .travis-deploy-release.json
|
||||||
prerelease: false
|
user: cryptobot
|
||||||
api_key: $GITHUB_API_KEY
|
key: $BINTRAY_API_KEY
|
||||||
file:
|
|
||||||
- "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar"
|
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
on:
|
on:
|
||||||
repo: cryptomator/cryptomator
|
repo: cryptomator/cryptomator
|
||||||
tags: true
|
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
|
|
||||||
|
|||||||
@@ -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.2</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>
|
<format>zip</format>
|
||||||
</formats>
|
</formats>
|
||||||
<fileSets>
|
<fileSets>
|
||||||
|
<fileSet>
|
||||||
|
<directory>target/</directory>
|
||||||
|
<includes>
|
||||||
|
<include>version.txt</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>libs</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target/libs</directory>
|
<directory>target/libs</directory>
|
||||||
<includes>
|
<includes>
|
||||||
@@ -15,24 +22,11 @@
|
|||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target/fixed-binaries</directory>
|
<directory>target/linux-libs</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>
|
|
||||||
<includes>
|
<includes>
|
||||||
<include>build.xml</include>
|
<include>*.jar</include>
|
||||||
<include>logback.xml</include>
|
|
||||||
</includes>
|
</includes>
|
||||||
<filtered>false</filtered>
|
<outputDirectory>libs</outputDirectory>
|
||||||
<outputDirectory>.</outputDirectory>
|
|
||||||
</fileSet>
|
</fileSet>
|
||||||
</fileSets>
|
</fileSets>
|
||||||
</assembly>
|
</assembly>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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.5.0-SNAPSHOT</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>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
${project.version}
|
||||||
@@ -4,13 +4,19 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.4.2</version>
|
<version>1.5.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
<name>Cryptomator Commons</name>
|
<name>Cryptomator Commons</name>
|
||||||
<description>Shared utilities</description>
|
<description>Shared utilities</description>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- JavaFx -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-base</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Libs -->
|
<!-- Libs -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<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 {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.nio.file.FileSystem;
|
import java.nio.file.FileSystem;
|
||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -27,6 +28,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
@@ -34,6 +36,7 @@ import javax.inject.Singleton;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.common.LazyInitializer;
|
import org.cryptomator.common.LazyInitializer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -45,92 +48,77 @@ import com.google.gson.GsonBuilder;
|
|||||||
public class SettingsProvider implements Provider<Settings> {
|
public class SettingsProvider implements Provider<Settings> {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
|
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
|
||||||
private static final Path DEFAULT_SETTINGS_PATH;
|
|
||||||
private static final long SAVE_DELAY_MS = 1000;
|
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 ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
|
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
|
||||||
private final AtomicReference<Settings> settings = new AtomicReference<>();
|
private final AtomicReference<Settings> settings = new AtomicReference<>();
|
||||||
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter();
|
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter();
|
||||||
|
private final Environment env;
|
||||||
private final Gson gson;
|
private final Gson gson;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SettingsProvider() {
|
public SettingsProvider(Environment env) {
|
||||||
|
this.env = env;
|
||||||
this.gson = new GsonBuilder() //
|
this.gson = new GsonBuilder() //
|
||||||
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
|
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
|
||||||
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
|
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
|
||||||
.create();
|
.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
|
@Override
|
||||||
public Settings get() {
|
public Settings get() {
|
||||||
return LazyInitializer.initializeLazily(settings, this::load);
|
return LazyInitializer.initializeLazily(settings, this::load);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Settings load() {
|
private Settings load() {
|
||||||
Settings settings;
|
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElse(new Settings());
|
||||||
final Path settingsPath = getSettingsPath();
|
settings.setSaveCmd(this::scheduleSave);
|
||||||
try (InputStream in = Files.newInputStream(settingsPath, StandardOpenOption.READ); //
|
return settings;
|
||||||
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
}
|
||||||
settings = gson.fromJson(reader, Settings.class);
|
|
||||||
|
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) {
|
if (settings == null) {
|
||||||
throw new IOException("Unexpected EOF");
|
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) {
|
} catch (IOException e) {
|
||||||
LOG.info("Failed to load settings, creating new one.");
|
LOG.warn("Exception while loading settings from " + path, e);
|
||||||
settings = new Settings();
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
settings.setSaveCmd(this::scheduleSave);
|
|
||||||
return settings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleSave(Settings settings) {
|
private void scheduleSave(Settings settings) {
|
||||||
if (settings == null) {
|
if (settings == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ScheduledFuture<?> saveCmd = saveScheduler.schedule(() -> {
|
final Optional<Path> settingsPath = env.getSettingsPath().findFirst(); // alway save to preferred (first) path
|
||||||
this.save(settings);
|
settingsPath.ifPresent(path -> {
|
||||||
}, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
|
Runnable saveCommand = () -> this.save(settings, path);
|
||||||
ScheduledFuture<?> previousSaveCmd = scheduledSaveCmd.getAndSet(saveCmd);
|
ScheduledFuture<?> scheduledTask = saveScheduler.schedule(saveCommand, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||||
if (previousSaveCmd != null) {
|
ScheduledFuture<?> previouslyScheduledTask = scheduledSaveCmd.getAndSet(scheduledTask);
|
||||||
previousSaveCmd.cancel(false);
|
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";
|
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 {
|
try {
|
||||||
Files.createDirectories(settingsPath.getParent());
|
Files.createDirectories(settingsPath.getParent());
|
||||||
try (OutputStream out = Files.newOutputStream(settingsPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
|
try (OutputStream out = Files.newOutputStream(settingsPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
|
||||||
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
|
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
|
||||||
gson.toJson(settings, writer);
|
gson.toJson(settings, writer);
|
||||||
LOG.info("Settings saved to " + settingsPath);
|
LOG.info("Settings saved to {}", settingsPath);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Failed to save settings.", e);
|
LOG.error("Failed to save settings.", e);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
@@ -20,6 +21,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,6 +33,7 @@ public class VaultSettings {
|
|||||||
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
|
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
|
||||||
public static final boolean DEFAULT_REAVEAL_AFTER_MOUNT = true;
|
public static final boolean DEFAULT_REAVEAL_AFTER_MOUNT = true;
|
||||||
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
|
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
|
||||||
|
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
|
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
|
||||||
@@ -40,6 +43,7 @@ public class VaultSettings {
|
|||||||
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
|
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
|
||||||
private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
|
private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
|
||||||
private final StringProperty individualMountPath = new SimpleStringProperty();
|
private final StringProperty individualMountPath = new SimpleStringProperty();
|
||||||
|
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
||||||
|
|
||||||
public VaultSettings(String id) {
|
public VaultSettings(String id) {
|
||||||
this.id = Objects.requireNonNull(id);
|
this.id = Objects.requireNonNull(id);
|
||||||
@@ -48,7 +52,7 @@ public class VaultSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Observable[] observables() {
|
Observable[] observables() {
|
||||||
return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, usesIndividualMountPath, individualMountPath};
|
return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, usesIndividualMountPath, individualMountPath, usesReadOnlyMode};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deriveMountNameFromPath(Path path) {
|
private void deriveMountNameFromPath(Path path) {
|
||||||
@@ -131,6 +135,18 @@ public class VaultSettings {
|
|||||||
return individualMountPath;
|
return individualMountPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<String> getIndividualMountPath() {
|
||||||
|
if (usesIndividualMountPath.get()) {
|
||||||
|
return Optional.ofNullable(Strings.emptyToNull(individualMountPath.get()));
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty usesReadOnlyMode() {
|
||||||
|
return usesReadOnlyMode;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hashcode/Equals */
|
/* Hashcode/Equals */
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+6
@@ -27,6 +27,7 @@ class VaultSettingsJsonAdapter {
|
|||||||
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
||||||
out.name("usesIndividualMountPath").value(value.usesIndividualMountPath().get());
|
out.name("usesIndividualMountPath").value(value.usesIndividualMountPath().get());
|
||||||
out.name("individualMountPath").value(value.individualMountPath().get()); //TODO: should this always be written? ( because it could contain metadata, which the user may not want to save!)
|
out.name("individualMountPath").value(value.individualMountPath().get()); //TODO: should this always be written? ( because it could contain metadata, which the user may not want to save!)
|
||||||
|
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ class VaultSettingsJsonAdapter {
|
|||||||
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
||||||
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
|
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
|
||||||
boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
||||||
|
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||||
|
|
||||||
in.beginObject();
|
in.beginObject();
|
||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
@@ -68,6 +70,9 @@ class VaultSettingsJsonAdapter {
|
|||||||
case "individualMountPath":
|
case "individualMountPath":
|
||||||
individualMountPath = in.nextString();
|
individualMountPath = in.nextString();
|
||||||
break;
|
break;
|
||||||
|
case "usesReadOnlyMode":
|
||||||
|
usesReadOnlyMode = in.nextBoolean();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
@@ -83,6 +88,7 @@ class VaultSettingsJsonAdapter {
|
|||||||
vaultSettings.revealAfterMount().set(revealAfterMount);
|
vaultSettings.revealAfterMount().set(revealAfterMount);
|
||||||
vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath);
|
vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath);
|
||||||
vaultSettings.individualMountPath().set(individualMountPath);
|
vaultSettings.individualMountPath().set(individualMountPath);
|
||||||
|
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
||||||
return vaultSettings;
|
return vaultSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
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 java.util.Comparator;
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class SemVerComparatorTest {
|
public class SemVerComparatorTest {
|
||||||
|
|
||||||
@@ -21,38 +21,38 @@ public class SemVerComparatorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void compareEqualVersions() {
|
public void compareEqualVersions() {
|
||||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4")));
|
Assertions.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")));
|
Assertions.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")));
|
Assertions.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-alpha+20170101", "1.23.4-alpha+20171231")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// newer versions in first argument
|
// newer versions in first argument
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void compareHigherToLowerVersions() {
|
public void compareHigherToLowerVersions() {
|
||||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.5", "1.23.4")));
|
Assertions.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")));
|
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.24.4", "1.23.4")));
|
||||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23")));
|
Assertions.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")));
|
Assertions.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")));
|
Assertions.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")));
|
Assertions.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")));
|
Assertions.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.4-56.79", "1.23.4-56.78")));
|
||||||
}
|
}
|
||||||
|
|
||||||
// newer versions in second argument
|
// newer versions in second argument
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void compareLowerToHigherVersions() {
|
public void compareLowerToHigherVersions() {
|
||||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.5")));
|
Assertions.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")));
|
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.24.4")));
|
||||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23", "1.23.4")));
|
Assertions.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")));
|
Assertions.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")));
|
Assertions.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")));
|
Assertions.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")));
|
Assertions.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-56.78", "1.23.4-56.79")));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-9
@@ -5,10 +5,10 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
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 java.io.IOException;
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class SettingsJsonAdapterTest {
|
public class SettingsJsonAdapterTest {
|
||||||
|
|
||||||
@@ -26,12 +26,12 @@ public class SettingsJsonAdapterTest {
|
|||||||
|
|
||||||
Settings settings = adapter.fromJson(json);
|
Settings settings = adapter.fromJson(json);
|
||||||
|
|
||||||
Assert.assertTrue(settings.checkForUpdates().get());
|
Assertions.assertTrue(settings.checkForUpdates().get());
|
||||||
Assert.assertEquals(2, settings.getDirectories().size());
|
Assertions.assertEquals(2, settings.getDirectories().size());
|
||||||
Assert.assertEquals(8080, settings.port().get());
|
Assertions.assertEquals(8080, settings.port().get());
|
||||||
Assert.assertEquals(42, settings.numTrayNotifications().get());
|
Assertions.assertEquals(42, settings.numTrayNotifications().get());
|
||||||
Assert.assertEquals("dav", settings.preferredGvfsScheme().get());
|
Assertions.assertEquals("dav", settings.preferredGvfsScheme().get());
|
||||||
Assert.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
|
Assertions.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
|
|
||||||
public class SettingsTest {
|
public class SettingsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+9
-10
@@ -5,15 +5,14 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
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.IOException;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import com.google.gson.stream.JsonReader;
|
|
||||||
|
|
||||||
public class VaultSettingsJsonAdapterTest {
|
public class VaultSettingsJsonAdapterTest {
|
||||||
|
|
||||||
private final VaultSettingsJsonAdapter adapter = new VaultSettingsJsonAdapter();
|
private final VaultSettingsJsonAdapter adapter = new VaultSettingsJsonAdapter();
|
||||||
@@ -24,11 +23,11 @@ public class VaultSettingsJsonAdapterTest {
|
|||||||
JsonReader jsonReader = new JsonReader(new StringReader(json));
|
JsonReader jsonReader = new JsonReader(new StringReader(json));
|
||||||
|
|
||||||
VaultSettings vaultSettings = adapter.read(jsonReader);
|
VaultSettings vaultSettings = adapter.read(jsonReader);
|
||||||
Assert.assertEquals("foo", vaultSettings.getId());
|
Assertions.assertEquals("foo", vaultSettings.getId());
|
||||||
Assert.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
Assertions.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
||||||
Assert.assertEquals("test", vaultSettings.mountName().get());
|
Assertions.assertEquals("test", vaultSettings.mountName().get());
|
||||||
Assert.assertEquals("X", vaultSettings.winDriveLetter().get());
|
Assertions.assertEquals("X", vaultSettings.winDriveLetter().get());
|
||||||
Assert.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get());
|
Assertions.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
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 {
|
public class VaultSettingsTest {
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,17 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.4.2</version>
|
<version>1.5.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>keychain</artifactId>
|
<artifactId>keychain</artifactId>
|
||||||
<name>System Keychain Access</name>
|
<name>System Keychain Access</name>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.cryptomator</groupId>
|
||||||
|
<artifactId>commons</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
|||||||
+60
-55
@@ -5,30 +5,6 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.keychain;
|
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.common.io.BaseEncoding;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
@@ -42,11 +18,36 @@ import com.google.gson.JsonSerializer;
|
|||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.jni.WinDataProtection;
|
import org.cryptomator.jni.WinDataProtection;
|
||||||
import org.cryptomator.jni.WinFunctions;
|
import org.cryptomator.jni.WinFunctions;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||||
@@ -57,24 +58,13 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
|||||||
.disableHtmlEscaping().create();
|
.disableHtmlEscaping().create();
|
||||||
|
|
||||||
private final Optional<WinFunctions> winFunctions;
|
private final Optional<WinFunctions> winFunctions;
|
||||||
private final Path keychainPath;
|
private final List<Path> keychainPaths;
|
||||||
private Map<String, KeychainEntry> keychainEntries;
|
private Map<String, KeychainEntry> keychainEntries;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions) {
|
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions, Environment environment) {
|
||||||
this.winFunctions = winFunctions;
|
this.winFunctions = winFunctions;
|
||||||
String keychainPathProperty = System.getProperty("cryptomator.keychainPath");
|
this.keychainPaths = environment.getKeychainPath().collect(Collectors.toList());
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private WinDataProtection dataProtection() {
|
private WinDataProtection dataProtection() {
|
||||||
@@ -124,7 +114,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported() {
|
public boolean isSupported() {
|
||||||
return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && keychainPath != null;
|
return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && !keychainPaths.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] generateSalt() {
|
private byte[] generateSalt() {
|
||||||
@@ -138,30 +128,44 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
|||||||
|
|
||||||
private void loadKeychainEntriesIfNeeded() {
|
private void loadKeychainEntriesIfNeeded() {
|
||||||
if (keychainEntries == null) {
|
if (keychainEntries == null) {
|
||||||
loadKeychainEntries();
|
for (Path keychainPath : keychainPaths) {
|
||||||
}
|
Optional<Map<String, KeychainEntry>> keychain = loadKeychainEntries(keychainPath);
|
||||||
assert keychainEntries != null;
|
if (keychain.isPresent()) {
|
||||||
}
|
keychainEntries = keychain.get();
|
||||||
|
break;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
if (keychainEntries == null) {
|
if (keychainEntries == null) {
|
||||||
|
LOG.info("Unable to load existing keychain file, creating new keychain.");
|
||||||
keychainEntries = new HashMap<>();
|
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() {
|
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); //
|
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);
|
GSON.toJson(keychainEntries, writer);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
|
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
|
||||||
@@ -169,6 +173,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class KeychainEntry {
|
private static class KeychainEntry {
|
||||||
|
|
||||||
@SerializedName("ciphertext")
|
@SerializedName("ciphertext")
|
||||||
byte[] ciphertext;
|
byte[] ciphertext;
|
||||||
@SerializedName("salt")
|
@SerializedName("salt")
|
||||||
|
|||||||
@@ -5,20 +5,20 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.keychain;
|
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 java.util.Optional;
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class KeychainModuleTest {
|
public class KeychainModuleTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetKeychain() {
|
public void testGetKeychain() {
|
||||||
Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess();
|
Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess();
|
||||||
Assert.assertTrue(keychainAccess.isPresent());
|
Assertions.assertTrue(keychainAccess.isPresent());
|
||||||
Assert.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
|
Assertions.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
|
||||||
keychainAccess.get().storePassphrase("test", "asd");
|
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;
|
package org.cryptomator.keychain;
|
||||||
|
|
||||||
import java.util.Optional;
|
import dagger.Component;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Optional;
|
||||||
import dagger.Component;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Component(modules = KeychainModule.class)
|
@Component(modules = KeychainModule.class)
|
||||||
|
|||||||
+23
-30
@@ -5,61 +5,54 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.keychain;
|
package org.cryptomator.keychain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import org.cryptomator.common.Environment;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import org.cryptomator.jni.WinDataProtection;
|
import org.cryptomator.jni.WinDataProtection;
|
||||||
import org.cryptomator.jni.WinFunctions;
|
import org.cryptomator.jni.WinFunctions;
|
||||||
import org.junit.After;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.Assert;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.Before;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.Rule;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.rules.ExpectedException;
|
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class WindowsProtectedKeychainAccessTest {
|
public class WindowsProtectedKeychainAccessTest {
|
||||||
|
|
||||||
@Rule
|
private Path keychainPath;
|
||||||
public final ExpectedException thrown = ExpectedException.none();
|
|
||||||
|
|
||||||
private Path tmpFile;
|
|
||||||
private WindowsProtectedKeychainAccess keychain;
|
private WindowsProtectedKeychainAccess keychain;
|
||||||
|
|
||||||
@Before
|
@BeforeEach
|
||||||
public void setup() throws IOException, ReflectiveOperationException {
|
public void setup(@TempDir Path tempDir) throws IOException {
|
||||||
tmpFile = Files.createTempFile("unit-tests", ".tmp");
|
keychainPath = tempDir.resolve("keychainfile.tmp");
|
||||||
System.setProperty("cryptomator.keychainPath", tmpFile.toAbsolutePath().normalize().toString());
|
Environment env = Mockito.mock(Environment.class);
|
||||||
|
Mockito.when(env.getKeychainPath()).thenReturn(Stream.of(keychainPath));
|
||||||
WinFunctions winFunctions = Mockito.mock(WinFunctions.class);
|
WinFunctions winFunctions = Mockito.mock(WinFunctions.class);
|
||||||
WinDataProtection winDataProtection = Mockito.mock(WinDataProtection.class);
|
WinDataProtection winDataProtection = Mockito.mock(WinDataProtection.class);
|
||||||
Mockito.when(winFunctions.dataProtection()).thenReturn(winDataProtection);
|
Mockito.when(winFunctions.dataProtection()).thenReturn(winDataProtection);
|
||||||
Answer<byte[]> answerReturningFirstArg = invocation -> ((byte[]) invocation.getArgument(0)).clone();
|
Answer<byte[]> answerReturningFirstArg = invocation -> ((byte[]) invocation.getArgument(0)).clone();
|
||||||
Mockito.when(winDataProtection.protect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
|
Mockito.when(winDataProtection.protect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
|
||||||
Mockito.when(winDataProtection.unprotect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
|
Mockito.when(winDataProtection.unprotect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
|
||||||
keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions));
|
keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions), env);
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void teardown() throws IOException {
|
|
||||||
Files.deleteIfExists(tmpFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStoreAndLoad() {
|
public void testStoreAndLoad() throws IOException {
|
||||||
String storedPw1 = "topSecret";
|
String storedPw1 = "topSecret";
|
||||||
String storedPw2 = "bottomSecret";
|
String storedPw2 = "bottomSecret";
|
||||||
keychain.storePassphrase("myPassword", storedPw1);
|
keychain.storePassphrase("myPassword", storedPw1);
|
||||||
keychain.storePassphrase("myOtherPassword", storedPw2);
|
keychain.storePassphrase("myOtherPassword", storedPw2);
|
||||||
String loadedPw1 = new String(keychain.loadPassphrase("myPassword"));
|
String loadedPw1 = new String(keychain.loadPassphrase("myPassword"));
|
||||||
String loadedPw2 = new String(keychain.loadPassphrase("myOtherPassword"));
|
String loadedPw2 = new String(keychain.loadPassphrase("myOtherPassword"));
|
||||||
Assert.assertEquals(storedPw1, loadedPw1);
|
Assertions.assertEquals(storedPw1, loadedPw1);
|
||||||
Assert.assertEquals(storedPw2, loadedPw2);
|
Assertions.assertEquals(storedPw2, loadedPw2);
|
||||||
keychain.deletePassphrase("myPassword");
|
keychain.deletePassphrase("myPassword");
|
||||||
Assert.assertNull(keychain.loadPassphrase("myPassword"));
|
Assertions.assertNull(keychain.loadPassphrase("myPassword"));
|
||||||
Assert.assertNull(keychain.loadPassphrase("nonExistingPassword"));
|
Assertions.assertNotNull(keychain.loadPassphrase("myOtherPassword"));
|
||||||
|
Assertions.assertNull(keychain.loadPassphrase("nonExistingPassword"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.4.2</version>
|
<version>1.5.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>launcher</artifactId>
|
<artifactId>launcher</artifactId>
|
||||||
<name>Cryptomator Launcher</name>
|
<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,127 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
import java.io.IOException;
|
import javafx.application.Application;
|
||||||
import java.nio.file.Path;
|
import javafx.application.Platform;
|
||||||
import java.util.Arrays;
|
import javafx.stage.Stage;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public class Cryptomator {
|
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);
|
private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
|
||||||
static final BlockingQueue<Path> FILE_OPEN_REQUESTS = new ArrayBlockingQueue<>(10);
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
private final LoggerConfiguration logConfig;
|
||||||
LOG.info("Starting Cryptomator {} on {} {} ({})", ApplicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
private final DebugMode debugMode;
|
||||||
|
private final IpcFactory ipcFactory;
|
||||||
|
private final Optional<String> applicationVersion;
|
||||||
|
|
||||||
FileOpenRequestHandler fileOpenRequestHandler = new FileOpenRequestHandler(FILE_OPEN_REQUESTS);
|
@Inject
|
||||||
try (InterProcessCommunicator communicator = InterProcessCommunicator.start(new IpcProtocolImpl(fileOpenRequestHandler))) {
|
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion) {
|
||||||
if (communicator.isServer()) {
|
this.logConfig = logConfig;
|
||||||
fileOpenRequestHandler.handleLaunchArgs(args);
|
this.debugMode = debugMode;
|
||||||
CleanShutdownPerformer.registerShutdownHook();
|
this.ipcFactory = ipcFactory;
|
||||||
Application.launch(MainApplication.class, args);
|
this.applicationVersion = applicationVersion;
|
||||||
} else {
|
|
||||||
communicator.handleLaunchArgs(args);
|
|
||||||
LOG.info("Found running application instance. Shutting down.");
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Failed to initiate inter-process communication.", e);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
LOG.error("Error during startup", e);
|
|
||||||
}
|
|
||||||
System.exit(0); // end remaining non-daemon threads.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class IpcProtocolImpl implements InterProcessCommunicationProtocol {
|
public static void main(String[] args) {
|
||||||
|
int exitCode = CRYPTOMATOR_COMPONENT.application().run(args);
|
||||||
|
System.exit(exitCode); // end remaining non-daemon threads.
|
||||||
|
}
|
||||||
|
|
||||||
private final FileOpenRequestHandler fileOpenRequestHandler;
|
/**
|
||||||
|
* Main entry point of the application launcher.
|
||||||
|
* @param args The arguments passed to this program via {@link #main(String[])}.
|
||||||
|
* @return Nonzero exit code in case of an error.
|
||||||
|
*/
|
||||||
|
private int run(String[] args) {
|
||||||
|
logConfig.init();
|
||||||
|
LOG.info("Starting Cryptomator {} on {} {} ({})", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||||
|
|
||||||
// TODO: inject?
|
if (sendArgsToRunningInstance(args)) {
|
||||||
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler) {
|
LOG.info("Found running application instance. Shutting down...");
|
||||||
this.fileOpenRequestHandler = fileOpenRequestHandler;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
runGuiApplication();
|
||||||
|
LOG.info("Shutting down...");
|
||||||
|
return 0;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.error("Terminating due to error", e);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args.
|
||||||
|
* If no external process could be reached, the args will be handled by the loopback IPC endpoint.
|
||||||
|
*
|
||||||
|
* @param args Arguments to send to the instance (if possible)
|
||||||
|
* @return <code>true</code> if a different process could be reached, <code>false</code> otherwise.
|
||||||
|
*/
|
||||||
|
private boolean sendArgsToRunningInstance(String[] args) {
|
||||||
|
try (IpcFactory.IpcEndpoint endpoint = ipcFactory.create()) {
|
||||||
|
endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
|
||||||
|
return endpoint.isConnectedToRemote();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Failed to initiate inter-process communication.", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches the JavaFX application and waits until shutdown is requested.
|
||||||
|
*/
|
||||||
|
private void runGuiApplication() {
|
||||||
|
debugMode.initialize();
|
||||||
|
CleanShutdownPerformer.registerShutdownHook();
|
||||||
|
Application.launch(MainApp.class);
|
||||||
|
// Platform.startup(() -> {
|
||||||
|
// assert Platform.isFxApplicationThread();
|
||||||
|
// FxApplication app = CRYPTOMATOR_COMPONENT.fxApplicationComponent().application();
|
||||||
|
// app.start();
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// We need a separate FX Application class, until we can use the module system. See https://stackoverflow.com/q/54756176/4014509
|
||||||
|
public static class MainApp extends Application {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Stage primaryStage) {
|
||||||
|
LOG.info("JavaFX application started.");
|
||||||
|
primaryStage.setMinWidth(652.0);
|
||||||
|
primaryStage.setMinHeight(440.0);
|
||||||
|
|
||||||
|
FxApplicationComponent fxApplicationComponent = CRYPTOMATOR_COMPONENT.fxApplicationComponent() //
|
||||||
|
.fxApplication(this) //
|
||||||
|
.mainWindow(primaryStage) //
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MainController mainCtrl = fxApplicationComponent.fxmlLoader().load("/fxml/main.fxml");
|
||||||
|
mainCtrl.initStage(primaryStage);
|
||||||
|
primaryStage.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleLaunchArgs(String[] args) {
|
public void stop() {
|
||||||
LOG.info("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
|
LOG.info("JavaFX application stopped.");
|
||||||
fileOpenRequestHandler.handleLaunchArgs(args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
|
import dagger.Component;
|
||||||
|
import org.cryptomator.common.CommonsModule;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
import org.cryptomator.logging.DebugMode;
|
||||||
|
import org.cryptomator.logging.LoggerModule;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class})
|
||||||
|
public interface CryptomatorComponent {
|
||||||
|
|
||||||
|
Cryptomator application();
|
||||||
|
|
||||||
|
FxApplicationComponent.Builder fxApplicationComponent();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,51 +7,71 @@
|
|||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
import java.awt.Desktop;
|
import java.awt.Desktop;
|
||||||
|
import java.awt.desktop.OpenFilesEvent;
|
||||||
|
import java.awt.desktop.QuitStrategy;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.FileSystem;
|
import java.nio.file.FileSystem;
|
||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.InvalidPathException;
|
import java.nio.file.InvalidPathException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.cryptomator.ui.model.AppLaunchEvent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class FileOpenRequestHandler {
|
class FileOpenRequestHandler {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(FileOpenRequestHandler.class);
|
private static final Logger LOG = LoggerFactory.getLogger(FileOpenRequestHandler.class);
|
||||||
private final BlockingQueue<Path> fileOpenRequests;
|
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
|
||||||
|
|
||||||
public FileOpenRequestHandler(BlockingQueue<Path> fileOpenRequests) {
|
@Inject
|
||||||
this.fileOpenRequests = fileOpenRequests;
|
public FileOpenRequestHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
|
||||||
|
this.launchEventQueue = launchEventQueue;
|
||||||
try {
|
try {
|
||||||
Desktop.getDesktop().setOpenFileHandler(e -> {
|
Desktop.getDesktop().setOpenFileHandler(this::openFiles);
|
||||||
e.getFiles().stream().map(File::toPath).forEach(fileOpenRequests::add);
|
|
||||||
});
|
|
||||||
} catch (UnsupportedOperationException e) {
|
} catch (UnsupportedOperationException e) {
|
||||||
LOG.info("Unable to setOpenFileHandler, probably not supported on this OS.");
|
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) {
|
public void handleLaunchArgs(String[] args) {
|
||||||
handleLaunchArgs(FileSystems.getDefault(), args);
|
handleLaunchArgs(FileSystems.getDefault(), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// visible for testing
|
// visible for testing
|
||||||
void handleLaunchArgs(FileSystem fs, String[] args) {
|
void handleLaunchArgs(FileSystem fs, String[] args) {
|
||||||
for (String arg : args) {
|
Stream<Path> pathsToOpen = Arrays.stream(args).map(str -> {
|
||||||
try {
|
try {
|
||||||
Path path = fs.getPath(arg);
|
return fs.getPath(str);
|
||||||
tryToEnqueueFileOpenRequest(path);
|
|
||||||
} catch (InvalidPathException e) {
|
} 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)) {
|
private void tryToEnqueueFileOpenRequest(AppLaunchEvent launchEvent) {
|
||||||
LOG.warn("{} could not be enqueued for opening.", path);
|
if (!launchEventQueue.offer(launchEvent)) {
|
||||||
|
LOG.warn("Could not enqueue application launch event.", launchEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+21
-7
@@ -5,19 +5,33 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import dagger.BindsInstance;
|
||||||
|
import dagger.Subcomponent;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.cryptomator.common.FxApplicationScoped;
|
||||||
import org.cryptomator.logging.DebugMode;
|
import org.cryptomator.logging.DebugMode;
|
||||||
import org.cryptomator.ui.controllers.ViewControllerLoader;
|
import org.cryptomator.ui.controllers.ViewControllerLoader;
|
||||||
|
|
||||||
import dagger.Component;
|
import javax.inject.Named;
|
||||||
|
|
||||||
@Singleton
|
@FxApplicationScoped
|
||||||
@Component(modules = LauncherModule.class)
|
@Subcomponent(modules = FxApplicationModule.class)
|
||||||
interface LauncherComponent {
|
interface FxApplicationComponent {
|
||||||
|
|
||||||
ViewControllerLoader fxmlLoader();
|
ViewControllerLoader fxmlLoader();
|
||||||
|
|
||||||
DebugMode debugMode();
|
@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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+7
-2
@@ -5,6 +5,11 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
public interface InterProcessCommunicationProtocol {
|
import java.rmi.Remote;
|
||||||
void handleLaunchArgs(String[] args);
|
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,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;
|
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.Level;
|
||||||
import ch.qos.logback.classic.Logger;
|
import ch.qos.logback.classic.Logger;
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
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
|
@Singleton
|
||||||
public class DebugMode {
|
public class DebugMode {
|
||||||
|
|
||||||
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(DebugMode.class);
|
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 Settings settings;
|
||||||
|
private final LoggerContext context;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DebugMode(Settings settings) {
|
public DebugMode(Settings settings, LoggerContext context) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
if (settings.debugMode().get()) {
|
setLogLevels(settings.debugMode().get());
|
||||||
enable();
|
settings.debugMode().addListener(this::logLevelChanged);
|
||||||
LOG.debug("Debug mode initialized");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enable() {
|
private void logLevelChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observable, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) {
|
||||||
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
setLogLevels(newValue);
|
||||||
if (loggerFactory instanceof LoggerContext) {
|
}
|
||||||
LoggerContext context = (LoggerContext) loggerFactory;
|
|
||||||
LOGGER_UPGRADES.forEach(loggerUpgrade -> loggerUpgrade.execute(context));
|
private void setLogLevels(boolean debugMode) {
|
||||||
|
if (debugMode) {
|
||||||
|
setLogLevels(LoggerModule.DEBUG_LOG_LEVELS);
|
||||||
|
LOG.debug("Debug mode enabled");
|
||||||
} else {
|
} 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) {
|
private void setLogLevels(Map<String, Level> logLevels) {
|
||||||
return new LoggerUpgrade(loggerName, minLevel);
|
for (Map.Entry<String, Level> loglevel : logLevels.entrySet()) {
|
||||||
}
|
Logger logger = context.getLogger(loglevel.getKey());
|
||||||
|
logger.setLevel(loglevel.getValue());
|
||||||
private static class LoggerUpgrade {
|
|
||||||
|
|
||||||
private final Level level;
|
|
||||||
private final String loggerName;
|
|
||||||
|
|
||||||
public LoggerUpgrade(String loggerName, Level minLevel) {
|
|
||||||
this.loggerName = loggerName;
|
|
||||||
this.level = minLevel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+42
-45
@@ -5,72 +5,69 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.launcher;
|
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.io.IOException;
|
||||||
import java.nio.file.FileSystem;
|
import java.nio.file.FileSystem;
|
||||||
import java.nio.file.InvalidPathException;
|
import java.nio.file.InvalidPathException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.spi.FileSystemProvider;
|
import java.util.List;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import org.junit.Assert;
|
import java.util.stream.Stream;
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
|
|
||||||
public class FileOpenRequestHandlerTest {
|
public class FileOpenRequestHandlerTest {
|
||||||
|
|
||||||
@Test
|
private FileOpenRequestHandler inTest;
|
||||||
public void testOpenArgsWithCorrectPaths() throws IOException {
|
private BlockingQueue<AppLaunchEvent> queue;
|
||||||
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);
|
|
||||||
|
|
||||||
BlockingQueue<Path> queue = new ArrayBlockingQueue<>(10);
|
@BeforeEach
|
||||||
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
|
public void setup() {
|
||||||
handler.handleLaunchArgs(fs, new String[] {"foo", "bar"});
|
queue = new ArrayBlockingQueue<>(1);
|
||||||
|
inTest = new FileOpenRequestHandler(queue);
|
||||||
Assert.assertEquals(p1, queue.poll());
|
|
||||||
Assert.assertEquals(p2, queue.poll());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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 {
|
public void testOpenArgsWithIncorrectPaths() throws IOException {
|
||||||
FileSystem fs = Mockito.mock(FileSystem.class);
|
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")
|
AppLaunchEvent evt = queue.poll();
|
||||||
BlockingQueue<Path> queue = Mockito.mock(BlockingQueue.class);
|
Assertions.assertNotNull(evt);
|
||||||
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
|
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
|
||||||
handler.handleLaunchArgs(fs, new String[] {"foo"});
|
Assertions.assertTrue(paths.isEmpty());
|
||||||
|
|
||||||
Mockito.verifyNoMoreInteractions(queue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@DisplayName("./cryptomator.exe foo (with full event queue)")
|
||||||
public void testOpenArgsWithFullQueue() throws IOException {
|
public void testOpenArgsWithFullQueue() throws IOException {
|
||||||
Path p = Mockito.mock(Path.class);
|
queue.add(new AppLaunchEvent(Stream.empty()));
|
||||||
FileSystem fs = Mockito.mock(FileSystem.class);
|
Assumptions.assumeTrue(queue.remainingCapacity() == 0);
|
||||||
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);
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
inTest.handleLaunchArgs(new String[]{"foo"});
|
||||||
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"});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
-100
@@ -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"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+7
-7
@@ -5,12 +5,12 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.logging;
|
package org.cryptomator.logging;
|
||||||
|
|
||||||
import java.io.File;
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
public class LaunchBasedTriggeringPolicyTest {
|
public class LaunchBasedTriggeringPolicyTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -21,15 +21,15 @@ public class LaunchBasedTriggeringPolicyTest {
|
|||||||
|
|
||||||
// 1st invocation
|
// 1st invocation
|
||||||
boolean triggered = policy.isTriggeringEvent(activeFile, event);
|
boolean triggered = policy.isTriggeringEvent(activeFile, event);
|
||||||
Assert.assertTrue(triggered);
|
Assertions.assertTrue(triggered);
|
||||||
|
|
||||||
// 2nd invocation
|
// 2nd invocation
|
||||||
triggered = policy.isTriggeringEvent(activeFile, event);
|
triggered = policy.isTriggeringEvent(activeFile, event);
|
||||||
Assert.assertFalse(triggered);
|
Assertions.assertFalse(triggered);
|
||||||
|
|
||||||
// 3rd invocation
|
// 3rd invocation
|
||||||
triggered = policy.isTriggeringEvent(activeFile, event);
|
triggered = policy.isTriggeringEvent(activeFile, event);
|
||||||
Assert.assertFalse(triggered);
|
Assertions.assertFalse(triggered);
|
||||||
|
|
||||||
Mockito.verifyZeroInteractions(activeFile);
|
Mockito.verifyZeroInteractions(activeFile);
|
||||||
Mockito.verifyZeroInteractions(event);
|
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>
|
|
||||||
+46
-34
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.4.2</version>
|
<version>1.5.0-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>Cryptomator</name>
|
<name>Cryptomator</name>
|
||||||
|
|
||||||
@@ -25,11 +25,13 @@
|
|||||||
|
|
||||||
<!-- dependency versions -->
|
<!-- dependency versions -->
|
||||||
<cryptomator.cryptolib.version>1.2.1</cryptomator.cryptolib.version>
|
<cryptomator.cryptolib.version>1.2.1</cryptomator.cryptolib.version>
|
||||||
<cryptomator.cryptofs.version>1.6.2</cryptomator.cryptofs.version>
|
<cryptomator.cryptofs.version>1.7.0</cryptomator.cryptofs.version>
|
||||||
<cryptomator.jni.version>2.0.0</cryptomator.jni.version>
|
<cryptomator.jni.version>2.0.0</cryptomator.jni.version>
|
||||||
<cryptomator.fuse.version>1.0.3</cryptomator.fuse.version>
|
<cryptomator.fuse.version>1.1.1</cryptomator.fuse.version>
|
||||||
<cryptomator.dokany.version>1.1.1</cryptomator.dokany.version>
|
<cryptomator.dokany.version>1.1.3</cryptomator.dokany.version>
|
||||||
<cryptomator.webdav.version>1.0.5</cryptomator.webdav.version>
|
<cryptomator.webdav.version>1.0.9</cryptomator.webdav.version>
|
||||||
|
|
||||||
|
<javafx.version>11.0.2</javafx.version>
|
||||||
|
|
||||||
<commons-io.version>2.6</commons-io.version>
|
<commons-io.version>2.6</commons-io.version>
|
||||||
<commons-lang3.version>3.8.1</commons-lang3.version>
|
<commons-lang3.version>3.8.1</commons-lang3.version>
|
||||||
@@ -43,10 +45,9 @@
|
|||||||
<slf4j.version>1.7.25</slf4j.version>
|
<slf4j.version>1.7.25</slf4j.version>
|
||||||
<logback.version>1.2.3</logback.version>
|
<logback.version>1.2.3</logback.version>
|
||||||
|
|
||||||
<junit.version>4.12</junit.version>
|
<junit.jupiter.version>5.4.0</junit.jupiter.version>
|
||||||
<junit.hierarchicalrunner.version>4.12.1</junit.hierarchicalrunner.version>
|
<mockito.version>2.24.0</mockito.version>
|
||||||
<mockito.version>2.23.0</mockito.version>
|
<hamcrest.version>1.3</hamcrest.version>
|
||||||
<hamcrest.version>1.3</hamcrest.version> <!-- keep in sync with version required by JUnit -->
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
@@ -122,6 +123,23 @@
|
|||||||
<version>${cryptomator.jni.version}</version>
|
<version>${cryptomator.jni.version}</version>
|
||||||
</dependency>
|
</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 -->
|
<!-- Logging -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
@@ -182,20 +200,10 @@
|
|||||||
|
|
||||||
<!-- JUnit / Mockito / Hamcrest -->
|
<!-- JUnit / Mockito / Hamcrest -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit-jupiter</artifactId>
|
||||||
<version>${junit.version}</version>
|
<version>${junit.jupiter.version}</version>
|
||||||
<exclusions>
|
<scope>test</scope>
|
||||||
<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>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
@@ -207,6 +215,12 @@
|
|||||||
<artifactId>hamcrest-all</artifactId>
|
<artifactId>hamcrest-all</artifactId>
|
||||||
<version>${hamcrest.version}</version>
|
<version>${hamcrest.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-swing</artifactId>
|
||||||
|
<version>${javafx.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
@@ -217,9 +231,8 @@
|
|||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit-jupiter</artifactId>
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hamcrest</groupId>
|
<groupId>org.hamcrest</groupId>
|
||||||
@@ -231,11 +244,6 @@
|
|||||||
<artifactId>mockito-core</artifactId>
|
<artifactId>mockito-core</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>de.bechte.junit</groupId>
|
|
||||||
<artifactId>junit-hierarchicalcontextrunner</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
@@ -249,8 +257,7 @@
|
|||||||
<profile>
|
<profile>
|
||||||
<id>release</id>
|
<id>release</id>
|
||||||
<modules>
|
<modules>
|
||||||
<module>uber-jar</module>
|
<module>buildkit</module>
|
||||||
<module>ant-kit</module>
|
|
||||||
</modules>
|
</modules>
|
||||||
</profile>
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
@@ -317,7 +324,7 @@
|
|||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.8.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<release>9</release>
|
<release>11</release>
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths>
|
||||||
<path>
|
<path>
|
||||||
<groupId>com.google.dagger</groupId>
|
<groupId>com.google.dagger</groupId>
|
||||||
@@ -327,6 +334,11 @@
|
|||||||
</annotationProcessorPaths>
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.22.1</version>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</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.2</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>
|
|
||||||
+16
-1
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.4.2</version>
|
<version>1.5.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>ui</artifactId>
|
<artifactId>ui</artifactId>
|
||||||
<name>Cryptomator GUI</name>
|
<name>Cryptomator GUI</name>
|
||||||
@@ -46,6 +46,16 @@
|
|||||||
<artifactId>cryptolib</artifactId>
|
<artifactId>cryptolib</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JavaFx -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-controls</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-fxml</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- EasyBind -->
|
<!-- EasyBind -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.fxmisc.easybind</groupId>
|
<groupId>org.fxmisc.easybind</groupId>
|
||||||
@@ -99,5 +109,10 @@
|
|||||||
<version>1.1</version>
|
<version>1.1</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-swing</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -9,6 +9,24 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui;
|
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.AWTException;
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.awt.MenuItem;
|
import java.awt.MenuItem;
|
||||||
@@ -24,27 +42,7 @@ import java.io.IOException;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
@FxApplicationScoped
|
||||||
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
|
|
||||||
public class ExitUtil {
|
public class ExitUtil {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ExitUtil.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ExitUtil.class);
|
||||||
|
|||||||
@@ -8,21 +8,11 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui;
|
package org.cryptomator.ui;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import javafx.beans.binding.Binding;
|
import javafx.beans.binding.Binding;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.CommonsModule;
|
import org.cryptomator.common.FxApplicationScoped;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.SettingsProvider;
|
import org.cryptomator.common.settings.SettingsProvider;
|
||||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||||
@@ -31,19 +21,21 @@ import org.cryptomator.ui.controllers.ViewControllerModule;
|
|||||||
import org.cryptomator.ui.model.VaultComponent;
|
import org.cryptomator.ui.model.VaultComponent;
|
||||||
import org.fxmisc.easybind.EasyBind;
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
|
||||||
@Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class})
|
import javax.inject.Named;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Module(includes = {ViewControllerModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class})
|
||||||
public class UiModule {
|
public class UiModule {
|
||||||
|
|
||||||
private static final int NUM_SCHEDULER_THREADS = 4;
|
private static final int NUM_SCHEDULER_THREADS = 4;
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@FxApplicationScoped
|
||||||
Settings provideSettings(SettingsProvider settingsProvider) {
|
|
||||||
return settingsProvider.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
||||||
final AtomicInteger threadNumber = new AtomicInteger(1);
|
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
|
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
|
||||||
@@ -57,7 +49,7 @@ public class UiModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@FxApplicationScoped
|
||||||
ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
||||||
final AtomicInteger threadNumber = new AtomicInteger(1);
|
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
ExecutorService executorService = Executors.newCachedThreadPool(r -> {
|
ExecutorService executorService = Executors.newCachedThreadPool(r -> {
|
||||||
@@ -71,7 +63,7 @@ public class UiModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@FxApplicationScoped
|
||||||
Binding<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
|
Binding<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
|
||||||
return EasyBind.map(settings.port(), (Number port) -> {
|
return EasyBind.map(settings.port(), (Number port) -> {
|
||||||
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
|
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
|
||||||
@@ -80,7 +72,7 @@ public class UiModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@FxApplicationScoped
|
||||||
WebDavServer provideWebDavServer(Binding<InetSocketAddress> serverSocketAddressBinding) {
|
WebDavServer provideWebDavServer(Binding<InetSocketAddress> serverSocketAddressBinding) {
|
||||||
WebDavServer server = WebDavServer.create();
|
WebDavServer server = WebDavServer.create();
|
||||||
// no need to unsubscribe eventually, because server is a singleton
|
// no need to unsubscribe eventually, because server is a singleton
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import java.util.Optional;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import javafx.beans.Observable;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||||
import org.cryptomator.ui.controls.SecPasswordField;
|
import org.cryptomator.ui.controls.SecPasswordField;
|
||||||
@@ -48,7 +49,7 @@ public class ChangePasswordController implements ViewController {
|
|||||||
private final Application app;
|
private final Application app;
|
||||||
private final PasswordStrengthUtil strengthRater;
|
private final PasswordStrengthUtil strengthRater;
|
||||||
private final Localization localization;
|
private final Localization localization;
|
||||||
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
|
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(-1); // 0-4
|
||||||
private Optional<ChangePasswordListener> listener = Optional.empty();
|
private Optional<ChangePasswordListener> listener = Optional.empty();
|
||||||
private Vault vault;
|
private Vault vault;
|
||||||
|
|
||||||
@@ -100,11 +101,9 @@ public class ChangePasswordController implements ViewController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
BooleanBinding oldPasswordIsEmpty = oldPasswordField.textProperty().isEmpty();
|
oldPasswordField.textProperty().addListener(this::passwordsChanged);
|
||||||
BooleanBinding newPasswordIsEmpty = newPasswordField.textProperty().isEmpty();
|
newPasswordField.textProperty().addListener(this::passwordsChanged);
|
||||||
BooleanBinding passwordsDiffer = newPasswordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
|
retypePasswordField.textProperty().addListener(this::passwordsChanged);
|
||||||
changePasswordButton.disableProperty().bind(oldPasswordIsEmpty.or(newPasswordIsEmpty.or(passwordsDiffer)));
|
|
||||||
passwordStrength.bind(EasyBind.map(newPasswordField.textProperty(), strengthRater::computeRate));
|
|
||||||
|
|
||||||
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
|
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
|
||||||
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
|
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
|
||||||
@@ -114,6 +113,14 @@ public class ChangePasswordController implements ViewController {
|
|||||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void passwordsChanged(Observable observable) {
|
||||||
|
boolean oldPasswordEmpty = oldPasswordField.getCharacters().length() == 0;
|
||||||
|
boolean newPasswordEmpty = newPasswordField.getCharacters().length() == 0;
|
||||||
|
boolean passwordsEqual = newPasswordField.getCharacters().equals(retypePasswordField.getCharacters());
|
||||||
|
changePasswordButton.setDisable(oldPasswordEmpty || newPasswordEmpty || !passwordsEqual);
|
||||||
|
passwordStrength.set(strengthRater.computeRate(newPasswordField.getCharacters().toString()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Parent getRoot() {
|
public Parent getRoot() {
|
||||||
return root;
|
return root;
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ import java.util.Optional;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import javafx.beans.Observable;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.value.ObservableIntegerValue;
|
||||||
import org.cryptomator.ui.controls.SecPasswordField;
|
import org.cryptomator.ui.controls.SecPasswordField;
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
import org.cryptomator.ui.model.Vault;
|
import org.cryptomator.ui.model.Vault;
|
||||||
@@ -42,7 +45,7 @@ public class InitializeController implements ViewController {
|
|||||||
|
|
||||||
private final Localization localization;
|
private final Localization localization;
|
||||||
private final PasswordStrengthUtil strengthRater;
|
private final PasswordStrengthUtil strengthRater;
|
||||||
private ObservableValue<Integer> passwordStrength; // 0-4
|
private IntegerProperty passwordStrength = new SimpleIntegerProperty(-1); // strengths: 0-4
|
||||||
private Optional<InitializationListener> listener = Optional.empty();
|
private Optional<InitializationListener> listener = Optional.empty();
|
||||||
private Vault vault;
|
private Vault vault;
|
||||||
|
|
||||||
@@ -87,10 +90,8 @@ public class InitializeController implements ViewController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
BooleanBinding passwordIsEmpty = passwordField.textProperty().isEmpty();
|
passwordField.textProperty().addListener(this::passwordsChanged);
|
||||||
BooleanBinding passwordsDiffer = passwordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
|
retypePasswordField.textProperty().addListener(this::passwordsChanged);
|
||||||
okButton.disableProperty().bind(passwordIsEmpty.or(passwordsDiffer));
|
|
||||||
passwordStrength = EasyBind.map(passwordField.textProperty(), strengthRater::computeRate);
|
|
||||||
|
|
||||||
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
|
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
|
||||||
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
|
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
|
||||||
@@ -100,6 +101,13 @@ public class InitializeController implements ViewController {
|
|||||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void passwordsChanged(Observable observable) {
|
||||||
|
boolean passwordsEmpty = passwordField.getCharacters().length() == 0;
|
||||||
|
boolean passwordsEqual = passwordField.getCharacters().equals(retypePasswordField.getCharacters());
|
||||||
|
okButton.setDisable(passwordsEmpty || !passwordsEqual);
|
||||||
|
passwordStrength.set(strengthRater.computeRate(passwordField.getCharacters().toString()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Parent getRoot() {
|
public Parent getRoot() {
|
||||||
return root;
|
return root;
|
||||||
|
|||||||
@@ -9,24 +9,6 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.cryptomator.ui.controllers;
|
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.Application;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Binding;
|
import javafx.beans.binding.Binding;
|
||||||
@@ -61,27 +43,47 @@ import javafx.scene.layout.Pane;
|
|||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import javafx.util.Duration;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.FxApplicationScoped;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.ui.ExitUtil;
|
import org.cryptomator.ui.ExitUtil;
|
||||||
import org.cryptomator.ui.controls.DirectoryListCell;
|
import org.cryptomator.ui.controls.DirectoryListCell;
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
|
import org.cryptomator.ui.model.AppLaunchEvent;
|
||||||
import org.cryptomator.ui.model.AutoUnlocker;
|
import org.cryptomator.ui.model.AutoUnlocker;
|
||||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
|
||||||
import org.cryptomator.ui.model.UpgradeStrategy;
|
import org.cryptomator.ui.model.upgrade.UpgradeStrategy;
|
||||||
import org.cryptomator.ui.model.Vault;
|
import org.cryptomator.ui.model.Vault;
|
||||||
import org.cryptomator.ui.model.VaultFactory;
|
import org.cryptomator.ui.model.VaultFactory;
|
||||||
import org.cryptomator.ui.model.VaultList;
|
import org.cryptomator.ui.model.VaultList;
|
||||||
import org.cryptomator.ui.util.DialogBuilderUtil;
|
import org.cryptomator.ui.util.DialogBuilderUtil;
|
||||||
|
import org.cryptomator.ui.util.Tasks;
|
||||||
import org.fxmisc.easybind.EasyBind;
|
import org.fxmisc.easybind.EasyBind;
|
||||||
import org.fxmisc.easybind.Subscription;
|
import org.fxmisc.easybind.Subscription;
|
||||||
import org.fxmisc.easybind.monadic.MonadicBinding;
|
import org.fxmisc.easybind.monadic.MonadicBinding;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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;
|
import static org.cryptomator.ui.util.DialogBuilderUtil.buildErrorDialog;
|
||||||
|
|
||||||
@Singleton
|
@FxApplicationScoped
|
||||||
public class MainController implements ViewController {
|
public class MainController implements ViewController {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
|
||||||
@@ -92,7 +94,7 @@ public class MainController implements ViewController {
|
|||||||
private final ExitUtil exitUtil;
|
private final ExitUtil exitUtil;
|
||||||
private final Localization localization;
|
private final Localization localization;
|
||||||
private final ExecutorService executorService;
|
private final ExecutorService executorService;
|
||||||
private final BlockingQueue<Path> fileOpenRequests;
|
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
|
||||||
private final VaultFactory vaultFactoy;
|
private final VaultFactory vaultFactoy;
|
||||||
private final ViewControllerLoader viewControllerLoader;
|
private final ViewControllerLoader viewControllerLoader;
|
||||||
private final ObjectProperty<ViewController> activeController = new SimpleObjectProperty<>();
|
private final ObjectProperty<ViewController> activeController = new SimpleObjectProperty<>();
|
||||||
@@ -109,11 +111,11 @@ public class MainController implements ViewController {
|
|||||||
private Subscription subs = Subscription.EMPTY;
|
private Subscription subs = Subscription.EMPTY;
|
||||||
|
|
||||||
@Inject
|
@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) {
|
VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker) {
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.executorService = executorService;
|
this.executorService = executorService;
|
||||||
this.fileOpenRequests = fileOpenRequests;
|
this.launchEventQueue = launchEventQueue;
|
||||||
this.exitUtil = exitUtil;
|
this.exitUtil = exitUtil;
|
||||||
this.localization = localization;
|
this.localization = localization;
|
||||||
this.vaultFactoy = vaultFactoy;
|
this.vaultFactoy = vaultFactoy;
|
||||||
@@ -211,7 +213,7 @@ public class MainController implements ViewController {
|
|||||||
stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon_32.png")));
|
stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon_32.png")));
|
||||||
Application.setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString());
|
Application.setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString());
|
||||||
}
|
}
|
||||||
exitUtil.initExitHandler(this::gracefulShutdown);
|
exitUtil.initExitHandler(() -> Platform.runLater(this::gracefulShutdown));
|
||||||
listenToFileOpenRequests(stage);
|
listenToFileOpenRequests(stage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,22 +250,13 @@ public class MainController implements ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void listenToFileOpenRequests(Stage stage) {
|
private void listenToFileOpenRequests(Stage stage) {
|
||||||
executorService.submit(() -> {
|
Tasks.create(launchEventQueue::take).onSuccess(event -> {
|
||||||
while (!Thread.interrupted()) {
|
stage.setIconified(false);
|
||||||
try {
|
stage.show();
|
||||||
final Path path = fileOpenRequests.take();
|
stage.toFront();
|
||||||
Platform.runLater(() -> {
|
stage.requestFocus();
|
||||||
addVault(path, true);
|
event.getPathsToOpen().forEach(path -> addVault(path, true));
|
||||||
stage.setIconified(false);
|
}).schedulePeriodically(executorService, Duration.ZERO, Duration.ZERO);
|
||||||
stage.show();
|
|
||||||
stage.toFront();
|
|
||||||
stage.requestFocus();
|
|
||||||
});
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
|
private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.controllers;
|
package org.cryptomator.ui.controllers;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import org.cryptomator.common.FxApplicationScoped;
|
||||||
|
|
||||||
@Singleton
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@FxApplicationScoped
|
||||||
public class NotFoundController implements ViewController {
|
public class NotFoundController implements ViewController {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import javafx.scene.input.KeyEvent;
|
|||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.FxApplicationScoped;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
@@ -32,10 +33,9 @@ import org.cryptomator.ui.model.Volume;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Singleton
|
@FxApplicationScoped
|
||||||
public class SettingsController implements ViewController {
|
public class SettingsController implements ViewController {
|
||||||
|
|
||||||
private static final CharMatcher DIGITS_MATCHER = CharMatcher.inRange('0', '9');
|
private static final CharMatcher DIGITS_MATCHER = CharMatcher.inRange('0', '9');
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ import javafx.scene.control.ProgressIndicator;
|
|||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.stage.DirectoryChooser;
|
||||||
|
import javafx.stage.Stage;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import org.apache.commons.lang3.CharUtils;
|
import org.apache.commons.lang3.CharUtils;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
@@ -36,11 +39,9 @@ import org.cryptomator.common.settings.VaultSettings;
|
|||||||
import org.cryptomator.common.settings.VolumeImpl;
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||||
import org.cryptomator.frontend.webdav.ServerLifecycleException;
|
|
||||||
import org.cryptomator.keychain.KeychainAccess;
|
import org.cryptomator.keychain.KeychainAccess;
|
||||||
import org.cryptomator.ui.controls.SecPasswordField;
|
import org.cryptomator.ui.controls.SecPasswordField;
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
import org.cryptomator.ui.model.InvalidSettingsException;
|
|
||||||
import org.cryptomator.ui.model.Vault;
|
import org.cryptomator.ui.model.Vault;
|
||||||
import org.cryptomator.ui.model.WindowsDriveLetters;
|
import org.cryptomator.ui.model.WindowsDriveLetters;
|
||||||
import org.cryptomator.ui.util.DialogBuilderUtil;
|
import org.cryptomator.ui.util.DialogBuilderUtil;
|
||||||
@@ -51,6 +52,12 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
|
import java.nio.file.NotDirectoryException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -67,6 +74,7 @@ public class UnlockController implements ViewController {
|
|||||||
.precomputed();
|
.precomputed();
|
||||||
|
|
||||||
private final Application app;
|
private final Application app;
|
||||||
|
private final Stage mainWindow;
|
||||||
private final Localization localization;
|
private final Localization localization;
|
||||||
private final WindowsDriveLetters driveLetters;
|
private final WindowsDriveLetters driveLetters;
|
||||||
private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange;
|
private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange;
|
||||||
@@ -78,8 +86,9 @@ public class UnlockController implements ViewController {
|
|||||||
private Subscription vaultSubs = Subscription.EMPTY;
|
private Subscription vaultSubs = Subscription.EMPTY;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UnlockController(Application app, Localization localization, WindowsDriveLetters driveLetters, Optional<KeychainAccess> keychainAccess, Settings settings, ExecutorService executor) {
|
public UnlockController(Application app, @Named("mainWindow") Stage mainWindow, Localization localization, WindowsDriveLetters driveLetters, Optional<KeychainAccess> keychainAccess, Settings settings, ExecutorService executor) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
this.localization = localization;
|
this.localization = localization;
|
||||||
this.driveLetters = driveLetters;
|
this.driveLetters = driveLetters;
|
||||||
this.keychainAccess = keychainAccess;
|
this.keychainAccess = keychainAccess;
|
||||||
@@ -97,7 +106,7 @@ public class UnlockController implements ViewController {
|
|||||||
private Button unlockButton;
|
private Button unlockButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label successMessage;
|
private Text messageText;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private CheckBox savePassword;
|
private CheckBox savePassword;
|
||||||
@@ -115,19 +124,19 @@ public class UnlockController implements ViewController {
|
|||||||
private ChoiceBox<Character> winDriveLetter;
|
private ChoiceBox<Character> winDriveLetter;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private CheckBox useCustomMountPath;
|
private CheckBox useCustomMountPoint;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label customMountPathLabel;
|
private HBox customMountPoint;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextField customMountPathField;
|
private Label customMountPointLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ProgressIndicator progressIndicator;
|
private ProgressIndicator progressIndicator;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Text messageText;
|
private Text progressText;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Hyperlink downloadsPageLink;
|
private Hyperlink downloadsPageLink;
|
||||||
@@ -141,6 +150,9 @@ public class UnlockController implements ViewController {
|
|||||||
@FXML
|
@FXML
|
||||||
private CheckBox unlockAfterStartup;
|
private CheckBox unlockAfterStartup;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox useReadOnlyMode;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
|
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
|
||||||
@@ -150,11 +162,8 @@ public class UnlockController implements ViewController {
|
|||||||
savePassword.setDisable(!keychainAccess.isPresent());
|
savePassword.setDisable(!keychainAccess.isPresent());
|
||||||
unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
|
unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
|
||||||
|
|
||||||
customMountPathLabel.visibleProperty().bind(useCustomMountPath.selectedProperty());
|
customMountPoint.visibleProperty().bind(useCustomMountPoint.selectedProperty());
|
||||||
customMountPathLabel.managedProperty().bind(useCustomMountPath.selectedProperty());
|
customMountPoint.managedProperty().bind(useCustomMountPoint.selectedProperty());
|
||||||
customMountPathField.visibleProperty().bind(useCustomMountPath.selectedProperty());
|
|
||||||
customMountPathField.managedProperty().bind(useCustomMountPath.selectedProperty());
|
|
||||||
customMountPathField.textProperty().addListener(this::mountPathDidChange);
|
|
||||||
winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
|
winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
|
||||||
|
|
||||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
if (!SystemUtils.IS_OS_WINDOWS) {
|
||||||
@@ -163,20 +172,6 @@ public class UnlockController implements ViewController {
|
|||||||
winDriveLetter.setVisible(false);
|
winDriveLetter.setVisible(false);
|
||||||
winDriveLetter.setManaged(false);
|
winDriveLetter.setManaged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VolumeImpl.WEBDAV.equals(settings.preferredVolumeImpl().get())) {
|
|
||||||
useCustomMountPath.setVisible(false);
|
|
||||||
useCustomMountPath.setManaged(false);
|
|
||||||
customMountPathField.setMouseTransparent(true);
|
|
||||||
} else {
|
|
||||||
useCustomMountPath.setVisible(true);
|
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
winDriveLetter.visibleProperty().bind(useCustomMountPath.selectedProperty().not());
|
|
||||||
winDriveLetter.managedProperty().bind(useCustomMountPath.selectedProperty().not());
|
|
||||||
winDriveLetterLabel.visibleProperty().bind(useCustomMountPath.selectedProperty().not());
|
|
||||||
winDriveLetterLabel.managedProperty().bind(useCustomMountPath.selectedProperty().not());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -205,8 +200,8 @@ public class UnlockController implements ViewController {
|
|||||||
advancedOptions.setVisible(false);
|
advancedOptions.setVisible(false);
|
||||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
||||||
progressIndicator.setVisible(false);
|
progressIndicator.setVisible(false);
|
||||||
successMessage.setVisible(state.successMessage().isPresent());
|
progressText.setText(null);
|
||||||
state.successMessage().map(localization::getString).ifPresent(successMessage::setText);
|
state.successMessage().map(localization::getString).ifPresent(messageText::setText);
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
winDriveLetter.valueProperty().removeListener(driveLetterChangeListener);
|
winDriveLetter.valueProperty().removeListener(driveLetterChangeListener);
|
||||||
winDriveLetter.getItems().clear();
|
winDriveLetter.getItems().clear();
|
||||||
@@ -217,7 +212,6 @@ public class UnlockController implements ViewController {
|
|||||||
chooseSelectedDriveLetter();
|
chooseSelectedDriveLetter();
|
||||||
}
|
}
|
||||||
downloadsPageLink.setVisible(false);
|
downloadsPageLink.setVisible(false);
|
||||||
messageText.setText(null);
|
|
||||||
mountName.setText(vault.getMountName());
|
mountName.setText(vault.getMountName());
|
||||||
savePassword.setSelected(false);
|
savePassword.setSelected(false);
|
||||||
// auto-fill pw from keychain:
|
// auto-fill pw from keychain:
|
||||||
@@ -225,7 +219,7 @@ public class UnlockController implements ViewController {
|
|||||||
char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
|
char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
|
||||||
if (storedPw != null) {
|
if (storedPw != null) {
|
||||||
savePassword.setSelected(true);
|
savePassword.setSelected(true);
|
||||||
passwordField.setText(new String(storedPw));
|
passwordField.setPassword(storedPw);
|
||||||
passwordField.selectRange(storedPw.length, storedPw.length);
|
passwordField.selectRange(storedPw.length, storedPw.length);
|
||||||
Arrays.fill(storedPw, ' ');
|
Arrays.fill(storedPw, ' ');
|
||||||
}
|
}
|
||||||
@@ -234,15 +228,58 @@ public class UnlockController implements ViewController {
|
|||||||
unlockAfterStartup.setSelected(savePassword.isSelected() && vaultSettings.unlockAfterStartup().get());
|
unlockAfterStartup.setSelected(savePassword.isSelected() && vaultSettings.unlockAfterStartup().get());
|
||||||
revealAfterMount.setSelected(vaultSettings.revealAfterMount().get());
|
revealAfterMount.setSelected(vaultSettings.revealAfterMount().get());
|
||||||
|
|
||||||
if (!settings.preferredVolumeImpl().get().equals(VolumeImpl.WEBDAV)) {
|
// WEBDAV-dependent controls:
|
||||||
useCustomMountPath.setSelected(vaultSettings.usesIndividualMountPath().get());
|
if (VolumeImpl.WEBDAV.equals(settings.preferredVolumeImpl().get())) {
|
||||||
customMountPathField.textProperty().setValue(vaultSettings.individualMountPath().getValueSafe());
|
useCustomMountPoint.setVisible(false);
|
||||||
|
useCustomMountPoint.setManaged(false);
|
||||||
|
} else {
|
||||||
|
useCustomMountPoint.setVisible(true);
|
||||||
|
useCustomMountPoint.setSelected(vaultSettings.usesIndividualMountPath().get());
|
||||||
|
if (Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
|
||||||
|
customMountPointLabel.setText(localization.getString("unlock.label.chooseMountPath"));
|
||||||
|
} else {
|
||||||
|
customMountPointLabel.setText(displayablePath(vaultSettings.individualMountPath().getValueSafe()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOKANY-dependent controls:
|
||||||
|
if (VolumeImpl.DOKANY.equals(settings.preferredVolumeImpl().get())) {
|
||||||
|
winDriveLetter.visibleProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
winDriveLetter.managedProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
winDriveLetterLabel.visibleProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
winDriveLetterLabel.managedProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
// readonly not yet supported by dokany
|
||||||
|
useReadOnlyMode.setSelected(false);
|
||||||
|
useReadOnlyMode.setVisible(false);
|
||||||
|
useReadOnlyMode.setManaged(false);
|
||||||
|
} else {
|
||||||
|
useReadOnlyMode.setSelected(vaultSettings.usesReadOnlyMode().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// OS-dependent controls:
|
||||||
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
winDriveLetter.visibleProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
winDriveLetter.managedProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
winDriveLetterLabel.visibleProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
winDriveLetterLabel.managedProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
}
|
}
|
||||||
|
|
||||||
vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), vaultSettings.unlockAfterStartup()::set));
|
vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), vaultSettings.unlockAfterStartup()::set));
|
||||||
vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), vaultSettings.revealAfterMount()::set));
|
vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), vaultSettings.revealAfterMount()::set));
|
||||||
vaultSubs = vaultSubs.and(EasyBind.subscribe(useCustomMountPath.selectedProperty(), vaultSettings.usesIndividualMountPath()::set));
|
vaultSubs = vaultSubs.and(EasyBind.subscribe(useCustomMountPoint.selectedProperty(), vaultSettings.usesIndividualMountPath()::set));
|
||||||
|
vaultSubs = vaultSubs.and(EasyBind.subscribe(useReadOnlyMode.selectedProperty(), vaultSettings.usesReadOnlyMode()::set));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String displayablePath(String path) {
|
||||||
|
Path homeDir = Paths.get(SystemUtils.USER_HOME);
|
||||||
|
Path p = Paths.get(path);
|
||||||
|
if (p.startsWith(homeDir)) {
|
||||||
|
Path relativePath = homeDir.relativize(p);
|
||||||
|
String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/";
|
||||||
|
return homePrefix + relativePath.toString();
|
||||||
|
} else {
|
||||||
|
return p.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************************
|
// ****************************************
|
||||||
@@ -260,7 +297,7 @@ public class UnlockController implements ViewController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void didClickAdvancedOptionsButton(ActionEvent event) {
|
private void didClickAdvancedOptionsButton(ActionEvent event) {
|
||||||
successMessage.setVisible(false);
|
messageText.setText(null);
|
||||||
advancedOptions.setVisible(!advancedOptions.isVisible());
|
advancedOptions.setVisible(!advancedOptions.isVisible());
|
||||||
if (advancedOptions.isVisible()) {
|
if (advancedOptions.isVisible()) {
|
||||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.hide"));
|
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.hide"));
|
||||||
@@ -284,8 +321,13 @@ public class UnlockController implements ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mountPathDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
|
public void didClickChooseCustomMountPoint(ActionEvent actionEvent) {
|
||||||
vault.setCustomMountPath(newValue);
|
DirectoryChooser dirChooser = new DirectoryChooser();
|
||||||
|
File file = dirChooser.showDialog(mainWindow);
|
||||||
|
if (file != null) {
|
||||||
|
customMountPointLabel.setText(displayablePath(file.toString()));
|
||||||
|
vault.setCustomMountPath(file.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -298,7 +340,7 @@ public class UnlockController implements ViewController {
|
|||||||
if (letter == null) {
|
if (letter == null) {
|
||||||
return localization.getString("unlock.choicebox.winDriveLetter.auto");
|
return localization.getString("unlock.choicebox.winDriveLetter.auto");
|
||||||
} else {
|
} else {
|
||||||
return Character.toString(letter) + ":";
|
return letter + ":";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,11 +430,12 @@ public class UnlockController implements ViewController {
|
|||||||
private void didClickUnlockButton(ActionEvent event) {
|
private void didClickUnlockButton(ActionEvent event) {
|
||||||
advancedOptions.setDisable(true);
|
advancedOptions.setDisable(true);
|
||||||
advancedOptions.setVisible(false);
|
advancedOptions.setVisible(false);
|
||||||
|
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
||||||
progressIndicator.setVisible(true);
|
progressIndicator.setVisible(true);
|
||||||
|
|
||||||
CharSequence password = passwordField.getCharacters();
|
CharSequence password = passwordField.getCharacters();
|
||||||
Tasks.create(() -> {
|
Tasks.create(() -> {
|
||||||
messageText.setText(localization.getString("unlock.pendingMessage.unlocking"));
|
progressText.setText(localization.getString("unlock.pendingMessage.unlocking"));
|
||||||
vault.unlock(password);
|
vault.unlock(password);
|
||||||
if (keychainAccess.isPresent() && savePassword.isSelected()) {
|
if (keychainAccess.isPresent() && savePassword.isSelected()) {
|
||||||
keychainAccess.get().storePassphrase(vault.getId(), password);
|
keychainAccess.get().storePassphrase(vault.getId(), password);
|
||||||
@@ -401,10 +444,6 @@ public class UnlockController implements ViewController {
|
|||||||
messageText.setText(null);
|
messageText.setText(null);
|
||||||
downloadsPageLink.setVisible(false);
|
downloadsPageLink.setVisible(false);
|
||||||
listener.ifPresent(lstnr -> lstnr.didUnlock(vault));
|
listener.ifPresent(lstnr -> lstnr.didUnlock(vault));
|
||||||
}).onError(InvalidSettingsException.class, e -> {
|
|
||||||
messageText.setText(localization.getString("unlock.errorMessage.invalidMountPath"));
|
|
||||||
advancedOptions.setVisible(true);
|
|
||||||
customMountPathField.setStyle("-fx-border-color: red;");
|
|
||||||
}).onError(InvalidPassphraseException.class, e -> {
|
}).onError(InvalidPassphraseException.class, e -> {
|
||||||
messageText.setText(localization.getString("unlock.errorMessage.wrongPassword"));
|
messageText.setText(localization.getString("unlock.errorMessage.wrongPassword"));
|
||||||
passwordField.selectAll();
|
passwordField.selectAll();
|
||||||
@@ -420,10 +459,17 @@ public class UnlockController implements ViewController {
|
|||||||
} else if (e.getDetectedVersion() == Integer.MAX_VALUE) {
|
} else if (e.getDetectedVersion() == Integer.MAX_VALUE) {
|
||||||
messageText.setText(localization.getString("unlock.errorMessage.unauthenticVersionMac"));
|
messageText.setText(localization.getString("unlock.errorMessage.unauthenticVersionMac"));
|
||||||
}
|
}
|
||||||
}).onError(ServerLifecycleException.class, e -> {
|
}).onError(NotDirectoryException.class, e -> {
|
||||||
LOG.error("Unlock failed for technical reasons.", e);
|
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
|
||||||
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
|
advancedOptions.setVisible(true);
|
||||||
}).onError(Exception.class, e -> {
|
messageText.setText(null);
|
||||||
|
showUnlockFailedErrorDialog("unlock.failedDialog.content.mountPathNonExisting");
|
||||||
|
}).onError(DirectoryNotEmptyException.class, e -> {
|
||||||
|
LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage());
|
||||||
|
advancedOptions.setVisible(true);
|
||||||
|
messageText.setText(null);
|
||||||
|
showUnlockFailedErrorDialog("unlock.failedDialog.content.mountPathNotEmpty");
|
||||||
|
}).onError(Exception.class, e -> { // including RuntimeExceptions
|
||||||
LOG.error("Unlock failed for technical reasons.", e);
|
LOG.error("Unlock failed for technical reasons.", e);
|
||||||
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
|
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
|
||||||
}).andFinally(() -> {
|
}).andFinally(() -> {
|
||||||
@@ -432,12 +478,18 @@ public class UnlockController implements ViewController {
|
|||||||
}
|
}
|
||||||
advancedOptions.setDisable(false);
|
advancedOptions.setDisable(false);
|
||||||
progressIndicator.setVisible(false);
|
progressIndicator.setVisible(false);
|
||||||
if (advancedOptions.isVisible()) { //dirty programming, but otherwise the focus is wrong
|
progressText.setText(null);
|
||||||
customMountPathField.requestFocus();
|
|
||||||
}
|
|
||||||
}).runOnce(executor);
|
}).runOnce(executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showUnlockFailedErrorDialog(String localizableContentKey) {
|
||||||
|
String title = localization.getString("unlock.failedDialog.title");
|
||||||
|
String header = localization.getString("unlock.failedDialog.header");
|
||||||
|
String content = localization.getString(localizableContentKey);
|
||||||
|
Alert alert = DialogBuilderUtil.buildErrorDialog(title, header, content, ButtonType.OK);
|
||||||
|
alert.show();
|
||||||
|
}
|
||||||
|
|
||||||
/* callback */
|
/* callback */
|
||||||
|
|
||||||
public void setListener(UnlockListener listener) {
|
public void setListener(UnlockListener listener) {
|
||||||
@@ -453,9 +505,9 @@ public class UnlockController implements ViewController {
|
|||||||
/* state */
|
/* state */
|
||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
UNLOCKING(null),
|
UNLOCKING(null), //
|
||||||
INITIALIZED("unlock.successLabel.vaultCreated"),
|
INITIALIZED("unlock.successLabel.vaultCreated"), //
|
||||||
PASSWORD_CHANGED("unlock.successLabel.passwordChanged"),
|
PASSWORD_CHANGED("unlock.successLabel.passwordChanged"), //
|
||||||
UPGRADED("unlock.successLabel.upgraded");
|
UPGRADED("unlock.successLabel.upgraded");
|
||||||
|
|
||||||
private Optional<String> successMessage;
|
private Optional<String> successMessage;
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ import javafx.scene.control.Label;
|
|||||||
import javafx.scene.control.ProgressIndicator;
|
import javafx.scene.control.ProgressIndicator;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import org.cryptomator.ui.controls.SecPasswordField;
|
import org.cryptomator.ui.controls.SecPasswordField;
|
||||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
|
||||||
import org.cryptomator.ui.model.UpgradeStrategy;
|
import org.cryptomator.ui.model.upgrade.UpgradeStrategy;
|
||||||
import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException;
|
import org.cryptomator.ui.model.upgrade.UpgradeStrategy.UpgradeFailedException;
|
||||||
import org.cryptomator.ui.model.Vault;
|
import org.cryptomator.ui.model.Vault;
|
||||||
import org.cryptomator.ui.util.Tasks;
|
import org.cryptomator.ui.util.Tasks;
|
||||||
import org.fxmisc.easybind.EasyBind;
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
|||||||
@@ -5,20 +5,18 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.controllers;
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
@FxApplicationScoped
|
||||||
import javax.inject.Provider;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
|
||||||
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class ViewControllerLoader {
|
public class ViewControllerLoader {
|
||||||
|
|
||||||
private final Map<Class<? extends ViewController>, Provider<ViewController>> controllerProviders;
|
private final Map<Class<? extends ViewController>, Provider<ViewController>> controllerProviders;
|
||||||
|
|||||||
@@ -8,22 +8,6 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.cryptomator.ui.controllers;
|
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.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
@@ -39,15 +23,31 @@ import javafx.scene.control.Label;
|
|||||||
import javafx.scene.control.ProgressIndicator;
|
import javafx.scene.control.ProgressIndicator;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.FxApplicationScoped;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
import org.cryptomator.ui.util.Tasks;
|
import org.cryptomator.ui.util.Tasks;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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;
|
import static org.cryptomator.ui.util.DialogBuilderUtil.buildYesNoDialog;
|
||||||
|
|
||||||
@Singleton
|
@FxApplicationScoped
|
||||||
public class WelcomeController implements ViewController {
|
public class WelcomeController implements ViewController {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class);
|
||||||
|
|||||||
@@ -8,23 +8,79 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.cryptomator.ui.controls;
|
package org.cryptomator.ui.controls;
|
||||||
|
|
||||||
import java.util.Arrays;
|
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.PasswordField;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.input.DragEvent;
|
import javafx.scene.input.DragEvent;
|
||||||
import javafx.scene.input.Dragboard;
|
import javafx.scene.input.Dragboard;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.input.TransferMode;
|
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compromise in security. While the text can be swiped, any access to the {@link #getText()} method will create a copy of the String in the heap.
|
* Patched PasswordField that doesn't create String copies of the password in memory. Instead the password is stored in a char[] that can be swiped.
|
||||||
|
*
|
||||||
|
* @implNote Since {@link #setText(String)} is final, we can not override its behaviour. For that reason you should not use the {@link #textProperty()} for anything else than display purposes.
|
||||||
*/
|
*/
|
||||||
public class SecPasswordField extends PasswordField {
|
public class SecPasswordField extends PasswordField {
|
||||||
|
|
||||||
private static final char SWIPE_CHAR = ' ';
|
private static final char SWIPE_CHAR = ' ';
|
||||||
|
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() {
|
public SecPasswordField() {
|
||||||
this.onDragOverProperty().set(this::handleDragOver);
|
this("", "");
|
||||||
this.onDragDroppedProperty().set(this::handleDragDropped);
|
}
|
||||||
|
|
||||||
|
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) {
|
private void handleDragOver(DragEvent event) {
|
||||||
@@ -43,26 +99,160 @@ public class SecPasswordField extends PasswordField {
|
|||||||
event.consume();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link #getContent()} uses a StringBuilder, which in turn is backed by a char[].
|
* @return <code>true</code> if any {@link Character#isISOControl(char) control character} is present in the current value of this password field.
|
||||||
* The delete operation of AbstractStringBuilder closes the gap, that forms by deleting chars, by moving up the following chars.
|
* @implNote runs in O(n)
|
||||||
* <br/>
|
*/
|
||||||
* Imagine the following example with <code>pass</code> being the password, <code>x</code> being the swipe char and <code>'</code> being the offset of the char array:
|
boolean containsNonPrintableCharacters() {
|
||||||
* <ol>
|
for (int i = 0; i < length; i++) {
|
||||||
* <li>Append filling chars to the end of the password: <code>passxxxx'</code></li>
|
if (Character.isISOControl(content[i])) {
|
||||||
* <li>Delete first 4 chars. Internal implementation will then copy subsequent chars to the position, where the deletion occured: <code>xxxx'xxxx</code></li>
|
return true;
|
||||||
* <li>Delete first 4 chars again, as we appended 4 chars in step 1: <code>'xxxxxx</code></li>
|
}
|
||||||
* </ol>
|
}
|
||||||
|
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 = normalizedText.length();
|
||||||
|
int delta = added - removed;
|
||||||
|
|
||||||
|
// 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 (length > content.length) {
|
||||||
|
char[] newContent = new char[length + GROW_BUFFER_SIZE];
|
||||||
|
System.arraycopy(content, 0, newContent, 0, content.length);
|
||||||
|
swipe(content);
|
||||||
|
this.content = newContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a CharSequence by wrapping the password characters.
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
public CharSequence getCharacters() {
|
||||||
|
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);
|
||||||
|
length = password.length;
|
||||||
|
|
||||||
|
String placeholderString = Strings.repeat(PLACEHOLDER, password.length);
|
||||||
|
setText(placeholderString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the stored password by overriding each character with a different character.
|
||||||
*/
|
*/
|
||||||
public void swipe() {
|
public void swipe() {
|
||||||
final int pwLength = this.getContent().length();
|
swipe(content);
|
||||||
final char[] fillingChars = new char[pwLength];
|
length = 0;
|
||||||
Arrays.fill(fillingChars, SWIPE_CHAR);
|
}
|
||||||
this.getContent().insert(pwLength, new String(fillingChars), false);
|
|
||||||
this.getContent().delete(0, pwLength, true);
|
private void swipe(char[] buffer) {
|
||||||
this.getContent().delete(0, pwLength, true);
|
Arrays.fill(buffer, SWIPE_CHAR);
|
||||||
// previous text has now been overwritten. but we still need to update the text to trigger some property bindings:
|
|
||||||
this.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,13 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.l10n;
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
@@ -19,16 +26,7 @@ import java.util.Objects;
|
|||||||
import java.util.PropertyResourceBundle;
|
import java.util.PropertyResourceBundle;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
@FxApplicationScoped
|
||||||
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
|
|
||||||
public class Localization extends ResourceBundle {
|
public class Localization extends ResourceBundle {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Localization.class);
|
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;
|
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.io.IOException;
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -14,15 +21,7 @@ import java.util.Optional;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
@FxApplicationScoped
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.cryptomator.cryptolib.api.CryptoException;
|
|
||||||
import org.cryptomator.keychain.KeychainAccess;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class AutoUnlocker {
|
public class AutoUnlocker {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AutoUnlocker.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AutoUnlocker.class);
|
||||||
|
|||||||
@@ -6,14 +6,24 @@ import org.cryptomator.cryptofs.CryptoFileSystem;
|
|||||||
import org.cryptomator.frontend.dokany.Mount;
|
import org.cryptomator.frontend.dokany.Mount;
|
||||||
import org.cryptomator.frontend.dokany.MountFactory;
|
import org.cryptomator.frontend.dokany.MountFactory;
|
||||||
import org.cryptomator.frontend.dokany.MountFailedException;
|
import org.cryptomator.frontend.dokany.MountFailedException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.NotDirectoryException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
public class DokanyVolume implements Volume {
|
public class DokanyVolume implements Volume {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DokanyVolume.class);
|
||||||
|
|
||||||
private static final String FS_TYPE_NAME = "Cryptomator File System";
|
private static final String FS_TYPE_NAME = "Cryptomator File System";
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
@@ -28,37 +38,52 @@ public class DokanyVolume implements Volume {
|
|||||||
this.windowsDriveLetters = windowsDriveLetters;
|
this.windowsDriveLetters = windowsDriveLetters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported() {
|
public boolean isSupported() {
|
||||||
return DokanyVolume.isSupportedStatic();
|
return DokanyVolume.isSupportedStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs) throws VolumeException {
|
public void mount(CryptoFileSystem fs) throws VolumeException, IOException {
|
||||||
Path mountPath = Paths.get(getMountPathString());
|
Path mountPath = getMountPoint();
|
||||||
String mountName = vaultSettings.mountName().get();
|
String mountName = vaultSettings.mountName().get();
|
||||||
try {
|
try {
|
||||||
this.mount = mountFactory.mount(fs.getPath("/"), mountPath, mountName, FS_TYPE_NAME);
|
this.mount = mountFactory.mount(fs.getPath("/"), mountPath, mountName, FS_TYPE_NAME);
|
||||||
} catch (MountFailedException e) {
|
} catch (MountFailedException e) {
|
||||||
|
if (vaultSettings.getIndividualMountPath().isPresent()) {
|
||||||
|
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPath);
|
||||||
|
}
|
||||||
throw new VolumeException("Unable to mount Filesystem", e);
|
throw new VolumeException("Unable to mount Filesystem", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getMountPathString() throws VolumeException {
|
private Path getMountPoint() throws VolumeException, IOException {
|
||||||
if (vaultSettings.usesIndividualMountPath().get()) {
|
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
|
||||||
return vaultSettings.individualMountPath().get();
|
if (optionalCustomMountPoint.isPresent()) {
|
||||||
|
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
||||||
|
checkProvidedMountPoint(customMountPoint);
|
||||||
|
return customMountPoint;
|
||||||
} else if (!Strings.isNullOrEmpty(vaultSettings.winDriveLetter().get())) {
|
} else if (!Strings.isNullOrEmpty(vaultSettings.winDriveLetter().get())) {
|
||||||
return vaultSettings.winDriveLetter().get().charAt(0) + ":\\";
|
return Paths.get(vaultSettings.winDriveLetter().get().charAt(0) + ":\\");
|
||||||
} else {
|
} else {
|
||||||
//auto assign drive letter
|
//auto assign drive letter
|
||||||
if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
|
if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
|
||||||
return windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\";
|
return Paths.get(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\");
|
||||||
} else {
|
} else {
|
||||||
throw new VolumeException("No free drive letter available.");
|
throw new VolumeException("No free drive letter available.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkProvidedMountPoint(Path mountPoint) throws IOException {
|
||||||
|
if (!Files.isDirectory(mountPoint)) {
|
||||||
|
throw new NotDirectoryException(mountPoint.toString());
|
||||||
|
}
|
||||||
|
try (DirectoryStream<Path> ds = Files.newDirectoryStream(mountPoint)) {
|
||||||
|
if (ds.iterator().hasNext()) {
|
||||||
|
throw new DirectoryNotEmptyException(mountPoint.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.ui.model;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
|
||||||
import java.nio.file.DirectoryStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
|
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
|
||||||
@@ -20,65 +12,94 @@ import org.cryptomator.frontend.fuse.mount.Mount;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class FuseVolume implements Volume {
|
public class FuseVolume implements Volume {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
|
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
|
||||||
|
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
|
||||||
/**
|
private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase().contains("mac");
|
||||||
* TODO: dont use fixed Strings and rather set them in some system environment variables in the cryptomator installer and load those!
|
|
||||||
*/
|
|
||||||
private static final String DEFAULT_MOUNTROOTPATH_MAC = System.getProperty("user.home") + "/Library/Application Support/Cryptomator";
|
|
||||||
private static final String DEFAULT_MOUNTROOTPATH_LINUX = System.getProperty("user.home") + "/.Cryptomator";
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
|
private final Environment environment;
|
||||||
|
|
||||||
private Mount fuseMnt;
|
private Mount fuseMnt;
|
||||||
private Path mountPath;
|
private Path mountPoint;
|
||||||
private boolean extraDirCreated;
|
private boolean createdTemporaryMountPoint;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FuseVolume(VaultSettings vaultSettings) {
|
public FuseVolume(VaultSettings vaultSettings, Environment environment) {
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.extraDirCreated = false;
|
this.environment = environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs) throws IOException, FuseNotSupportedException, VolumeException {
|
public void mount(CryptoFileSystem fs) throws IOException, FuseNotSupportedException, VolumeException {
|
||||||
String mountPath;
|
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
|
||||||
if (vaultSettings.usesIndividualMountPath().get()) {
|
if (optionalCustomMountPoint.isPresent()) {
|
||||||
//specific path given
|
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
||||||
mountPath = vaultSettings.individualMountPath().get();
|
checkProvidedMountPoint(customMountPoint);
|
||||||
|
this.mountPoint = customMountPoint;
|
||||||
|
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
|
||||||
} else {
|
} else {
|
||||||
//choose default path & create extra directory
|
this.mountPoint = prepareTemporaryMountPoint();
|
||||||
mountPath = createDirIfNotExist(SystemUtils.IS_OS_MAC ? DEFAULT_MOUNTROOTPATH_MAC : DEFAULT_MOUNTROOTPATH_LINUX, vaultSettings.mountName().get());
|
LOG.debug("Successfully created mount point: {}", mountPoint);
|
||||||
extraDirCreated = true;
|
|
||||||
}
|
}
|
||||||
this.mountPath = Paths.get(mountPath).toAbsolutePath();
|
|
||||||
mount(fs.getPath("/"));
|
mount(fs.getPath("/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createDirIfNotExist(String prefix, String dirName) throws IOException {
|
private void checkProvidedMountPoint(Path mountPoint) throws IOException {
|
||||||
Path p = Paths.get(prefix, dirName + vaultSettings.getId());
|
if (!Files.isDirectory(mountPoint)) {
|
||||||
if (Files.isDirectory(p)) {
|
throw new NotDirectoryException(mountPoint.toString());
|
||||||
try (DirectoryStream<Path> emptyCheck = Files.newDirectoryStream(p)) {
|
|
||||||
if (emptyCheck.iterator().hasNext()) {
|
|
||||||
throw new DirectoryNotEmptyException("Mount point is not empty.");
|
|
||||||
} else {
|
|
||||||
LOG.info("Directory already exists and is empty. Using it as mount point.");
|
|
||||||
return p.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Files.createDirectory(p);
|
|
||||||
return p.toString();
|
|
||||||
}
|
}
|
||||||
|
try (DirectoryStream<Path> ds = Files.newDirectoryStream(mountPoint)) {
|
||||||
|
if (ds.iterator().hasNext()) {
|
||||||
|
throw new DirectoryNotEmptyException(mountPoint.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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++) {
|
||||||
|
Path mountPoint = parent.resolve(basename + "_" + i);
|
||||||
|
if (Files.notExists(mountPoint)) {
|
||||||
|
return mountPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
private void mount(Path root) throws VolumeException {
|
||||||
try {
|
try {
|
||||||
EnvironmentVariables envVars = EnvironmentVariables.create()
|
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
||||||
.withMountName(vaultSettings.mountName().getValue())
|
.withMountName(vaultSettings.mountName().getValue()) //
|
||||||
.withMountPath(mountPath)
|
.withMountPath(mountPoint) //
|
||||||
.build();
|
.build();
|
||||||
this.fuseMnt = FuseMountFactory.getMounter().mount(root, envVars);
|
this.fuseMnt = FuseMountFactory.getMounter().mount(root, envVars);
|
||||||
} catch (CommandFailedException e) {
|
} catch (CommandFailedException e) {
|
||||||
@@ -91,27 +112,45 @@ public class FuseVolume implements Volume {
|
|||||||
try {
|
try {
|
||||||
fuseMnt.revealInFileManager();
|
fuseMnt.revealInFileManager();
|
||||||
} catch (CommandFailedException e) {
|
} catch (CommandFailedException e) {
|
||||||
LOG.info("Revealing the vault in file manger failed: " + e.getMessage());
|
LOG.debug("Revealing the vault in file manger failed: " + e.getMessage());
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void unmount() throws VolumeException {
|
public boolean supportsForcedUnmount() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void unmountForced() throws VolumeException {
|
||||||
try {
|
try {
|
||||||
|
fuseMnt.unmountForced();
|
||||||
fuseMnt.close();
|
fuseMnt.close();
|
||||||
} catch (CommandFailedException e) {
|
} catch (CommandFailedException e) {
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
cleanup();
|
cleanupTemporaryMountPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanup() {
|
@Override
|
||||||
if (extraDirCreated) {
|
public synchronized void unmount() throws VolumeException {
|
||||||
|
try {
|
||||||
|
fuseMnt.unmount();
|
||||||
|
fuseMnt.close();
|
||||||
|
} catch (CommandFailedException e) {
|
||||||
|
throw new VolumeException(e);
|
||||||
|
}
|
||||||
|
cleanupTemporaryMountPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanupTemporaryMountPoint() {
|
||||||
|
if (createdTemporaryMountPoint) {
|
||||||
try {
|
try {
|
||||||
Files.delete(mountPath);
|
Files.delete(mountPoint);
|
||||||
|
LOG.debug("Successfully deleted mount point: {}", mountPoint);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Could not delete mounting directory:" + e.getMessage());
|
LOG.warn("Could not delete mount point: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package org.cryptomator.ui.model;
|
|
||||||
|
|
||||||
public class InvalidSettingsException extends RuntimeException {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.ui.model;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.Binding;
|
import javafx.beans.binding.Binding;
|
||||||
@@ -21,6 +22,7 @@ import org.cryptomator.common.settings.Settings;
|
|||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
import org.cryptomator.cryptolib.api.CryptoException;
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||||
@@ -35,8 +37,11 @@ import java.io.IOException;
|
|||||||
import java.nio.file.FileAlreadyExistsException;
|
import java.nio.file.FileAlreadyExistsException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.nio.file.NotDirectoryException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@@ -77,9 +82,13 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CryptoFileSystem unlockCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
|
private CryptoFileSystem unlockCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
|
||||||
|
List<FileSystemFlags> flags = new ArrayList<>();
|
||||||
|
if (vaultSettings.usesReadOnlyMode().get()) {
|
||||||
|
flags.add(FileSystemFlags.READONLY);
|
||||||
|
}
|
||||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
||||||
.withPassphrase(passphrase) //
|
.withPassphrase(passphrase) //
|
||||||
.withFlags() //
|
.withFlags(flags) //
|
||||||
.withMasterkeyFilename(MASTERKEY_FILENAME) //
|
.withMasterkeyFilename(MASTERKEY_FILENAME) //
|
||||||
.build();
|
.build();
|
||||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||||
@@ -97,11 +106,11 @@ public class Vault {
|
|||||||
CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase);
|
CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void unlock(CharSequence passphrase) throws InvalidSettingsException, CryptoException, IOException, Volume.VolumeException {
|
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
|
||||||
Platform.runLater(() -> state.set(State.PROCESSING));
|
Platform.runLater(() -> state.set(State.PROCESSING));
|
||||||
try {
|
try {
|
||||||
if (vaultSettings.usesIndividualMountPath().and(vaultSettings.individualMountPath().isEmpty()).get()) {
|
if (vaultSettings.usesIndividualMountPath().get() && Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
|
||||||
throw new InvalidSettingsException();
|
throw new NotDirectoryException("");
|
||||||
}
|
}
|
||||||
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
|
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
|
||||||
volume = volumeProvider.get();
|
volume = volumeProvider.get();
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.ui.model;
|
||||||
|
|
||||||
|
import dagger.BindsInstance;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.ui.model.VaultModule.PerVault;
|
import org.cryptomator.ui.model.VaultModule.PerVault;
|
||||||
|
|
||||||
import dagger.Subcomponent;
|
import dagger.Subcomponent;
|
||||||
@@ -17,7 +19,9 @@ public interface VaultComponent {
|
|||||||
|
|
||||||
@Subcomponent.Builder
|
@Subcomponent.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
Builder vaultModule(VaultModule module);
|
|
||||||
|
@BindsInstance
|
||||||
|
Builder vaultSettings(VaultSettings vaultSettings);
|
||||||
|
|
||||||
VaultComponent build();
|
VaultComponent build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,14 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.model;
|
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.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
@FxApplicationScoped
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class VaultFactory {
|
public class VaultFactory {
|
||||||
|
|
||||||
private final VaultComponent.Builder vaultComponentBuilder;
|
private final VaultComponent.Builder vaultComponentBuilder;
|
||||||
@@ -32,8 +31,7 @@ public class VaultFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Vault create(VaultSettings vaultSettings) {
|
private Vault create(VaultSettings vaultSettings) {
|
||||||
VaultModule module = new VaultModule(vaultSettings);
|
VaultComponent comp = vaultComponentBuilder.vaultSettings(vaultSettings).build();
|
||||||
VaultComponent comp = vaultComponentBuilder.vaultModule(module).build();
|
|
||||||
return comp.vault();
|
return comp.vault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,22 +5,19 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.model;
|
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 com.google.common.collect.Lists;
|
||||||
|
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.collections.transformation.TransformationList;
|
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> {
|
public class VaultList extends TransformationList<Vault, VaultSettings> {
|
||||||
|
|
||||||
private final VaultFactory vaultFactory;
|
private final VaultFactory vaultFactory;
|
||||||
|
|||||||
@@ -5,37 +5,22 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.model;
|
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.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
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
|
@Module
|
||||||
public class VaultModule {
|
public class VaultModule {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class);
|
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
|
@Scope
|
||||||
@Documented
|
@Documented
|
||||||
|
|||||||
@@ -5,6 +5,11 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.model;
|
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.FileSystems;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -13,13 +18,7 @@ import java.util.stream.Collectors;
|
|||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
@FxApplicationScoped
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.CharUtils;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public final class WindowsDriveLetters {
|
public final class WindowsDriveLetters {
|
||||||
|
|
||||||
private static final Set<Character> D_TO_Z;
|
private static final Set<Character> D_TO_Z;
|
||||||
|
|||||||
+6
-5
@@ -3,17 +3,18 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* 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.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
@FxApplicationScoped
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class UpgradeStrategies {
|
public class UpgradeStrategies {
|
||||||
|
|
||||||
private final Collection<UpgradeStrategy> strategies;
|
private final Collection<UpgradeStrategy> strategies;
|
||||||
+2
-1
@@ -3,7 +3,7 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* 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.io.IOException;
|
||||||
import java.nio.file.Files;
|
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.KeyFile;
|
||||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
|
import org.cryptomator.ui.model.Vault;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
+13
-14
@@ -3,25 +3,24 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* 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.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
@FxApplicationScoped
|
||||||
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
|
|
||||||
class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3DropBundleExtension.class);
|
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3DropBundleExtension.class);
|
||||||
+13
-12
@@ -3,10 +3,20 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* 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.Inject;
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.FileVisitOption;
|
import java.nio.file.FileVisitOption;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
@@ -19,22 +29,13 @@ import java.util.EnumSet;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
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;
|
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.
|
* 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.
|
* 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 {
|
class UpgradeVersion3to4 extends UpgradeStrategy {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3to4.class);
|
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3to4.class);
|
||||||
+12
-12
@@ -3,8 +3,18 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* 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.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
@@ -18,21 +28,11 @@ import java.nio.file.attribute.BasicFileAttributes;
|
|||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.regex.Pattern;
|
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.
|
* 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.
|
* 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 {
|
class UpgradeVersion4to5 extends UpgradeStrategy {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion4to5.class);
|
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion4to5.class);
|
||||||
+7
-7
@@ -3,23 +3,23 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* 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 javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
|
import org.cryptomator.common.FxApplicationScoped;
|
||||||
import org.cryptomator.cryptofs.migration.Migrators;
|
import org.cryptomator.cryptofs.migration.Migrators;
|
||||||
import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException;
|
import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException;
|
||||||
import org.cryptomator.cryptolib.Cryptors;
|
import org.cryptomator.cryptolib.Cryptors;
|
||||||
import org.cryptomator.cryptolib.api.Cryptor;
|
import org.cryptomator.cryptolib.api.Cryptor;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
|
import org.cryptomator.ui.model.Vault;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@Singleton
|
import javax.inject.Inject;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@FxApplicationScoped
|
||||||
class UpgradeVersion5toX extends UpgradeStrategy {
|
class UpgradeVersion5toX extends UpgradeStrategy {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion5toX.class);
|
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion5toX.class);
|
||||||
@@ -8,24 +8,21 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.util;
|
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.google.common.base.Strings;
|
||||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||||
|
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.layout.Background;
|
import javafx.scene.layout.Background;
|
||||||
import javafx.scene.layout.BackgroundFill;
|
import javafx.scene.layout.BackgroundFill;
|
||||||
import javafx.scene.layout.CornerRadii;
|
import javafx.scene.layout.CornerRadii;
|
||||||
import javafx.scene.paint.Color;
|
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 {
|
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
|
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>
|
<children>
|
||||||
<!-- Row 0 -->
|
<!-- Row 0 -->
|
||||||
<Label text="%changePassword.label.oldPassword" GridPane.rowIndex="0" GridPane.columnIndex="0" cacheShape="true" cache="true" />
|
<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 -->
|
<!-- Row 1 -->
|
||||||
<Label text="%changePassword.label.newPassword" GridPane.rowIndex="1" GridPane.columnIndex="0" cacheShape="true" cache="true" />
|
<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 -->
|
<!-- Row 2 -->
|
||||||
<Label text="%changePassword.label.retypePassword" GridPane.rowIndex="2" GridPane.columnIndex="0" cacheShape="true" cache="true" />
|
<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 -->
|
<!-- Row 3 -->
|
||||||
<VBox GridPane.columnIndex="1" GridPane.rowIndex="3" spacing="6.0">
|
<VBox GridPane.columnIndex="1" GridPane.rowIndex="3" spacing="6.0">
|
||||||
|
|||||||
@@ -36,11 +36,11 @@
|
|||||||
<children>
|
<children>
|
||||||
<!-- Row 0 -->
|
<!-- Row 0 -->
|
||||||
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.password" cacheShape="true" cache="true" />
|
<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 -->
|
<!-- Row 1 -->
|
||||||
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%initialize.label.retypePassword" cacheShape="true" cache="true" />
|
<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 -->
|
<!-- Row 2 -->
|
||||||
<VBox GridPane.columnIndex="1" GridPane.rowIndex="2" spacing="6.0">
|
<VBox GridPane.columnIndex="1" GridPane.rowIndex="2" spacing="6.0">
|
||||||
|
|||||||
@@ -56,5 +56,4 @@
|
|||||||
|
|
||||||
</children>
|
</children>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
<Label VBox.vgrow="NEVER" text="%settings.requiresRestartLabel" alignment="CENTER" cacheShape="true" cache="true" />
|
|
||||||
</VBox>
|
</VBox>
|
||||||
@@ -7,23 +7,20 @@
|
|||||||
Contributors:
|
Contributors:
|
||||||
Sebastian Stenzel - initial API and implementation
|
Sebastian Stenzel - initial API and implementation
|
||||||
-->
|
-->
|
||||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.CheckBox?>
|
||||||
|
<?import javafx.scene.control.ChoiceBox?>
|
||||||
|
<?import javafx.scene.control.Hyperlink?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.ProgressIndicator?>
|
<?import javafx.scene.control.ProgressIndicator?>
|
||||||
<?import javafx.scene.control.CheckBox?>
|
|
||||||
<?import javafx.scene.control.Button?>
|
|
||||||
<?import javafx.scene.layout.GridPane?>
|
|
||||||
<?import javafx.geometry.Insets?>
|
|
||||||
<?import javafx.scene.layout.ColumnConstraints?>
|
|
||||||
<?import javafx.scene.control.TextField?>
|
|
||||||
<?import javafx.scene.text.TextFlow?>
|
|
||||||
<?import javafx.scene.control.Hyperlink?>
|
|
||||||
<?import javafx.scene.text.Text?>
|
|
||||||
<?import javafx.scene.layout.HBox?>
|
|
||||||
<?import javafx.scene.control.Separator?>
|
<?import javafx.scene.control.Separator?>
|
||||||
<?import javafx.scene.control.ChoiceBox?>
|
<?import javafx.scene.control.TextField?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.text.Text?>
|
||||||
|
<?import javafx.scene.text.TextFlow?>
|
||||||
|
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||||
<GridPane fx:controller="org.cryptomator.ui.controllers.UnlockController" fx:id="root" vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
<GridPane fx:controller="org.cryptomator.ui.controllers.UnlockController" fx:id="root" vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets top="24.0" right="12.0" bottom="24.0" left="12.0" />
|
<Insets top="24.0" right="12.0" bottom="24.0" left="12.0" />
|
||||||
@@ -37,7 +34,7 @@
|
|||||||
<children>
|
<children>
|
||||||
<!-- Row 0 -->
|
<!-- Row 0 -->
|
||||||
<Label text="%unlock.label.password" GridPane.rowIndex="0" GridPane.columnIndex="0" cacheShape="true" cache="true" />
|
<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 -->
|
<!-- Row 1 -->
|
||||||
<HBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="12.0" alignment="CENTER_RIGHT" cacheShape="true" cache="true">
|
<HBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="12.0" alignment="CENTER_RIGHT" cacheShape="true" cache="true">
|
||||||
@@ -46,7 +43,7 @@
|
|||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<!-- Row 3 -->
|
<!-- 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 -->
|
<!-- 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">
|
<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">
|
||||||
@@ -83,16 +80,23 @@
|
|||||||
<CheckBox GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="revealAfterMount" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
|
<CheckBox GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="revealAfterMount" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
|
||||||
|
|
||||||
<!-- Row 3.5 -->
|
<!-- Row 3.5 -->
|
||||||
<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="useCustomMountPath" text="%unlock.label.useOwnMountPath" cacheShape="true" cache="true" />
|
<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="useReadOnlyMode" text="%unlock.label.useReadOnlyMode" cacheShape="true" cache="true" />
|
||||||
|
|
||||||
<!-- Row 3.6 Alt1 -->
|
<!-- Row 3.6 -->
|
||||||
<Label GridPane.rowIndex="6" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
|
<CheckBox GridPane.rowIndex="6" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="useCustomMountPoint" text="%unlock.label.useOwnMountPath" cacheShape="true" cache="true" />
|
||||||
<ChoiceBox GridPane.rowIndex="6" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
|
||||||
|
|
||||||
<!-- Row 3.6 Alt2 -->
|
<!-- Row 3.7 Alt1 -->
|
||||||
<Label GridPane.rowIndex="6" GridPane.columnIndex="0" fx:id="customMountPathLabel" text="%unlock.label.mountPath" cacheShape="true" cache="true" />
|
<Label GridPane.rowIndex="7" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
|
||||||
<TextField GridPane.rowIndex="6" GridPane.columnIndex="1" fx:id="customMountPathField" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
<ChoiceBox GridPane.rowIndex="7" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||||
|
|
||||||
|
<!-- Row 3.7 Alt2 -->
|
||||||
|
<HBox fx:id="customMountPoint" GridPane.rowIndex="7" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="6" alignment="BASELINE_LEFT" cacheShape="true" cache="true">
|
||||||
|
<padding>
|
||||||
|
<Insets left="20.0" />
|
||||||
|
</padding>
|
||||||
|
<Label HBox.hgrow="ALWAYS" fx:id="customMountPointLabel" textOverrun="LEADING_ELLIPSIS" cacheShape="true" cache="true" />
|
||||||
|
<Button HBox.hgrow="NEVER" minWidth="-Infinity" text="" styleClass="ionicons" onAction="#didClickChooseCustomMountPoint" focusTraversable="true" cacheShape="true" cache="true" />
|
||||||
|
</HBox>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
|
|
||||||
<!-- Row 4 -->
|
<!-- Row 4 -->
|
||||||
@@ -108,7 +112,7 @@
|
|||||||
<!-- Row 5 -->
|
<!-- Row 5 -->
|
||||||
<VBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="12.0" alignment="CENTER" cacheShape="true" cache="true">
|
<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" />
|
<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>
|
</VBox>
|
||||||
</children>
|
</children>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = دخول تلقائي
|
|||||||
unlock.errorMessage.wrongPassword = كلمة مرور خاطئة
|
unlock.errorMessage.wrongPassword = كلمة مرور خاطئة
|
||||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = محفظة غير مدعومة . هذه المحفظة تم انشاؤها بواسطة اصدار اقدم من كريبتوماتور
|
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = محفظة غير مدعومة . هذه المحفظة تم انشاؤها بواسطة اصدار اقدم من كريبتوماتور
|
||||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = محفظة غير مدعومة . هذه المحفظة تم انشاؤها بواسطة اصدار احدث من كريبتوماتور
|
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = محفظة غير مدعومة . هذه المحفظة تم انشاؤها بواسطة اصدار احدث من كريبتوماتور
|
||||||
unlock.messageLabel.startServerFailed = Starting WebDAV server failed.
|
|
||||||
# change_password.fxml
|
# change_password.fxml
|
||||||
changePassword.label.oldPassword = كلمة المرور القديمة
|
changePassword.label.oldPassword = كلمة المرور القديمة
|
||||||
changePassword.label.newPassword = كلمة المرور الجديدة
|
changePassword.label.newPassword = كلمة المرور الجديدة
|
||||||
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Throughput (MiB/s)
|
|||||||
# settings.fxml
|
# settings.fxml
|
||||||
settings.version.label = الاصدار %s
|
settings.version.label = الاصدار %s
|
||||||
settings.checkForUpdates.label = افحص التحديثات
|
settings.checkForUpdates.label = افحص التحديثات
|
||||||
settings.requiresRestartLabel = * Cryptomator needs to restart
|
|
||||||
# tray icon
|
# tray icon
|
||||||
tray.menu.open = فتح
|
tray.menu.open = فتح
|
||||||
tray.menu.quit = اغلاق
|
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
|
upgrade.confirmation.label = Yes, I've made sure that synchronization has finished
|
||||||
unlock.label.savePassword = حفظ كلمة المرور
|
unlock.label.savePassword = حفظ كلمة المرور
|
||||||
unlock.errorMessage.unauthenticVersionMac = Could not authenticate version MAC.
|
unlock.errorMessage.unauthenticVersionMac = Could not authenticate version MAC.
|
||||||
unlocked.label.mountFailed = Connecting drive failed
|
|
||||||
unlock.savePassword.delete.confirmation.title = حذف كلمة المرور المحفوظة
|
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.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.
|
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.version3dropBundleExtension.title = Vault Version 3 Upgrade (Drop Bundle Extension)
|
||||||
upgrade.version3to4.title = Vault Version 3 to 4 Upgrade
|
upgrade.version3to4.title = Vault Version 3 to 4 Upgrade
|
||||||
upgrade.version4to5.title = Vault Version 4 to 5 Upgrade
|
upgrade.version4to5.title = Vault Version 4 to 5 Upgrade
|
||||||
@@ -96,8 +93,6 @@ upgrade.version5toX.msg = This vault needs to be migrated to a newer format.\nPl
|
|||||||
main.createVault.nonEmptyDir.title = Creating vault failed
|
main.createVault.nonEmptyDir.title = Creating vault failed
|
||||||
main.createVault.nonEmptyDir.header = Chosen directory is not empty
|
main.createVault.nonEmptyDir.header = Chosen directory is not empty
|
||||||
main.createVault.nonEmptyDir.content = The selected directory already contains files (possibly hidden). A vault can only be created in an empty directory.
|
main.createVault.nonEmptyDir.content = The selected directory already contains files (possibly hidden). A vault can only be created in an empty directory.
|
||||||
unlock.label.mountPath = Mount path
|
|
||||||
unlock.label.mountPathButton = Apply
|
|
||||||
settings.webdav.port.label = WebDAV Port
|
settings.webdav.port.label = WebDAV Port
|
||||||
settings.webdav.port.prompt = 0 \= Choose automatically
|
settings.webdav.port.prompt = 0 \= Choose automatically
|
||||||
settings.webdav.port.apply = Apply
|
settings.webdav.port.apply = Apply
|
||||||
@@ -107,16 +102,23 @@ settings.volume.webdav = WebDAV
|
|||||||
settings.volume.fuse = FUSE
|
settings.volume.fuse = FUSE
|
||||||
unlock.successLabel.vaultCreated = Vault was successfully created.
|
unlock.successLabel.vaultCreated = Vault was successfully created.
|
||||||
unlock.successLabel.passwordChanged = Password was successfully changed.
|
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
|
unlock.label.useOwnMountPath = Use Custom Mount Point
|
||||||
welcome.askForUpdateCheck.dialog.title = Update check
|
welcome.askForUpdateCheck.dialog.title = Update check
|
||||||
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
|
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
|
||||||
welcome.askForUpdateCheck.dialog.content = Recommended\: Enable the update check to always be sure you have the newest version of Cryptomator, with all security patches, installed.\n\nYou can change this from within the settings at any time.
|
welcome.askForUpdateCheck.dialog.content = Recommended\: Enable the update check to always be sure you have the newest version of Cryptomator, with all security patches, installed.\n\nYou can change this from within the settings at any time.
|
||||||
settings.volume.dokany = Dokany
|
settings.volume.dokany = Dokany
|
||||||
unlock.errorMessage.invalidMountPath = Use of custom mount path selected, but no path given.
|
|
||||||
main.gracefulShutdown.dialog.title = Locking vault(s) failed
|
main.gracefulShutdown.dialog.title = Locking vault(s) failed
|
||||||
main.gracefulShutdown.dialog.header = Vault(s) in use
|
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.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.tryAgain = Try Again
|
||||||
main.gracefulShutdown.button.forceShutdown = Force shutdown
|
main.gracefulShutdown.button.forceShutdown = Force Shutdown
|
||||||
unlock.pendingMessage.unlocking = Unlocking vault...
|
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…
|
||||||
|
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.wrongPassword = Неправилна парола
|
||||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Неподържана версия. Този сейф е бил създаден със стара версия на Криптоматор.
|
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Неподържана версия. Този сейф е бил създаден със стара версия на Криптоматор.
|
||||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Неподържана версия. Този сейф е бил създаден с по-нова версия на Криптоматор.
|
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Неподържана версия. Този сейф е бил създаден с по-нова версия на Криптоматор.
|
||||||
unlock.messageLabel.startServerFailed = Неуспешно стартиране на WebDAV сървъра.
|
|
||||||
# change_password.fxml
|
# change_password.fxml
|
||||||
changePassword.label.oldPassword = Стара парола
|
changePassword.label.oldPassword = Стара парола
|
||||||
changePassword.label.newPassword = Нова парола
|
changePassword.label.newPassword = Нова парола
|
||||||
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Скорост (MB/s)
|
|||||||
# settings.fxml
|
# settings.fxml
|
||||||
settings.version.label = Версия %s
|
settings.version.label = Версия %s
|
||||||
settings.checkForUpdates.label = Проверка за обновления
|
settings.checkForUpdates.label = Проверка за обновления
|
||||||
settings.requiresRestartLabel = * Криптоматор трябва да се рестартира
|
|
||||||
# tray icon
|
# tray icon
|
||||||
tray.menu.open = Отворяне
|
tray.menu.open = Отворяне
|
||||||
tray.menu.quit = Изход
|
tray.menu.quit = Изход
|
||||||
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Преместването е отменено по
|
|||||||
upgrade.confirmation.label = Да, сигурен съм, че сихронизацията е приключила
|
upgrade.confirmation.label = Да, сигурен съм, че сихронизацията е приключила
|
||||||
unlock.label.savePassword = Запазване на парола
|
unlock.label.savePassword = Запазване на парола
|
||||||
unlock.errorMessage.unauthenticVersionMac = Неуспешна оторизация на MAC версията
|
unlock.errorMessage.unauthenticVersionMac = Неуспешна оторизация на MAC версията
|
||||||
unlocked.label.mountFailed = Връзката с диска неуспешна
|
|
||||||
unlock.savePassword.delete.confirmation.title = Изтриване на запазената парола
|
unlock.savePassword.delete.confirmation.title = Изтриване на запазената парола
|
||||||
unlock.savePassword.delete.confirmation.header = Неистина ли искате да изтриете запазената парола за този сейф?
|
unlock.savePassword.delete.confirmation.header = Неистина ли искате да изтриете запазената парола за този сейф?
|
||||||
unlock.savePassword.delete.confirmation.content = Запазената парола за този сейф ще бъде незабавно премахната от Вашата система. Ако желаете да запазите паролата отново, трябва да отключите сейса с пусната опция "Запазване на павола".
|
unlock.savePassword.delete.confirmation.content = Запазената парола за този сейф ще бъде незабавно премахната от Вашата система. Ако желаете да запазите паролата отново, трябва да отключите сейса с пусната опция "Запазване на павола".
|
||||||
settings.debugMode.label = Режим за отстраняване на грешки *
|
settings.debugMode.label = Режим за отстраняване на грешки
|
||||||
upgrade.version3dropBundleExtension.title = Обновяване до сейф версия 3
|
upgrade.version3dropBundleExtension.title = Обновяване до сейф версия 3
|
||||||
upgrade.version3to4.title = Обновяване на сейф от 3-та до 4-та версия
|
upgrade.version3to4.title = Обновяване на сейф от 3-та до 4-та версия
|
||||||
upgrade.version4to5.title = Обновяване на сейф от 4-та до 5-та версия
|
upgrade.version4to5.title = Обновяване на сейф от 4-та до 5-та версия
|
||||||
@@ -96,8 +93,6 @@ upgrade.version5toX.msg = Този сейф трябва да бъде обно
|
|||||||
main.createVault.nonEmptyDir.title = Неуспешно създаване на сейф
|
main.createVault.nonEmptyDir.title = Неуспешно създаване на сейф
|
||||||
main.createVault.nonEmptyDir.header = Избраната директория не е празна
|
main.createVault.nonEmptyDir.header = Избраната директория не е празна
|
||||||
main.createVault.nonEmptyDir.content = Избраната директория съдържа файлове /може би скрити/. Сейфът може да бъде създаден само в празна директория
|
main.createVault.nonEmptyDir.content = Избраната директория съдържа файлове /може би скрити/. Сейфът може да бъде създаден само в празна директория
|
||||||
unlock.label.mountPath = Път за монтиране
|
|
||||||
unlock.label.mountPathButton = Приложи
|
|
||||||
settings.webdav.port.label = WebCAM порт
|
settings.webdav.port.label = WebCAM порт
|
||||||
settings.webdav.port.prompt = 0 \= Автоматично избиране
|
settings.webdav.port.prompt = 0 \= Автоматично избиране
|
||||||
settings.webdav.port.apply = Приложи
|
settings.webdav.port.apply = Приложи
|
||||||
@@ -113,10 +108,17 @@ welcome.askForUpdateCheck.dialog.title = Update check
|
|||||||
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
|
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
|
||||||
welcome.askForUpdateCheck.dialog.content = Recommended\: Enable the update check to always be sure you have the newest version of Cryptomator, with all security patches, installed.\n\nYou can change this from within the settings at any time.
|
welcome.askForUpdateCheck.dialog.content = Recommended\: Enable the update check to always be sure you have the newest version of Cryptomator, with all security patches, installed.\n\nYou can change this from within the settings at any time.
|
||||||
settings.volume.dokany = Dokany
|
settings.volume.dokany = Dokany
|
||||||
unlock.errorMessage.invalidMountPath = Use of custom mount path selected, but no path given.
|
|
||||||
main.gracefulShutdown.dialog.title = Locking vault(s) failed
|
main.gracefulShutdown.dialog.title = Locking vault(s) failed
|
||||||
main.gracefulShutdown.dialog.header = Vault(s) in use
|
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.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.tryAgain = Try Again
|
||||||
main.gracefulShutdown.button.forceShutdown = Force shutdown
|
main.gracefulShutdown.button.forceShutdown = Force Shutdown
|
||||||
unlock.pendingMessage.unlocking = Unlocking vault...
|
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…
|
||||||
|
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
|
||||||
|
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
app.name = Cryptomator
|
||||||
|
# main.fxml
|
||||||
|
main.emptyListInstructions = Feu click ací per afegir una caixa forta
|
||||||
|
main.directoryList.contextMenu.remove = Elimina de la llista
|
||||||
|
main.directoryList.contextMenu.changePassword = Canvia la contrasenya
|
||||||
|
main.addDirectory.contextMenu.new = Crea una caixa forta nova
|
||||||
|
main.addDirectory.contextMenu.open = Obri una caixa forta existent
|
||||||
|
# welcome.fxml
|
||||||
|
welcome.checkForUpdates.label.currentlyChecking = Comprovant actualitzacions
|
||||||
|
welcome.newVersionMessage = La versió %1$s és disponible per descarregar.\nLa versió actual és %2$s.
|
||||||
|
# initialize.fxml
|
||||||
|
initialize.label.password = Contrasenya
|
||||||
|
initialize.label.retypePassword = Torneu a escriure la contrasenya
|
||||||
|
initialize.button.ok = Crea una caixa forta
|
||||||
|
initialize.messageLabel.alreadyInitialized = La caixa forta ja està inicialitzada
|
||||||
|
initialize.messageLabel.initializationFailed = No s'ha pogut inicialitzar la caixa forta. Consulteu l'arxiu de registre per a més informació.
|
||||||
|
# notfound.fxml
|
||||||
|
notfound.label = No s'ha trobat la caixa forta. S'ha mogut a altre lloc?
|
||||||
|
# upgrade.fxml
|
||||||
|
upgrade.button = Actualitza la caixa forta
|
||||||
|
upgrade.version3dropBundleExtension.msg = Esta caixa forta es deu actualitzar a un format més modern.\nEs va a canviar el nom de "%1$s" a "%2$s".\nAssegureu-vos de que la sincronització ha acabat abans d'iniciar el procés.
|
||||||
|
upgrade.version3dropBundleExtension.err.alreadyExists = Error en la migració automàtica.\n"%s" ja existeix.
|
||||||
|
# unlock.fxml
|
||||||
|
unlock.label.password = Contrasenya
|
||||||
|
unlock.label.mountName = Nom de la unitat
|
||||||
|
unlock.label.winDriveLetter = Lletra de la unitat
|
||||||
|
unlock.label.downloadsPageLink = Totes les versions de Cryptomator
|
||||||
|
unlock.label.advancedHeading = Opcions avançades
|
||||||
|
unlock.button.unlock = Debloqueja la caixa forta
|
||||||
|
unlock.button.advancedOptions.show = Més opcions
|
||||||
|
unlock.button.advancedOptions.hide = Menys opcions
|
||||||
|
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.
|
||||||
|
# change_password.fxml
|
||||||
|
changePassword.label.oldPassword = Contrasenya antiga
|
||||||
|
changePassword.label.newPassword = Contrasenya nova
|
||||||
|
changePassword.label.retypePassword = Torneu a escriure la contrasenya
|
||||||
|
changePassword.label.downloadsPageLink = Totes les versions de Cryptomator
|
||||||
|
changePassword.button.change = Canvia la contrasenya
|
||||||
|
changePassword.errorMessage.wrongPassword = Contrasenya incorrecta
|
||||||
|
changePassword.errorMessage.decryptionFailed = Ha fallat el desencriptatge
|
||||||
|
# unlocked.fxml
|
||||||
|
unlocked.button.lock = Bloqueja la caixa forta
|
||||||
|
unlocked.moreOptions.reveal = Mostra la unitat
|
||||||
|
unlocked.label.revealFailed = L'ordre ha fallat
|
||||||
|
unlocked.label.unmountFailed = Error al expulsar la unidad
|
||||||
|
unlocked.label.statsEncrypted = xifrat
|
||||||
|
unlocked.label.statsDecrypted = desxifrat
|
||||||
|
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
|
||||||
|
# tray icon
|
||||||
|
tray.menu.open = Obri
|
||||||
|
tray.menu.quit = Surt
|
||||||
|
tray.infoMsg.title = Encara s'està executant
|
||||||
|
tray.infoMsg.msg = Cryptomator encara està executant-se. Sortiu des de la icona de la safata.
|
||||||
|
tray.infoMsg.msg.osx = Cryptomator encara està executant-se. Sortiu des de la icona de la barra de menú
|
||||||
|
initialize.messageLabel.passwordStrength.0 = Molt dèbil
|
||||||
|
initialize.messageLabel.passwordStrength.1 = Dèbil
|
||||||
|
initialize.messageLabel.passwordStrength.2 = Acceptable
|
||||||
|
initialize.messageLabel.passwordStrength.3 = Forta
|
||||||
|
initialize.messageLabel.passwordStrength.4 = Molt forta
|
||||||
|
initialize.label.doNotForget = IMPORTANT\: No hi ha manera de recuperar les dades si oblideu la contrasenya.
|
||||||
|
main.directoryList.remove.confirmation.title = Suprimeix la caixa forta
|
||||||
|
main.directoryList.remove.confirmation.header = ¿Esteu segur que voleu suprimir aquesta caixa forta?
|
||||||
|
main.directoryList.remove.confirmation.content = La caixa forta només es suprimeix de la llista. Per tal de eliminar-la permanentment esborreu la caixa forta del vostre sistema de fitxers.
|
||||||
|
upgrade.version3to4.msg = S'ha de migrar la caixa forta a un format més nou.\nS'actualitzaran els noms xifrats de les carpetes.\nAssegureu-vos que la sincronització ha acabat abans de continuar.
|
||||||
|
upgrade.version3to4.err.io = Error en la migració degut a una excepció de E/S. Comproveu el registre per veure'n els detalls.\n
|
||||||
|
# upgrade.fxml
|
||||||
|
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.
|
||||||
|
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ó
|
||||||
|
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
|
||||||
|
upgrade.version4to5.msg = S'ha de migrar la caixa forta a un format més nou.\nS'actualitzaran els fitxers xifrats.\nAssegureu-vos que la sincronització ha acabat abans de continuar.\n\nNota\: la data de modificació de tots els fitxers es canviarà a la data/hora del procés.
|
||||||
|
upgrade.version4to5.err.io = La migració ha fallat a causa d'una excepció d'E/S. Comproveu el registre per veure'n els detalls.
|
||||||
|
unlock.label.revealAfterMount = Mostra la unitat
|
||||||
|
unlocked.lock.force.confirmation.title = Ha fallat el bloqueig de %1$s
|
||||||
|
unlocked.lock.force.confirmation.header = Voleu forçar el bloqueig?
|
||||||
|
unlocked.lock.force.confirmation.content = Això pot ser perquè altres programes encara estan accedint als fitxers de la caixa forta o perquè s'ha produït un altre problema.\n\nEls programes què encara estan accedint als fitxers poden funcionar incorrectament i les dades què aquests programes no hagin escrit es poden perdre.
|
||||||
|
unlock.label.unlockAfterStartup = Desbloqueig automàtic al iniciar (experimental)
|
||||||
|
unlock.errorMessage.unlockFailed = Ha fallat el desbloqueig. Comproveu el registre per veure'n els detalls.
|
||||||
|
upgrade.version5toX.title = Actualització de la versió de la caixa forta
|
||||||
|
upgrade.version5toX.msg = S'ha de migrar la caixa forta a un format més nou.\nAssegureu-vos que la sincronització ha acabat abans de continuar.
|
||||||
|
main.createVault.nonEmptyDir.title = Ha fallat la creació de la caixa forta
|
||||||
|
main.createVault.nonEmptyDir.header = El directori seleccionat no és buit
|
||||||
|
main.createVault.nonEmptyDir.content = Hi ha fitxers (possibement ocults) al directori seleccionat. Només es pot crear una caixa forta a un directori buit.
|
||||||
|
settings.webdav.port.label = Port WebDAV
|
||||||
|
settings.webdav.port.prompt = 0 \= Tria automàticament
|
||||||
|
settings.webdav.port.apply = Aplica
|
||||||
|
settings.webdav.prefGvfsScheme.label = Esquema de WebDAV
|
||||||
|
settings.volume.label = Tipus de volum preferit
|
||||||
|
settings.volume.webdav = WebDAV
|
||||||
|
settings.volume.fuse = FUSE
|
||||||
|
unlock.successLabel.vaultCreated = La caixa forta s'ha creat correctament.
|
||||||
|
unlock.successLabel.passwordChanged = La contrasenya s'ha canviat correctament.
|
||||||
|
unlock.successLabel.upgraded = Cryptomator s'ha actualitzat correctament.
|
||||||
|
unlock.label.useOwnMountPath = Utilitza un punt de muntatge personalitzat
|
||||||
|
welcome.askForUpdateCheck.dialog.title = Comprovació d'actualizacions
|
||||||
|
welcome.askForUpdateCheck.dialog.header = Activo la comprovació automàtica d'actualitzacions?
|
||||||
|
welcome.askForUpdateCheck.dialog.content = Recomanat\: Activa la comprovació d'actualitzacions per assegurar-vos que sempre teniu la darrera versió de Cryptomator instal·lada amb totes les actualitzacions de seguretat.\n\nPodeu canviar aquesta opció des de la configuració en qualsevol moment.
|
||||||
|
settings.volume.dokany = Dokany
|
||||||
|
main.gracefulShutdown.dialog.title = Ha fallat el bloqueig de la caixa(es) forta(es)
|
||||||
|
main.gracefulShutdown.dialog.header = La caixa(es) forta(es) és(són) en ús
|
||||||
|
main.gracefulShutdown.dialog.content = Hi ha programes encara estan utilitzant una caixa forta o més d'una. Tanqueu-los per permetre que Cryptomator es tanqui correctament i, a continuació, torneu-ho a intentar.\n\nSi això no funciona, es pot forçar l'aturada de Cryptomator tot i que no es recomana, donç pot comportar pèrdua de dades.
|
||||||
|
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 = 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.wrongPassword = Chybné heslo
|
||||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Nepodporovaná verze trezoru. Byl vytvořen ve starším Cryptomator.
|
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.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
|
# change_password.fxml
|
||||||
changePassword.label.oldPassword = Původní heslo
|
changePassword.label.oldPassword = Původní heslo
|
||||||
changePassword.label.newPassword = Nové heslo
|
changePassword.label.newPassword = Nové heslo
|
||||||
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Propustnost (MiB/s)
|
|||||||
# settings.fxml
|
# settings.fxml
|
||||||
settings.version.label = Verze %s
|
settings.version.label = Verze %s
|
||||||
settings.checkForUpdates.label = Zjistit případné aktualizace
|
settings.checkForUpdates.label = Zjistit případné aktualizace
|
||||||
settings.requiresRestartLabel = * Vyžaduje restart Cryptomator
|
|
||||||
# tray icon
|
# tray icon
|
||||||
tray.menu.open = Otevřít
|
tray.menu.open = Otevřít
|
||||||
tray.menu.quit = Ukončit
|
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
|
upgrade.confirmation.label = Ano, je ověřeno, že synchronizace byla dokončena
|
||||||
unlock.label.savePassword = Uložit heslo
|
unlock.label.savePassword = Uložit heslo
|
||||||
unlock.errorMessage.unauthenticVersionMac = Nedaří se ověřit MAC funkci verze.
|
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.title = Smazat uložené heslo
|
||||||
unlock.savePassword.delete.confirmation.header = Opravdu chcete smazat uložené heslo pro tento trezor?
|
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“.
|
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 :)
|
# 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.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
|
upgrade.version3to4.title = Aktualizace trezoru z verze 3 na 4
|
||||||
@@ -98,8 +95,6 @@ upgrade.version5toX.msg = Tento trezor je třeba aktualizovat na novější form
|
|||||||
main.createVault.nonEmptyDir.title = Vytvoření trezoru se nezdařilo
|
main.createVault.nonEmptyDir.title = Vytvoření trezoru se nezdařilo
|
||||||
main.createVault.nonEmptyDir.header = Zvolená složka není prázdná
|
main.createVault.nonEmptyDir.header = Zvolená složka není prázdná
|
||||||
main.createVault.nonEmptyDir.content = Zvolená složka už obsahuje soubory (možná skryté). Trezor je možné vytvořit pouze v prázdné složce.
|
main.createVault.nonEmptyDir.content = Zvolená složka už obsahuje soubory (možná skryté). Trezor je možné vytvořit pouze v prázdné složce.
|
||||||
unlock.label.mountPath = Popis umístění přípojného bodu
|
|
||||||
unlock.label.mountPathButton = Použít
|
|
||||||
settings.webdav.port.label = Port WebDAV
|
settings.webdav.port.label = Port WebDAV
|
||||||
settings.webdav.port.prompt = 0 \= zvolit automaticky
|
settings.webdav.port.prompt = 0 \= zvolit automaticky
|
||||||
settings.webdav.port.apply = Použít
|
settings.webdav.port.apply = Použít
|
||||||
@@ -115,10 +110,17 @@ welcome.askForUpdateCheck.dialog.title = Zjišťování aktualizací
|
|||||||
welcome.askForUpdateCheck.dialog.header = Zjišťovat automaticky nové verze?
|
welcome.askForUpdateCheck.dialog.header = Zjišťovat automaticky nové verze?
|
||||||
welcome.askForUpdateCheck.dialog.content = Ke zjištění aktualizací, Cryptomator stáhne aktuální verzi z instalačních serverů a zobrazí nápovědu, když je k dispozici novější verze, než je nainstalovaná.\n\nDoporučujeme zapnout zjišťování aktualizací a zajistit tak, že vždy budete mít nejnovější verzi Cryptomator, se všemi opravami zabezpečení nainstalovanými. Pokud automatické zjišťování nezapnete, je možné ručně stahovat nejnovější verzi z https\://cryptomator.org/downloads/.\n\nToto nastavení je možné kdykoli změnit v nastavení aplikace.
|
welcome.askForUpdateCheck.dialog.content = Ke zjištění aktualizací, Cryptomator stáhne aktuální verzi z instalačních serverů a zobrazí nápovědu, když je k dispozici novější verze, než je nainstalovaná.\n\nDoporučujeme zapnout zjišťování aktualizací a zajistit tak, že vždy budete mít nejnovější verzi Cryptomator, se všemi opravami zabezpečení nainstalovanými. Pokud automatické zjišťování nezapnete, je možné ručně stahovat nejnovější verzi z https\://cryptomator.org/downloads/.\n\nToto nastavení je možné kdykoli změnit v nastavení aplikace.
|
||||||
settings.volume.dokany = Dokany
|
settings.volume.dokany = Dokany
|
||||||
unlock.errorMessage.invalidMountPath = Individuální přípojný bod není nastaven
|
|
||||||
main.gracefulShutdown.dialog.title = Zamčení trezorů se nezdařilo
|
main.gracefulShutdown.dialog.title = Zamčení trezorů se nezdařilo
|
||||||
main.gracefulShutdown.dialog.header = Trezory v používání
|
main.gracefulShutdown.dialog.header = Trezory v používání
|
||||||
main.gracefulShutdown.dialog.content = Jeden nebo více trezorů je stále v používání ostatními programy. Ukončete je aby se Cryptomator mohl správně vypnout a zkuste to znovu.\n\nPokud to nezabere, Cryptomator je možné vypnout vynuceně, ale to může vést ke ztrátě dat a není proto doporučeno.
|
main.gracefulShutdown.dialog.content = Jeden nebo více trezorů je stále v používání ostatními programy. Ukončete je aby se Cryptomator mohl správně vypnout a zkuste to znovu.\n\nPokud to nezabere, Cryptomator je možné vypnout vynuceně, ale to může vést ke ztrátě dat a není proto doporučeno.
|
||||||
main.gracefulShutdown.button.tryAgain = Zkusit znovu
|
main.gracefulShutdown.button.tryAgain = Zkusit znovu
|
||||||
main.gracefulShutdown.button.forceShutdown = Vynutit vypnutí
|
main.gracefulShutdown.button.forceShutdown = Vynutit vypnutí
|
||||||
unlock.pendingMessage.unlocking = Unlocking vault...
|
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…
|
||||||
|
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 = Tildel automatisk
|
|||||||
unlock.errorMessage.wrongPassword = Forkert adgangskode
|
unlock.errorMessage.wrongPassword = Forkert adgangskode
|
||||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Ikke-understøttet Vault. Denne Vault er blevet oprettet med en ældre version af Cryptomator.
|
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Ikke-understøttet Vault. Denne Vault er blevet oprettet med en ældre version af Cryptomator.
|
||||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Ikke-understøttet Vault. Denne Vault er blevet oprettet med en nyere version af Cryptomator.
|
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Ikke-understøttet Vault. Denne Vault er blevet oprettet med en nyere version af Cryptomator.
|
||||||
unlock.messageLabel.startServerFailed = Kunne ikke starte WebDAV server.
|
|
||||||
# change_password.fxml
|
# change_password.fxml
|
||||||
changePassword.label.oldPassword = Gammel adgangskode
|
changePassword.label.oldPassword = Gammel adgangskode
|
||||||
changePassword.label.newPassword = Ny adgangskode
|
changePassword.label.newPassword = Ny adgangskode
|
||||||
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Throughput (MiB/s)
|
|||||||
# settings.fxml
|
# settings.fxml
|
||||||
settings.version.label = Version %s
|
settings.version.label = Version %s
|
||||||
settings.checkForUpdates.label = Tjek for opdateringer
|
settings.checkForUpdates.label = Tjek for opdateringer
|
||||||
settings.requiresRestartLabel = * Cryptomator skal genstartes
|
|
||||||
# tray icon
|
# tray icon
|
||||||
tray.menu.open = Åbn
|
tray.menu.open = Åbn
|
||||||
tray.menu.quit = Afslut
|
tray.menu.quit = Afslut
|
||||||
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Migrering fejlede pga. en I/O fejl. Se logfilen for
|
|||||||
upgrade.confirmation.label = Ja, jeg har sikret mig at al synkronisering er gennemført.
|
upgrade.confirmation.label = Ja, jeg har sikret mig at al synkronisering er gennemført.
|
||||||
unlock.label.savePassword = Gem adgangskode
|
unlock.label.savePassword = Gem adgangskode
|
||||||
unlock.errorMessage.unauthenticVersionMac = Kunne ikke autentificere versions-MAC
|
unlock.errorMessage.unauthenticVersionMac = Kunne ikke autentificere versions-MAC
|
||||||
unlocked.label.mountFailed = Montering af drev fejlede
|
|
||||||
unlock.savePassword.delete.confirmation.title = Slet gemt adgangskode
|
unlock.savePassword.delete.confirmation.title = Slet gemt adgangskode
|
||||||
unlock.savePassword.delete.confirmation.header = Er du sikker på at du vil slette den til Vault'en gemte adgangskode?
|
unlock.savePassword.delete.confirmation.header = Er du sikker på at du vil slette den til Vault'en gemte adgangskode?
|
||||||
unlock.savePassword.delete.confirmation.content = Den til Vault'en gemte adgangskode vil blive slettet fra dit systems keychain med øjeblikkelig virkning. Hvis du vil gemme din adgangskode på ny, skal du låse din Vault op med indstillingen "Gem adgangskode" slået til.
|
unlock.savePassword.delete.confirmation.content = Den til Vault'en gemte adgangskode vil blive slettet fra dit systems keychain med øjeblikkelig virkning. Hvis du vil gemme din adgangskode på ny, skal du låse din Vault op med indstillingen "Gem adgangskode" slået til.
|
||||||
settings.debugMode.label = Debug Tilstand *
|
settings.debugMode.label = Debug Tilstand
|
||||||
upgrade.version3dropBundleExtension.title = Vault Version 3 Opgradering (Drop Bundle Extension)
|
upgrade.version3dropBundleExtension.title = Vault Version 3 Opgradering (Drop Bundle Extension)
|
||||||
upgrade.version3to4.title = Vault Version 3 til 4 Opgradering
|
upgrade.version3to4.title = Vault Version 3 til 4 Opgradering
|
||||||
upgrade.version4to5.title = Vault Version 4 til 5 Opgradering
|
upgrade.version4to5.title = Vault Version 4 til 5 Opgradering
|
||||||
@@ -96,8 +93,6 @@ upgrade.version5toX.msg = This vault needs to be migrated to a newer format.\nPl
|
|||||||
main.createVault.nonEmptyDir.title = Creating vault failed
|
main.createVault.nonEmptyDir.title = Creating vault failed
|
||||||
main.createVault.nonEmptyDir.header = Chosen directory is not empty
|
main.createVault.nonEmptyDir.header = Chosen directory is not empty
|
||||||
main.createVault.nonEmptyDir.content = The selected directory already contains files (possibly hidden). A vault can only be created in an empty directory.
|
main.createVault.nonEmptyDir.content = The selected directory already contains files (possibly hidden). A vault can only be created in an empty directory.
|
||||||
unlock.label.mountPath = Mount path
|
|
||||||
unlock.label.mountPathButton = Apply
|
|
||||||
settings.webdav.port.label = WebDAV Port
|
settings.webdav.port.label = WebDAV Port
|
||||||
settings.webdav.port.prompt = 0 \= Choose automatically
|
settings.webdav.port.prompt = 0 \= Choose automatically
|
||||||
settings.webdav.port.apply = Apply
|
settings.webdav.port.apply = Apply
|
||||||
@@ -107,16 +102,23 @@ settings.volume.webdav = WebDAV
|
|||||||
settings.volume.fuse = FUSE
|
settings.volume.fuse = FUSE
|
||||||
unlock.successLabel.vaultCreated = Vault was successfully created.
|
unlock.successLabel.vaultCreated = Vault was successfully created.
|
||||||
unlock.successLabel.passwordChanged = Password was successfully changed.
|
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
|
unlock.label.useOwnMountPath = Use Custom Mount Point
|
||||||
welcome.askForUpdateCheck.dialog.title = Update check
|
welcome.askForUpdateCheck.dialog.title = Update check
|
||||||
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
|
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
|
||||||
welcome.askForUpdateCheck.dialog.content = Recommended\: Enable the update check to always be sure you have the newest version of Cryptomator, with all security patches, installed.\n\nYou can change this from within the settings at any time.
|
welcome.askForUpdateCheck.dialog.content = Recommended\: Enable the update check to always be sure you have the newest version of Cryptomator, with all security patches, installed.\n\nYou can change this from within the settings at any time.
|
||||||
settings.volume.dokany = Dokany
|
settings.volume.dokany = Dokany
|
||||||
unlock.errorMessage.invalidMountPath = Use of custom mount path selected, but no path given.
|
|
||||||
main.gracefulShutdown.dialog.title = Locking vault(s) failed
|
main.gracefulShutdown.dialog.title = Locking vault(s) failed
|
||||||
main.gracefulShutdown.dialog.header = Vault(s) in use
|
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.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.tryAgain = Try Again
|
||||||
main.gracefulShutdown.button.forceShutdown = Force shutdown
|
main.gracefulShutdown.button.forceShutdown = Force Shutdown
|
||||||
unlock.pendingMessage.unlocking = Unlocking vault...
|
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…
|
||||||
|
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 = Automatisch ermitteln
|
|||||||
unlock.errorMessage.wrongPassword = Falsches Passwort
|
unlock.errorMessage.wrongPassword = Falsches Passwort
|
||||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Tresor nicht unterstützt. Der Tresor wurde mit einer älteren Version von Cryptomator erstellt.
|
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Tresor nicht unterstützt. Der Tresor wurde mit einer älteren Version von Cryptomator erstellt.
|
||||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Tresor nicht unterstützt. Der Tresor wurde mit einer neueren Version von Cryptomator erstellt.
|
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Tresor nicht unterstützt. Der Tresor wurde mit einer neueren Version von Cryptomator erstellt.
|
||||||
unlock.messageLabel.startServerFailed = Starten des WebDAV-Servers fehlgeschlagen.
|
|
||||||
# change_password.fxml
|
# change_password.fxml
|
||||||
changePassword.label.oldPassword = Altes Passwort
|
changePassword.label.oldPassword = Altes Passwort
|
||||||
changePassword.label.newPassword = Neues Passwort
|
changePassword.label.newPassword = Neues Passwort
|
||||||
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Durchsatz (MiB/s)
|
|||||||
# settings.fxml
|
# settings.fxml
|
||||||
settings.version.label = Version %s
|
settings.version.label = Version %s
|
||||||
settings.checkForUpdates.label = Auf Updates prüfen
|
settings.checkForUpdates.label = Auf Updates prüfen
|
||||||
settings.requiresRestartLabel = * benötigt Neustart von Cryptomator
|
|
||||||
# tray icon
|
# tray icon
|
||||||
tray.menu.open = Öffnen
|
tray.menu.open = Öffnen
|
||||||
tray.menu.quit = Beenden
|
tray.menu.quit = Beenden
|
||||||
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Migration aufgrund eines I/O-Fehlers fehlgeschlagen
|
|||||||
upgrade.confirmation.label = Ja, die Synchronisation ist abgeschlossen
|
upgrade.confirmation.label = Ja, die Synchronisation ist abgeschlossen
|
||||||
unlock.label.savePassword = Passwort speichern
|
unlock.label.savePassword = Passwort speichern
|
||||||
unlock.errorMessage.unauthenticVersionMac = Versions-MAC konnte nicht authentifiziert werden.
|
unlock.errorMessage.unauthenticVersionMac = Versions-MAC konnte nicht authentifiziert werden.
|
||||||
unlocked.label.mountFailed = Verbinden des Laufwerks fehlgeschlagen
|
|
||||||
unlock.savePassword.delete.confirmation.title = Gespeichertes Passwort löschen
|
unlock.savePassword.delete.confirmation.title = Gespeichertes Passwort löschen
|
||||||
unlock.savePassword.delete.confirmation.header = Möchten Sie das gespeicherte Passwort von diesem Tresor wirklich löschen?
|
unlock.savePassword.delete.confirmation.header = Möchten Sie das gespeicherte Passwort von diesem Tresor wirklich löschen?
|
||||||
unlock.savePassword.delete.confirmation.content = Das gespeicherte Passwort von diesem Tresor wird sofort aus Ihrem System-Schlüsselbund gelöscht. Falls Sie das Passwort erneut speichern möchten, müssen Sie den Tresor entsperren und dabei die "Passwort speichern"-Option aktiviert haben.
|
unlock.savePassword.delete.confirmation.content = Das gespeicherte Passwort von diesem Tresor wird sofort aus Ihrem System-Schlüsselbund gelöscht. Falls Sie das Passwort erneut speichern möchten, müssen Sie den Tresor entsperren und dabei die "Passwort speichern"-Option aktiviert haben.
|
||||||
settings.debugMode.label = Debug-Modus *
|
settings.debugMode.label = Debug-Modus
|
||||||
upgrade.version3dropBundleExtension.title = Upgrade Tresor-Version 3 (Entfall der Bundle-Extension)
|
upgrade.version3dropBundleExtension.title = Upgrade Tresor-Version 3 (Entfall der Bundle-Extension)
|
||||||
upgrade.version3to4.title = Upgrade Tresor-Version 3 zu 4
|
upgrade.version3to4.title = Upgrade Tresor-Version 3 zu 4
|
||||||
upgrade.version4to5.title = Upgrade Tresor-Version 4 zu 5
|
upgrade.version4to5.title = Upgrade Tresor-Version 4 zu 5
|
||||||
@@ -96,8 +93,6 @@ upgrade.version5toX.msg = Dieser Tresor muss auf ein neueres Format aktualisiert
|
|||||||
main.createVault.nonEmptyDir.title = Erstellung des Tresors fehlgeschlagen
|
main.createVault.nonEmptyDir.title = Erstellung des Tresors fehlgeschlagen
|
||||||
main.createVault.nonEmptyDir.header = Ausgewählter Ordner ist nicht leer
|
main.createVault.nonEmptyDir.header = Ausgewählter Ordner ist nicht leer
|
||||||
main.createVault.nonEmptyDir.content = Der ausgewählte Ordner enthält bereits Dateien (möglicherweise unsichtbar). Ein Tresor kann nur innerhalb eines leeren Ordners erstellt werden.
|
main.createVault.nonEmptyDir.content = Der ausgewählte Ordner enthält bereits Dateien (möglicherweise unsichtbar). Ein Tresor kann nur innerhalb eines leeren Ordners erstellt werden.
|
||||||
unlock.label.mountPath = Verzeichnis
|
|
||||||
unlock.label.mountPathButton = Anwenden
|
|
||||||
settings.webdav.port.label = WebDAV-Port
|
settings.webdav.port.label = WebDAV-Port
|
||||||
settings.webdav.port.prompt = 0 \= automatische Auswahl
|
settings.webdav.port.prompt = 0 \= automatische Auswahl
|
||||||
settings.webdav.port.apply = Anwenden
|
settings.webdav.port.apply = Anwenden
|
||||||
@@ -107,16 +102,23 @@ settings.volume.webdav = WebDAV
|
|||||||
settings.volume.fuse = FUSE
|
settings.volume.fuse = FUSE
|
||||||
unlock.successLabel.vaultCreated = Der Tresor wurde erfolgreich erstellt.
|
unlock.successLabel.vaultCreated = Der Tresor wurde erfolgreich erstellt.
|
||||||
unlock.successLabel.passwordChanged = Das Passwort wurde erfolgreich geändert.
|
unlock.successLabel.passwordChanged = Das Passwort wurde erfolgreich geändert.
|
||||||
unlock.successLabel.upgraded = Das Cryptomator Upgrade wurde erfolgreich abgeschlossen.
|
unlock.successLabel.upgraded = Der Tresor wurde erfolgreich aktualisiert.
|
||||||
unlock.label.useOwnMountPath = Eigenes Laufwerksverzeichnis nutzen
|
unlock.label.useOwnMountPath = Eigenes Laufwerksverzeichnis nutzen
|
||||||
welcome.askForUpdateCheck.dialog.title = Auf Updates prüfen
|
welcome.askForUpdateCheck.dialog.title = Auf Updates prüfen
|
||||||
welcome.askForUpdateCheck.dialog.header = Eingebaute Update-Prüfung aktivieren?
|
welcome.askForUpdateCheck.dialog.header = Eingebaute Update-Prüfung aktivieren?
|
||||||
welcome.askForUpdateCheck.dialog.content = Empfehlung\: Aktivieren Sie die Update-Prüfung, um sicherzustellen, dass Sie stets die neueste Cryptomator-Version mit allen Sicherheits-Patches verwenden.\n\nDiese Einstellung können Sie jederzeit im Einstellungs-Menü ändern.
|
welcome.askForUpdateCheck.dialog.content = Empfehlung\: Aktivieren Sie die Update-Prüfung, um sicherzustellen, dass Sie stets die neueste Cryptomator-Version mit allen Sicherheits-Patches verwenden.\n\nDiese Einstellung können Sie jederzeit im Einstellungs-Menü ändern.
|
||||||
settings.volume.dokany = Dokany
|
settings.volume.dokany = Dokany
|
||||||
unlock.errorMessage.invalidMountPath = Individueller Mount-Punkt ist nicht gesetzt.
|
|
||||||
main.gracefulShutdown.dialog.title = Sperren des Tresors gescheitert
|
main.gracefulShutdown.dialog.title = Sperren des Tresors gescheitert
|
||||||
main.gracefulShutdown.dialog.header = Tresor in Gebrauch
|
main.gracefulShutdown.dialog.header = Tresor in Gebrauch
|
||||||
main.gracefulShutdown.dialog.content = Ein oder mehrere Tresore werden noch von anderen Programmen genutzt. Bitte schliessen Sie die Programme um es Cryptomator zu ermöglichen richtig herunter zu fahren. Versuchen Sie es danach erneut.\n\nFalls dies nicht klappt, kann Cryptomator das Beenden erzwingen. Dies kann zu einem Datenverlust führen und ist nicht empfohlen.
|
main.gracefulShutdown.dialog.content = Ein oder mehrere Tresore werden noch von anderen Programmen genutzt. Bitte schliessen Sie die Programme um es Cryptomator zu ermöglichen richtig herunter zu fahren. Versuchen Sie es danach erneut.\n\nFalls dies nicht klappt, kann Cryptomator das Beenden erzwingen. Dies kann zu einem Datenverlust führen und ist nicht empfohlen.
|
||||||
main.gracefulShutdown.button.tryAgain = Versuchen Sie es erneut
|
main.gracefulShutdown.button.tryAgain = Versuchen Sie es erneut
|
||||||
main.gracefulShutdown.button.forceShutdown = Herunterfahren erzwingen
|
main.gracefulShutdown.button.forceShutdown = Herunterfahren erzwingen
|
||||||
unlock.pendingMessage.unlocking = Entsperre Tresor...
|
unlock.pendingMessage.unlocking = Entsperre Tresor...
|
||||||
|
unlock.failedDialog.title = Entsperren fehlgeschlagen
|
||||||
|
unlock.failedDialog.header = Entsperren fehlgeschlagen
|
||||||
|
unlock.failedDialog.content.mountPathNonExisting = Laufwerksverzeichnis existiert nicht.
|
||||||
|
unlock.failedDialog.content.mountPathNotEmpty = Laufwerksverzeichnis ist nicht leer.
|
||||||
|
unlock.label.useReadOnlyMode = Nur lesend
|
||||||
|
unlock.label.chooseMountPath = Leeren Ordner auswählen…
|
||||||
|
ctrl.secPasswordField.nonPrintableChars = Das Passwort enthält Steuerzeichen.\nEmpfehlung\: Entfernen Sie diese, um die Kompatibilität mit anderen Clients sicherzustellen.
|
||||||
|
ctrl.secPasswordField.capsLocked = Die Feststelltaste ist aktiviert.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user