mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-15 01:01:26 +00:00
Compare commits
316 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41492a951a | ||
|
|
cbbfec5531 | ||
|
|
d7a4dff080 | ||
|
|
8e56218fbb | ||
|
|
9c40386106 | ||
|
|
c865805f55 | ||
|
|
8e352301cd | ||
|
|
52db1e8bdd | ||
|
|
1947623be8 | ||
|
|
4f8602684d | ||
|
|
75f66e40bf | ||
|
|
73587437cb | ||
|
|
18f6e70bba | ||
|
|
e067b94aca | ||
|
|
b04d5669fb | ||
|
|
ad44af338d | ||
|
|
4f4ddbc3e0 | ||
|
|
4e9af1c713 | ||
|
|
9a94b6f95e | ||
|
|
371b826aa0 | ||
|
|
d3fb90bf6d | ||
|
|
d24c478faa | ||
|
|
48d0aa423c | ||
|
|
5695fa2916 | ||
|
|
bcea1a4b32 | ||
|
|
2f8d4568fc | ||
|
|
5b55318e52 | ||
|
|
459f2ea2c3 | ||
|
|
d26147a34a | ||
|
|
afb9d4f010 | ||
|
|
db3bf9723e | ||
|
|
3a5937b35f | ||
|
|
f5cddda7a8 | ||
|
|
e9fa1d6476 | ||
|
|
084c1881ba | ||
|
|
ce131e4653 | ||
|
|
a121c7d0fe | ||
|
|
603bf99b25 | ||
|
|
9076fe5b46 | ||
|
|
6848f1a38e | ||
|
|
f4103fc917 | ||
|
|
c838da9df4 | ||
|
|
78df4e24b3 | ||
|
|
c0e2d01297 | ||
|
|
313f8b36d2 | ||
|
|
343794c1fe | ||
|
|
813c01aaed | ||
|
|
510ea8a6f6 | ||
|
|
a22fbea467 | ||
|
|
f64144d1da | ||
|
|
c01dd225c9 | ||
|
|
18b408a83a | ||
|
|
efaf107b20 | ||
|
|
86cba5e8a7 | ||
|
|
f4cee6d161 | ||
|
|
28c40b2277 | ||
|
|
8853054ed4 | ||
|
|
3eb44b06af | ||
|
|
1554437884 | ||
|
|
268bae8879 | ||
|
|
b382e436e2 | ||
|
|
6ccd248675 | ||
|
|
c760e63995 | ||
|
|
959dee153e | ||
|
|
aa22f656e8 | ||
|
|
d24734e16f | ||
|
|
01522e8c97 | ||
|
|
14e056551d | ||
|
|
82a42ea183 | ||
|
|
bfde26be79 | ||
|
|
51e43a0567 | ||
|
|
9a375cb3f6 | ||
|
|
2b8f39a149 | ||
|
|
2720a999d1 | ||
|
|
f675bd5017 | ||
|
|
16be3e7a97 | ||
|
|
551d9d1af1 | ||
|
|
0acc8ddb0a | ||
|
|
f0e1bcb899 | ||
|
|
d331c30224 | ||
|
|
d0d39a1c72 | ||
|
|
8904df91b4 | ||
|
|
786365c095 | ||
|
|
eb8f5f523d | ||
|
|
9c22adc97d | ||
|
|
265212cc0f | ||
|
|
9c860cc17c | ||
|
|
7f63153bc1 | ||
|
|
9531d44ab5 | ||
|
|
253abd4930 | ||
|
|
022ba81f2f | ||
|
|
26160e9bbc | ||
|
|
0a32de2c92 | ||
|
|
60aeb6a04b | ||
|
|
0206fc0b8f | ||
|
|
06b3343a6d | ||
|
|
598bec3050 | ||
|
|
0e5d72c22a | ||
|
|
21f4fab9ed | ||
|
|
58e2ec4742 | ||
|
|
6b6867b826 | ||
|
|
92321a7569 | ||
|
|
7781572b6d | ||
|
|
b5efe39eb8 | ||
|
|
cae86cd71f | ||
|
|
667617b480 | ||
|
|
9329311491 | ||
|
|
22438d1eba | ||
|
|
d295084312 | ||
|
|
ab538cd215 | ||
|
|
f9cb438a0b | ||
|
|
354de225ed | ||
|
|
0b7f8d71ed | ||
|
|
9657a13912 | ||
|
|
c5a3b29554 | ||
|
|
ae957c2588 | ||
|
|
da84133732 | ||
|
|
10275eb80f | ||
|
|
29a9c7dffe | ||
|
|
d5b8996a39 | ||
|
|
f012977aac | ||
|
|
8680bd1dee | ||
|
|
528510eb53 | ||
|
|
e8d17cfb7b | ||
|
|
cdbcd20241 | ||
|
|
fef3ade5c6 | ||
|
|
194d7f0da9 | ||
|
|
8ad20c27da | ||
|
|
f891a9f1a7 | ||
|
|
2a335eb7af | ||
|
|
6578ac0121 | ||
|
|
83f9514a42 | ||
|
|
c92f1aa655 | ||
|
|
688993ba37 | ||
|
|
ddfc8d5bc3 | ||
|
|
c3141e3601 | ||
|
|
f49974180b | ||
|
|
eb6ade49f6 | ||
|
|
6553c04256 | ||
|
|
a8cb015a06 | ||
|
|
ee81dca71c | ||
|
|
ce06ddf312 | ||
|
|
fee2d4bc0c | ||
|
|
a2a990bd0c | ||
|
|
cedbe6523d | ||
|
|
ec64055d93 | ||
|
|
6f542ac8b8 | ||
|
|
8a3c101764 | ||
|
|
49a8689090 | ||
|
|
8f5c4997b1 | ||
|
|
900fdd7f6f | ||
|
|
4723416d45 | ||
|
|
8483f71f57 | ||
|
|
5dac594cc9 | ||
|
|
06f4e160d6 | ||
|
|
1d38ee2fcb | ||
|
|
921b70ebaa | ||
|
|
0f56d424da | ||
|
|
4cd43a2b1c | ||
|
|
1ef359e68b | ||
|
|
58ce1e45b8 | ||
|
|
bd854e7f7a | ||
|
|
3ecc0cf906 | ||
|
|
ea6925f905 | ||
|
|
f45b78d8c0 | ||
|
|
75ca8a91f8 | ||
|
|
16462d05c8 | ||
|
|
cbef54f05c | ||
|
|
f601ff4ce3 | ||
|
|
84fbf7392b | ||
|
|
bbf9379f56 | ||
|
|
6aa14fcfb1 | ||
|
|
710c7d0da7 | ||
|
|
841d4eb9c7 | ||
|
|
8c4d35d3db | ||
|
|
4110057fa1 | ||
|
|
9b1d6d2f2f | ||
|
|
1c45455cb2 | ||
|
|
411aca8695 | ||
|
|
3de69021df | ||
|
|
8418038736 | ||
|
|
3bf2b499a7 | ||
|
|
430da2b78d | ||
|
|
51047dcbce | ||
|
|
708bcaa630 | ||
|
|
724b20c826 | ||
|
|
69f8c46f15 | ||
|
|
b7f2fb0bdf | ||
|
|
222ffd8c53 | ||
|
|
00e0a0f79a | ||
|
|
d6bbc4383b | ||
|
|
b51a2602a9 | ||
|
|
c3fbddc671 | ||
|
|
ce262b691a | ||
|
|
65de6fef9b | ||
|
|
31d95ae5b0 | ||
|
|
429098733c | ||
|
|
597899d2bf | ||
|
|
b1c66b181d | ||
|
|
100b836979 | ||
|
|
817907c25a | ||
|
|
25195fffe2 | ||
|
|
0e784a6ffc | ||
|
|
351f96fa8b | ||
|
|
703fb4fb51 | ||
|
|
a975df6f8c | ||
|
|
fb90128b63 | ||
|
|
ea4c91fb37 | ||
|
|
7c1d6973ba | ||
|
|
4f2120b729 | ||
|
|
51f7d74e4a | ||
|
|
32a810fe1d | ||
|
|
570482521a | ||
|
|
3bd2a2f911 | ||
|
|
1ac6d086bd | ||
|
|
8374599183 | ||
|
|
0091c401df | ||
|
|
00dd67e345 | ||
|
|
158677bd54 | ||
|
|
a1b375316c | ||
|
|
dae2814b0f | ||
|
|
341a98848d | ||
|
|
902e66906a | ||
|
|
c59d3cd03f | ||
|
|
ed9f16e012 | ||
|
|
ed77a4aacd | ||
|
|
ff2fa70bd6 | ||
|
|
3519346df9 | ||
|
|
45f6caa7bd | ||
|
|
e2e7755af2 | ||
|
|
4ab7bfaa20 | ||
|
|
7e0ffb43a6 | ||
|
|
526c8328f6 | ||
|
|
81bad4eb25 | ||
|
|
dcaa6e81a3 | ||
|
|
e78155396d | ||
|
|
d3063c8117 | ||
|
|
cd1ed088c3 | ||
|
|
e831debb94 | ||
|
|
8d15a6612f | ||
|
|
9c034f3be6 | ||
|
|
60dd9349a8 | ||
|
|
330b1cbf7d | ||
|
|
9ffa38d54e | ||
|
|
c925727269 | ||
|
|
ac2dc06bd6 | ||
|
|
80b83adb53 | ||
|
|
ddc31ac2bb | ||
|
|
eb7c5d0b2b | ||
|
|
e5ce3cb62d | ||
|
|
7a69692999 | ||
|
|
d00d9d98dc | ||
|
|
1846c5c14c | ||
|
|
0d9db783f7 | ||
|
|
d3a6964d8f | ||
|
|
55e9cabf51 | ||
|
|
5180f2061a | ||
|
|
05154cf6aa | ||
|
|
f61e955945 | ||
|
|
d80605532f | ||
|
|
8d5fb14b50 | ||
|
|
570286c7df | ||
|
|
11e3ee8d90 | ||
|
|
0664c670f7 | ||
|
|
f36f9d412c | ||
|
|
bd734f1960 | ||
|
|
675a0b1a73 | ||
|
|
a2816277bf | ||
|
|
e37f1f914b | ||
|
|
adf7694308 | ||
|
|
c13449c6ad | ||
|
|
f72035210c | ||
|
|
89ef5238ea | ||
|
|
8198f66c1f | ||
|
|
eb5aa4ee44 | ||
|
|
adb09a0efe | ||
|
|
241eb8bed5 | ||
|
|
842a0d6ff3 | ||
|
|
3200917df2 | ||
|
|
12dcf0647d | ||
|
|
22859c9ffa | ||
|
|
29182156df | ||
|
|
357d63f398 | ||
|
|
e594bf208d | ||
|
|
2f0de3520a | ||
|
|
8def68eb02 | ||
|
|
aef33dc864 | ||
|
|
06d2f2d9e9 | ||
|
|
dad0ad76fb | ||
|
|
99fa8e7c8e | ||
|
|
ab0f175edf | ||
|
|
8cb9728565 | ||
|
|
d91d27f2a4 | ||
|
|
b2a6e038ae | ||
|
|
75f360903c | ||
|
|
79c3137b90 | ||
|
|
d2189d379c | ||
|
|
49aead7323 | ||
|
|
7bd610563f | ||
|
|
5c1a1ad162 | ||
|
|
86906d0049 | ||
|
|
93011dc754 | ||
|
|
b084b651af | ||
|
|
fecf9c0423 | ||
|
|
117fe78a4a | ||
|
|
153d43573a | ||
|
|
9145f5d2f8 | ||
|
|
835ea3b640 | ||
|
|
1e7eb23d1b | ||
|
|
cfe25d0bf5 | ||
|
|
b14939bd77 | ||
|
|
26e140ee22 | ||
|
|
1c5ecf8c01 | ||
|
|
9b528a05b5 | ||
|
|
3a7aa6d64f | ||
|
|
55820e47f9 |
7
.github/ISSUE_TEMPLATE/bug.md
vendored
7
.github/ISSUE_TEMPLATE/bug.md
vendored
@@ -5,6 +5,7 @@ labels: type:bug
|
||||
---
|
||||
|
||||
<!--
|
||||
⚠️⚠️⚠️ READ CAREFULLY ⚠️⚠️⚠️
|
||||
|
||||
Do you want to ask a QUESTION? Are you looking for SUPPORT?
|
||||
We're happy to help you via our support channels! Please read: https://github.com/cryptomator/cryptomator/blob/develop/SUPPORT.md
|
||||
@@ -13,7 +14,9 @@ By filing an issue, you are expected to comply with our code of conduct: https:/
|
||||
|
||||
Of course, we also expect you to search for existing similar issues first! ;) https://github.com/cryptomator/cryptomator/issues?q=
|
||||
|
||||
⚠️ IMPORTANT: If you don't stick to this template, the issue will get closed. To proof that you read this, please remove the X from the following line:
|
||||
-->
|
||||
<!-- oooXooo -->
|
||||
|
||||
### Description
|
||||
|
||||
@@ -23,7 +26,7 @@ Of course, we also expect you to search for existing similar issues first! ;) ht
|
||||
|
||||
* Operating system and version: [Windows/macOS/Linux + Version]
|
||||
* Cryptomator version: [Shown in the settings]
|
||||
* Drive: [Dokany/FUSE/WebDAV]
|
||||
* Volume type: [Dokany/FUSE/WebDAV, shown in the settings]
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
@@ -58,4 +61,4 @@ Log file location:
|
||||
- macOS: ~/Library/Logs/Cryptomator
|
||||
- Linux: ~/.local/share/Cryptomator/logs
|
||||
|
||||
-->
|
||||
-->
|
||||
|
||||
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -7,6 +7,7 @@ jobs:
|
||||
build:
|
||||
name: Build and Test
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
@@ -27,24 +28,27 @@ jobs:
|
||||
run: |
|
||||
curl -o ~/codacy-coverage-reporter.jar https://repo.maven.apache.org/maven2/com/codacy/codacy-coverage-reporter/7.1.0/codacy-coverage-reporter-7.1.0-assembly.jar
|
||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial
|
||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/keychain/target/site/jacoco/jacoco.xml --partial
|
||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
|
||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
|
||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
|
||||
env:
|
||||
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
||||
- name: Assemble Buildkit
|
||||
run: mvn -B package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease
|
||||
- name: Assemble buildkit-linux.zip
|
||||
run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,linux
|
||||
- name: Upload buildkit-linux.zip
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: buildkit-linux.zip
|
||||
path: main/buildkit/target/buildkit-linux.zip
|
||||
- name: Assemble buildkit-mac.zip
|
||||
run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,mac
|
||||
- name: Upload buildkit-mac.zip
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: buildkit-mac.zip
|
||||
path: main/buildkit/target/buildkit-mac.zip
|
||||
- name: Assemble buildkit-win.zip
|
||||
run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,windows
|
||||
- name: Upload buildkit-win.zip
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
|
||||
25
.github/workflows/triageBugs.yml
vendored
Normal file
25
.github/workflows/triageBugs.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Bug Report Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
closeTemplateViolation:
|
||||
name: Close bug reports that violate the issue template
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- if: |
|
||||
contains(github.event.issue.labels.*.name, 'type:bug')
|
||||
&& (
|
||||
!contains(github.event.issue.body, '<!-- oooooo -->')
|
||||
|| !contains(github.event.issue.body, '### Description')
|
||||
)
|
||||
name: Close Issue
|
||||
uses: peter-evans/close-issue@v1
|
||||
with:
|
||||
comment: |
|
||||
This bug report did ignore our issue template. 😞
|
||||
Auto-closing this issue, since it is most likely not useful.
|
||||
|
||||
_This decision was made by a bot. If you think the bot is wrong, let us know and we'll reopen this issue._
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -18,5 +18,8 @@ pom.xml.versionsBackup
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
.idea/compiler.xml
|
||||
.idea/encodings.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/**/libraries/
|
||||
*.iml
|
||||
29
.idea/codeStyles/Project.xml
generated
29
.idea/codeStyles/Project.xml
generated
@@ -9,14 +9,36 @@
|
||||
<option name="RIGHT_MARGIN" value="220" />
|
||||
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="30" />
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="10" />
|
||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||
<value />
|
||||
</option>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<package name="javafx" withSubpackages="true" static="false" />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
|
||||
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
|
||||
</JavaCodeStyleSettings>
|
||||
<Properties>
|
||||
<option name="KEEP_BLANK_LINES" value="true" />
|
||||
</Properties>
|
||||
<XML>
|
||||
<option name="XML_ATTRIBUTE_WRAP" value="0" />
|
||||
</XML>
|
||||
<codeStyleSettings language="CSS">
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Groovy">
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
@@ -30,12 +52,17 @@
|
||||
<codeStyleSettings language="JAVA">
|
||||
<option name="KEEP_LINE_BREAKS" value="false" />
|
||||
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
|
||||
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||
<option name="ENUM_CONSTANTS_WRAP" value="2" />
|
||||
<indentOptions>
|
||||
<option name="USE_TAB_CHARACTER" value="true" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules />
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JSON">
|
||||
<indentOptions>
|
||||
|
||||
41
.idea/compiler.xml
generated
41
.idea/compiler.xml
generated
@@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile name="Annotation profile for Cryptomator" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.27/dagger-compiler-2.27.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.27/dagger-2.27.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.27/dagger-producers-2.27.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/27.1-jre/guava-27.1-jre.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.1/jsr305-3.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/2.5.2/checker-qual-2.5.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.2.0/error_prone_annotations-2.2.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.3/checker-compat-qual-2.5.3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.27/dagger-spi-2.27.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.11.1/javapoet-1.11.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.3.50/kotlin-stdlib-1.3.50.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.3.50/kotlin-stdlib-common-1.3.50.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.1.0/kotlinx-metadata-jvm-0.1.0.jar" />
|
||||
</processorPath>
|
||||
<module name="keychain" />
|
||||
<module name="launcher" />
|
||||
<module name="commons" />
|
||||
<module name="ui" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
</project>
|
||||
13
.idea/encodings.xml
generated
13
.idea/encodings.xml
generated
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
||||
<file url="file://$PROJECT_DIR$/main" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/commons/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/keychain/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/keychain/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/launcher/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/ui/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/ui/src/main/resources" charset="UTF-8" />
|
||||
<file url="PROJECT" charset="UTF-8" />
|
||||
</component>
|
||||
</project>
|
||||
30
.idea/jarRepositories.xml
generated
30
.idea/jarRepositories.xml
generated
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="bintray" />
|
||||
<option name="name" value="Bintray JCenter" />
|
||||
<option name="url" value="https://jcenter.bintray.com" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jcenter" />
|
||||
<option name="name" value="jcenter" />
|
||||
<option name="url" value="https://jcenter.bintray.com" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/runConfigurations/Cryptomator_Linux_Dev.xml
generated
Normal file
10
.idea/runConfigurations/Cryptomator_Linux_Dev.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
|
||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||
<module name="launcher" />
|
||||
<option name="VM_PARAMETERS" value="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath="~/.config/Cryptomator-Dev/settings.json" -Dcryptomator.ipcPortPath="~/.config/Cryptomator-Dev/ipcPort.bin" -Dcryptomator.logDir="~/.local/share/Cryptomator-Dev/logs" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator-Dev/mnt" -Dfuse.experimental="true" -Xss20m -Xmx512m" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
2
.idea/runConfigurations/Cryptomator_Windows.xml
generated
2
.idea/runConfigurations/Cryptomator_Windows.xml
generated
@@ -2,7 +2,7 @@
|
||||
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
|
||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||
<module name="launcher" />
|
||||
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" -Xss2m -Xmx512m" />
|
||||
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" -Dcryptomator.mountPointsDir="~/Cryptomator" -Xss2m -Xmx512m" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
|
||||
10
.idea/runConfigurations/Cryptomator_Windows_Dev.xml
generated
Normal file
10
.idea/runConfigurations/Cryptomator_Windows_Dev.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
|
||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||
<module name="launcher" />
|
||||
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator-Dev/settings.json" -Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator-Dev/ipcPort.bin" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator-Dev" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator-Dev/keychain.json" -Dcryptomator.mountPointsDir="~/Cryptomator-Dev" -Dfuse.experimental="true" -Xss2m -Xmx512m" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
13
.idea/runConfigurations/Cryptomator_macOS_Dev.xml
generated
Normal file
13
.idea/runConfigurations/Cryptomator_macOS_Dev.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Cryptomator macOS Dev" type="Application" factoryName="Application">
|
||||
<envs>
|
||||
<env name="LD_LIBRARY_PATH" value="/usr/local/lib" />
|
||||
</envs>
|
||||
<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-Dev/settings.json" -Dcryptomator.ipcPortPath="~/Library/Application Support/Cryptomator-Dev/ipcPort.bin" -Dcryptomator.logDir="~/Library/Logs/Cryptomator-Dev" -Dcryptomator.mountPointsDir="/Volumes/" -Dfuse.experimental="true" -Xss2m -Xmx512m -ea" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -73,7 +73,9 @@ For more information on the security details visit [cryptomator.org](https://doc
|
||||
|
||||
```
|
||||
cd main
|
||||
mvn clean install -Prelease
|
||||
mvn clean install -Prelease,windows
|
||||
# or mvn clean install -Prelease,mac
|
||||
# or mvn clean install -Prelease,linux
|
||||
```
|
||||
|
||||
This will build all the jars and bundle them together with their OS-specific dependencies under `main/buildkit/target`. This can now be used to build native packages.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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"
|
||||
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
|
||||
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>
|
||||
@@ -14,13 +14,6 @@
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
<include>ffi-version.txt</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
@@ -43,12 +36,5 @@
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/linux-libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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"
|
||||
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
|
||||
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>
|
||||
@@ -14,13 +14,6 @@
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
<include>ffi-version.txt</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
@@ -43,12 +36,5 @@
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/mac-libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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"
|
||||
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
|
||||
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>
|
||||
@@ -14,13 +14,6 @@
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
<include>ffi-version.txt</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
@@ -43,12 +36,5 @@
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/win-libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
@@ -1,10 +1,10 @@
|
||||
<?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">
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" 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.2</version>
|
||||
<version>1.5.9</version>
|
||||
</parent>
|
||||
<artifactId>buildkit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
@@ -24,7 +24,6 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
@@ -55,8 +54,8 @@
|
||||
|
||||
<!-- copy libraries to target/libs/: -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-libs</id>
|
||||
@@ -65,110 +64,153 @@
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includeScope>runtime</includeScope>
|
||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||
<excludeClassifiers>linux,mac,win</excludeClassifiers>
|
||||
<excludeArtifactIds>dbus-java,secret-service,hkdf,java-utils</excludeArtifactIds>
|
||||
</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-linux-secret-service</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/linux-libs</outputDirectory>
|
||||
<includeArtifactIds>dbus-java,secret-service,hkdf,java-utils</includeArtifactIds>
|
||||
</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>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>linux</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<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>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-linux-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||
<includeGroupIds>org.openjfx</includeGroupIds>
|
||||
<classifier>linux</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>mac</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<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>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-mac-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||
<includeGroupIds>org.openjfx</includeGroupIds>
|
||||
<classifier>mac</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>windows</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<executions>
|
||||
<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>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-win-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||
<includeGroupIds>org.openjfx</includeGroupIds>
|
||||
<classifier>win</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -1 +0,0 @@
|
||||
${cryptomator.jni.version}
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
cd $(dirname $0)
|
||||
java \
|
||||
-cp "libs/*" \
|
||||
-Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" \
|
||||
@@ -6,6 +7,6 @@ java \
|
||||
-Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \
|
||||
-Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \
|
||||
-Djdk.gtk.version=2 \
|
||||
-Xss20m \
|
||||
-Xss2m \
|
||||
-Xmx512m \
|
||||
org.cryptomator.launcher.Cryptomator
|
||||
org.cryptomator.launcher.Cryptomator
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
cd $(dirname $0)
|
||||
java \
|
||||
-cp "libs/*" \
|
||||
-Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" \
|
||||
|
||||
@@ -4,6 +4,7 @@ java ^
|
||||
-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" ^
|
||||
-Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" ^
|
||||
-Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" ^
|
||||
-Dcryptomator.mountPointsDir="~/Cryptomator" ^
|
||||
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
|
||||
-Xss20m ^
|
||||
-Xmx512m ^
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?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">
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" 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.2</version>
|
||||
<version>1.5.9</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator Commons</name>
|
||||
@@ -29,7 +29,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>jni</artifactId>
|
||||
<artifactId>integrations-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JavaFx -->
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
<groupId>com.tobiasdiez</groupId>
|
||||
<artifactId>easybind</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -5,24 +5,25 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common;
|
||||
|
||||
import com.tobiasdiez.easybind.EasyBind;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.keychain.KeychainModule;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.SettingsProvider;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultComponent;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ObservableList;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -33,7 +34,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Module(subcomponents = {VaultComponent.class})
|
||||
@Module(subcomponents = {VaultComponent.class}, includes = {KeychainModule.class})
|
||||
public abstract class CommonsModule {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
|
||||
|
||||
@@ -40,6 +40,7 @@ public class Environment {
|
||||
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
||||
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
|
||||
LOG.debug("cryptomator.buildNumber: {}", System.getProperty("cryptomator.buildNumber"));
|
||||
LOG.debug("fuse.experimental: {}", Boolean.getBoolean("fuse.experimental"));
|
||||
}
|
||||
|
||||
public boolean useCustomLogbackConfig() {
|
||||
@@ -74,6 +75,10 @@ public class Environment {
|
||||
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
|
||||
}
|
||||
|
||||
public boolean useExperimentalFuse() {
|
||||
return Boolean.getBoolean("fuse.experimental");
|
||||
}
|
||||
|
||||
private int getInt(String propertyName, int defaultValue) {
|
||||
String value = System.getProperty(propertyName);
|
||||
try {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019 Skymatic GmbH.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.jni.JniFunctions;
|
||||
import org.cryptomator.jni.MacFunctions;
|
||||
import org.cryptomator.jni.WinFunctions;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
@Module
|
||||
public class JniModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Optional<MacFunctions> provideOptionalMacFunctions() {
|
||||
return JniFunctions.macFunctions();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Optional<WinFunctions> provideOptionalWinFunctions() {
|
||||
return JniFunctions.winFunctions();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,12 +5,12 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
|
||||
public final class LazyInitializer {
|
||||
|
||||
private LazyInitializer() {
|
||||
@@ -18,7 +18,7 @@ public final class LazyInitializer {
|
||||
|
||||
/**
|
||||
* Same as {@link #initializeLazily(AtomicReference, SupplierThrowingException, Class)} except that no checked exception may be thrown by the factory function.
|
||||
*
|
||||
*
|
||||
* @param <T> Type of the value
|
||||
* @param reference A reference to a maybe not yet initialized value.
|
||||
* @param factory A factory providing a value for the reference, if it doesn't exist yet. The factory may be invoked multiple times, but only one result will survive.
|
||||
@@ -31,7 +31,7 @@ public final class LazyInitializer {
|
||||
|
||||
/**
|
||||
* Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509
|
||||
*
|
||||
*
|
||||
* @param <T> Type of the value
|
||||
* @param <E> Type of the any expected exception that may occur during initialization
|
||||
* @param reference A reference to a maybe not yet initialized value.
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Compares version strings according to <a href="http://semver.org/spec/v2.0.0.html">SemVer 2.0.0</a>.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package org.cryptomator.common.keychain;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.ObjectExpression;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Singleton
|
||||
public class KeychainManager implements KeychainAccessProvider {
|
||||
|
||||
private final ObjectExpression<KeychainAccessProvider> keychain;
|
||||
private final LoadingCache<String, BooleanProperty> passphraseStoredProperties;
|
||||
|
||||
@Inject
|
||||
KeychainManager(ObjectExpression<KeychainAccessProvider> selectedKeychain) {
|
||||
this.keychain = selectedKeychain;
|
||||
this.passphraseStoredProperties = CacheBuilder.newBuilder() //
|
||||
.weakValues() //
|
||||
.build(CacheLoader.from(this::createStoredPassphraseProperty));
|
||||
keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll());
|
||||
}
|
||||
|
||||
private KeychainAccessProvider getKeychainOrFail() throws KeychainAccessException {
|
||||
var result = keychain.getValue();
|
||||
if (result == null) {
|
||||
throw new NoKeychainAccessProviderException();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||
getKeychainOrFail().storePassphrase(key, passphrase);
|
||||
setPassphraseStored(key, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] loadPassphrase(String key) throws KeychainAccessException {
|
||||
char[] passphrase = getKeychainOrFail().loadPassphrase(key);
|
||||
setPassphraseStored(key, passphrase != null);
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePassphrase(String key) throws KeychainAccessException {
|
||||
getKeychainOrFail().deletePassphrase(key);
|
||||
setPassphraseStored(key, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||
getKeychainOrFail().changePassphrase(key, passphrase);
|
||||
setPassphraseStored(key, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return keychain.getValue() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the keychain knows a passphrase for the given key.
|
||||
* <p>
|
||||
* Expensive operation. If possible, use {@link #getPassphraseStoredProperty(String)} instead.
|
||||
*
|
||||
* @param key The key to look up
|
||||
* @return <code>true</code> if a password for <code>key</code> is stored.
|
||||
* @throws KeychainAccessException
|
||||
*/
|
||||
public boolean isPassphraseStored(String key) throws KeychainAccessException {
|
||||
char[] storedPw = null;
|
||||
try {
|
||||
storedPw = getKeychainOrFail().loadPassphrase(key);
|
||||
return storedPw != null;
|
||||
} finally {
|
||||
if (storedPw != null) {
|
||||
Arrays.fill(storedPw, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setPassphraseStored(String key, boolean value) {
|
||||
BooleanProperty property = passphraseStoredProperties.getIfPresent(key);
|
||||
if (property != null) {
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
property.set(value);
|
||||
} else {
|
||||
Platform.runLater(() -> property.set(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable property for use in the UI that tells whether a passphrase is stored for the given key.
|
||||
* <p>
|
||||
* Assuming that this process is the only process modifying Cryptomator-related items in the system keychain, this
|
||||
* property stays in memory in an attempt to avoid unnecessary calls to the system keychain. Note that due to this
|
||||
* fact the value stored in the returned property is not 100% reliable. Code defensively!
|
||||
*
|
||||
* @param key The key to look up
|
||||
* @return An observable property which is <code>true</code> when it almost certain that a password for <code>key</code> is stored.
|
||||
* @see #isPassphraseStored(String)
|
||||
*/
|
||||
public ReadOnlyBooleanProperty getPassphraseStoredProperty(String key) {
|
||||
return passphraseStoredProperties.getUnchecked(key);
|
||||
}
|
||||
|
||||
private BooleanProperty createStoredPassphraseProperty(String key) {
|
||||
try {
|
||||
return new SimpleBooleanProperty(isPassphraseStored(key));
|
||||
} catch (KeychainAccessException e) {
|
||||
return new SimpleBooleanProperty(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.cryptomator.common.keychain;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectExpression;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Module
|
||||
public class KeychainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Set<ServiceLoader.Provider<KeychainAccessProvider>> provideAvailableKeychainAccessProviderFactories() {
|
||||
return ServiceLoader.load(KeychainAccessProvider.class).stream().collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Set<KeychainAccessProvider> provideSupportedKeychainAccessProviders(Set<ServiceLoader.Provider<KeychainAccessProvider>> availableFactories) {
|
||||
return availableFactories.stream() //
|
||||
.map(ServiceLoader.Provider::get) //
|
||||
.filter(KeychainAccessProvider::isSupported) //
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static ObjectExpression<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, Set<KeychainAccessProvider> providers) {
|
||||
return Bindings.createObjectBinding(() -> {
|
||||
var selectedProviderClass = settings.keychainBackend().get().getProviderClass();
|
||||
var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny();
|
||||
var fallbackProvider = providers.stream().findAny().orElse(null);
|
||||
return selectedProvider.orElse(fallbackProvider);
|
||||
}, settings.keychainBackend());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.cryptomator.common.keychain;
|
||||
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||
|
||||
/**
|
||||
* Thrown by {@link KeychainManager} if attempted to access a keychain despite no supported keychain access provider being available.
|
||||
*/
|
||||
public class NoKeychainAccessProviderException extends KeychainAccessException {
|
||||
|
||||
public NoKeychainAccessProviderException() {
|
||||
super("Did not find any supported keychain access provider.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.cryptomator.common.mountpoint;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
import org.cryptomator.common.vaults.WindowsDriveLetters;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public class AvailableDriveLetterChooser implements MountPointChooser {
|
||||
|
||||
public static final int PRIORITY = 200;
|
||||
|
||||
private final WindowsDriveLetters windowsDriveLetters;
|
||||
|
||||
@Inject
|
||||
public AvailableDriveLetterChooser(WindowsDriveLetters windowsDriveLetters) {
|
||||
this.windowsDriveLetters = windowsDriveLetters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(Volume caller) {
|
||||
return SystemUtils.IS_OS_WINDOWS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||
return this.windowsDriveLetters.getAvailableDriveLetterPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return PRIORITY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.cryptomator.common.mountpoint;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CustomDriveLetterChooser implements MountPointChooser {
|
||||
|
||||
public static final int PRIORITY = 100;
|
||||
|
||||
private final VaultSettings vaultSettings;
|
||||
|
||||
@Inject
|
||||
public CustomDriveLetterChooser(VaultSettings vaultSettings) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(Volume caller) {
|
||||
return SystemUtils.IS_OS_WINDOWS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||
return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return PRIORITY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.cryptomator.common.mountpoint;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
import org.slf4j.Logger;
|
||||
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.LinkOption;
|
||||
import java.nio.file.NotDirectoryException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CustomMountPointChooser implements MountPointChooser {
|
||||
|
||||
public static final int PRIORITY = 0;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class);
|
||||
|
||||
private final VaultSettings vaultSettings;
|
||||
private final Environment environment;
|
||||
|
||||
@Inject
|
||||
public CustomMountPointChooser(VaultSettings vaultSettings, Environment environment) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(Volume caller) {
|
||||
//Disable if useExperimentalFuse is required (Win + Fuse), but set to false
|
||||
return caller.getImplementationType() != VolumeImpl.FUSE || !SystemUtils.IS_OS_WINDOWS || environment.useExperimentalFuse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||
//VaultSettings#getCustomMountPath already checks whether the saved custom mountpoint should be used
|
||||
return this.vaultSettings.getCustomMountPath().map(Paths::get);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
||||
switch (caller.getMountPointRequirement()) {
|
||||
case PARENT_NO_MOUNT_POINT -> prepareParentNoMountPoint(mountPoint);
|
||||
case EMPTY_MOUNT_POINT -> prepareEmptyMountPoint(mountPoint);
|
||||
case NONE -> {
|
||||
//Requirement "NONE" doesn't make any sense here.
|
||||
//No need to prepare/verify a Mountpoint without requiring one...
|
||||
throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
|
||||
}
|
||||
default -> {
|
||||
//Currently the case for "PARENT_OPT_MOUNT_POINT"
|
||||
throw new InvalidMountPointException(new IllegalStateException("Not implemented"));
|
||||
}
|
||||
}
|
||||
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException {
|
||||
//This the case on Windows when using FUSE
|
||||
//See https://github.com/billziss-gh/winfsp/issues/320
|
||||
Path parent = mountPoint.getParent();
|
||||
if (!Files.isDirectory(parent)) {
|
||||
throw new InvalidMountPointException(new NotDirectoryException(parent.toString()));
|
||||
}
|
||||
//We must use #notExists() here because notExists =/= !exists (see docs)
|
||||
if (!Files.notExists(mountPoint, LinkOption.NOFOLLOW_LINKS)) {
|
||||
//File exists OR can't be determined
|
||||
throw new InvalidMountPointException(new FileAlreadyExistsException(mountPoint.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareEmptyMountPoint(Path mountPoint) throws InvalidMountPointException {
|
||||
//This is the case for Windows when using Dokany and for Linux and Mac
|
||||
if (!Files.isDirectory(mountPoint)) {
|
||||
throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString()));
|
||||
}
|
||||
try (DirectoryStream<Path> ds = Files.newDirectoryStream(mountPoint)) {
|
||||
if (ds.iterator().hasNext()) {
|
||||
throw new InvalidMountPointException(new DirectoryNotEmptyException(mountPoint.toString()));
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
throw new InvalidMountPointException("IOException while checking folder content", exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return PRIORITY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.cryptomator.common.mountpoint;
|
||||
|
||||
public class InvalidMountPointException extends Exception {
|
||||
|
||||
public InvalidMountPointException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InvalidMountPointException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public InvalidMountPointException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.cryptomator.common.mountpoint;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryNotEmptyException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
|
||||
public class IrregularUnmountCleaner {
|
||||
|
||||
public static Logger LOG = LoggerFactory.getLogger(IrregularUnmountCleaner.class);
|
||||
|
||||
public static void removeIrregularUnmountDebris(Path dirContainingMountPoints) {
|
||||
IOException cleanupFailed = new IOException("Cleanup failed");
|
||||
|
||||
try {
|
||||
LOG.debug("Performing cleanup of mountpoint dir {}.", dirContainingMountPoints);
|
||||
for (Path p : Files.newDirectoryStream(dirContainingMountPoints)) {
|
||||
try {
|
||||
var attr = Files.readAttributes(p, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
|
||||
if (attr.isOther() && attr.isDirectory()) { // yes, this is possible with windows junction points -.-
|
||||
Files.delete(p);
|
||||
} else if (attr.isDirectory()) {
|
||||
deleteEmptyDir(p);
|
||||
} else if (attr.isSymbolicLink()) {
|
||||
deleteDeadLink(p);
|
||||
} else {
|
||||
LOG.debug("Found non-directory element in mountpoint dir: {}", p);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
cleanupFailed.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanupFailed.getSuppressed().length > 0) {
|
||||
throw cleanupFailed;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unable to perform cleanup of mountpoint dir {}.", dirContainingMountPoints, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void deleteEmptyDir(Path dir) throws IOException {
|
||||
assert Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS);
|
||||
try {
|
||||
Files.delete(dir); // attempt to delete dir non-recursively (will fail, if there are contents)
|
||||
} catch (DirectoryNotEmptyException e) {
|
||||
LOG.info("Found non-empty directory in mountpoint dir: {}", dir);
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteDeadLink(Path symlink) throws IOException {
|
||||
assert Files.isSymbolicLink(symlink);
|
||||
if (Files.notExists(symlink)) { // following link: target does not exist
|
||||
Files.delete(symlink);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package org.cryptomator.common.mountpoint;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.SortedSet;
|
||||
|
||||
/**
|
||||
* Base interface for the Mountpoint-Choosing-Operation that results in the choice and
|
||||
* preparation of a mountpoint or an exception otherwise.<br>
|
||||
* <p>All <i>MountPointChoosers (MPCs)</i> need to implement this class and must be added to
|
||||
* the pool of possible MPCs by the {@link MountPointChooserModule MountPointChooserModule.}
|
||||
* The MountPointChooserModule will sort them according to their {@link #getPriority() priority.}
|
||||
* The priority must be defined by the developer to reflect a useful execution order.<br>
|
||||
* A specific priority <b>must not</b> be assigned to more than one MPC at a time;
|
||||
* the result of having two MPCs with equal priority is undefined.
|
||||
*
|
||||
* <p>MPCs are executed by a {@link Volume} in ascending order of their priority
|
||||
* (smaller priorities are tried first) to find and prepare a suitable mountpoint for the volume.
|
||||
* The volume has access to a {@link SortedSet} of MPCs in this specific order,
|
||||
* that is provided by the Module. The Set contains all available Choosers, even if they
|
||||
* are not {@link #isApplicable(Volume) applicable} for the Vault/Volume. The Volume must
|
||||
* check whether a MPC is applicable by invoking {@code #isApplicable(Volume)} on it
|
||||
* <i>before</i> calling {@code #chooseMountPoint(Volume)}.
|
||||
*
|
||||
* <p>At execution of a MPC {@link #chooseMountPoint(Volume)} is called to choose a mountpoint
|
||||
* according to the MPC's <i>strategy.</i> The <i>strategy</i> can involve reading configs,
|
||||
* searching the filesystem, processing user-input or similar operations.
|
||||
* If {@code #chooseMountPoint(Volume)} returns a non-null path (everything but
|
||||
* {@linkplain Optional#empty()}) the MPC's {@link #prepare(Volume, Path)} method is called and the
|
||||
* MountPoint is verified and/or prepared. In this case <i>no other MPC's will be called for
|
||||
* this volume, even if {@code #prepare(Volume, Path)} fails.</i>
|
||||
*
|
||||
* <p>If {@code #chooseMountPoint(Volume)} yields no result, the next MPC is executed
|
||||
* <i>without</i> first calling the {@code #prepare(Volume, Path)} method of the current MPC.
|
||||
* This is repeated until<br>
|
||||
* <ul>
|
||||
* <li><b>either</b> a mountpoint is returned by {@code #chooseMountPoint(Volume)}
|
||||
* and {@code #prepare(Volume, Path)} succeeds or fails, ending the entire operation</li>
|
||||
* <li><b>or</b> no MPC remains and an {@link InvalidMountPointException} is thrown.</li>
|
||||
* </ul>
|
||||
* If the {@code #prepare(Volume, Path)} method of a MPC fails, the entire
|
||||
* Mountpoint-Choosing-Operation is aborted and the method should do all necessary cleanup
|
||||
* before throwing the exception.
|
||||
* If the preparation succeeds {@link #cleanup(Volume, Path)} can be used after unmount to do any
|
||||
* remaining cleanup.
|
||||
*/
|
||||
public interface MountPointChooser extends Comparable<MountPointChooser> {
|
||||
|
||||
/**
|
||||
* Called by the {@link Volume} to determine whether this MountPointChooser is
|
||||
* applicable for mounting the Vault/Volume, especially with regard to the
|
||||
* current system configuration and particularities of the Volume type.
|
||||
*
|
||||
* <p>Developers should override this method to check for system configurations
|
||||
* that are unsuitable for this MPC.
|
||||
*
|
||||
* @param caller The Volume that is calling the method to determine applicability of the MPC
|
||||
* @return a boolean flag; true if applicable, else false.
|
||||
* @see #chooseMountPoint(Volume)
|
||||
*/
|
||||
boolean isApplicable(Volume caller);
|
||||
|
||||
/**
|
||||
* Called by a {@link Volume} to choose a mountpoint according to the
|
||||
* MountPointChoosers strategy.
|
||||
*
|
||||
* <p>This method must only be called for MPCs that were deemed
|
||||
* {@link #isApplicable(Volume) applicable} by the {@link Volume Volume.}
|
||||
* Developers should override this method to find or extract a mountpoint for
|
||||
* the volume <b>without</b> preparing it. Preparation should be done by
|
||||
* {@link #prepare(Volume, Path)} instead.
|
||||
* Exceptions in this method should be handled gracefully and result in returning
|
||||
* {@link Optional#empty()} instead of throwing an exception.
|
||||
*
|
||||
* @param caller The Volume that is calling the method to choose a mountpoint
|
||||
* @return the chosen path or {@link Optional#empty()} if an exception occurred
|
||||
* or no mountpoint could be found.
|
||||
* @see #isApplicable(Volume)
|
||||
* @see #prepare(Volume, Path)
|
||||
*/
|
||||
Optional<Path> chooseMountPoint(Volume caller);
|
||||
|
||||
/**
|
||||
* Called by a {@link Volume} to prepare and/or verify the chosen mountpoint.<br>
|
||||
* This method is only called if the {@link #chooseMountPoint(Volume)} method
|
||||
* of the same MountPointChooser returned a path.
|
||||
*
|
||||
* <p>Developers should override this method to prepare the mountpoint for
|
||||
* the volume and check for any obstacles that could hinder the mount operation.
|
||||
* The mountpoint is deemed "prepared" if it can be used to mount a volume
|
||||
* without any further filesystem actions or user interaction. If this is not possible,
|
||||
* this method should fail. In other words: This method should not return without
|
||||
* either failing or finalizing the preparation of the mountpoint.
|
||||
* Generally speaking exceptions should be wrapped as
|
||||
* {@link InvalidMountPointException} to allow efficient handling by the caller.
|
||||
*
|
||||
* <p>Often the preparation of a mountpoint involves creating files or others
|
||||
* actions that require cleaning up after the volume is unmounted.
|
||||
* In this case developers should override the {@link #cleanup(Volume, Path)}
|
||||
* method and return {@code true} to the volume to indicate that the
|
||||
* {@code #cleanup} method of this MPC should be called after unmount.
|
||||
*
|
||||
* <p><b>Please note:</b> If this method fails the entire
|
||||
* Mountpoint-Choosing-Operation is aborted without calling
|
||||
* {@link #cleanup(Volume, Path)} or any other MPCs. Therefore this method should
|
||||
* do all necessary cleanup before throwing the exception.
|
||||
*
|
||||
* @param caller The Volume that is calling the method to prepare a mountpoint
|
||||
* @param mountPoint the mountpoint chosen by {@link #chooseMountPoint(Volume)}
|
||||
* @return a boolean flag; true if cleanup is needed, false otherwise
|
||||
* @throws InvalidMountPointException if the preparation fails
|
||||
* @see #chooseMountPoint(Volume)
|
||||
* @see #cleanup(Volume, Path)
|
||||
*/
|
||||
default boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
||||
return false; //NO-OP
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a {@link Volume} to do any cleanup needed after unmount.
|
||||
*
|
||||
* <p>This method is only called if the {@link #prepare(Volume, Path)} method
|
||||
* of the same MountPointChooser returned {@code true}. Typically developers want to
|
||||
* delete any files created prior to mount or do similar tasks.<br>
|
||||
* Exceptions in this method should be handled gracefully.
|
||||
*
|
||||
* @param caller The Volume that is calling the method to cleanup the prepared mountpoint
|
||||
* @param mountPoint the mountpoint that was prepared by {@link #prepare(Volume, Path)}
|
||||
* @see #prepare(Volume, Path)
|
||||
*/
|
||||
default void cleanup(Volume caller, Path mountPoint) {
|
||||
//NO-OP
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the {@link MountPointChooserModule} to sort the available MPCs
|
||||
* and determine their execution order.
|
||||
* The priority must be defined by the developer to reflect a useful execution order.
|
||||
* MPCs with lower priorities will be placed at lower indices in the resulting
|
||||
* {@link SortedSet} and will be executed with higher probability.<br>
|
||||
* A specific priority <b>must not</b> be assigned to more than one MPC at a time;
|
||||
* the result of having two MPCs with equal priority is undefined.
|
||||
*
|
||||
* @return the priority of this MPC.
|
||||
*/
|
||||
int getPriority();
|
||||
|
||||
/**
|
||||
* Called by the {@link Volume} to determine the execution order of the registered MPCs.
|
||||
* <b>Implementations usually may not override this method.</b> This default implementation
|
||||
* sorts the MPCs in ascending order of their {@link #getPriority() priority.}<br>
|
||||
* <br>
|
||||
* <b>Original description:</b>
|
||||
* <p>{@inheritDoc}
|
||||
*
|
||||
* @implNote This default implementation sorts the MPCs in ascending order
|
||||
* of their {@link #getPriority() priority.}
|
||||
*/
|
||||
@Override
|
||||
default int compareTo(MountPointChooser other) {
|
||||
Preconditions.checkNotNull(other, "Other must not be null!");
|
||||
|
||||
//Sort by priority (ascending order)
|
||||
return Integer.compare(this.getPriority(), other.getPriority());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.cryptomator.common.mountpoint;
|
||||
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import org.cryptomator.common.vaults.PerVault;
|
||||
|
||||
import javax.inject.Named;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
|
||||
/**
|
||||
* Dagger-Module for {@link MountPointChooser MountPointChoosers.}<br>
|
||||
* See there for additional information.
|
||||
*
|
||||
* @see MountPointChooser
|
||||
*/
|
||||
@Module
|
||||
public abstract class MountPointChooserModule {
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
@PerVault
|
||||
public abstract MountPointChooser bindCustomMountPointChooser(CustomMountPointChooser chooser);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
@PerVault
|
||||
public abstract MountPointChooser bindCustomDriveLetterChooser(CustomDriveLetterChooser chooser);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
@PerVault
|
||||
public abstract MountPointChooser bindAvailableDriveLetterChooser(AvailableDriveLetterChooser chooser);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
@PerVault
|
||||
public abstract MountPointChooser bindTemporaryMountPointChooser(TemporaryMountPointChooser chooser);
|
||||
|
||||
@Provides
|
||||
@PerVault
|
||||
@Named("orderedMountPointChoosers")
|
||||
public static SortedSet<MountPointChooser> provideOrderedMountPointChoosers(Set<MountPointChooser> choosers) {
|
||||
//Sort by natural order. The natural order is defined by MountPointChooser#compareTo
|
||||
return ImmutableSortedSet.copyOf(choosers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.cryptomator.common.mountpoint;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
|
||||
public class TemporaryMountPointChooser implements MountPointChooser {
|
||||
|
||||
public static final int PRIORITY = 300;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TemporaryMountPointChooser.class);
|
||||
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
|
||||
|
||||
private final VaultSettings vaultSettings;
|
||||
private final Environment environment;
|
||||
|
||||
@Inject
|
||||
public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(Volume caller) {
|
||||
if (this.environment.getMountPointsDir().isEmpty()) {
|
||||
LOG.warn("\"cryptomator.mountPointsDir\" is not set to a valid path!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||
return this.environment.getMountPointsDir().map(this::choose);
|
||||
}
|
||||
|
||||
private Path choose(Path parent) {
|
||||
String basename = this.vaultSettings.mountName().get();
|
||||
//regular
|
||||
Path mountPoint = parent.resolve(basename);
|
||||
if (Files.notExists(mountPoint)) {
|
||||
return mountPoint;
|
||||
}
|
||||
//with id
|
||||
mountPoint = parent.resolve(basename + " (" +vaultSettings.getId() + ")");
|
||||
if (Files.notExists(mountPoint)) {
|
||||
return mountPoint;
|
||||
}
|
||||
//with id and count
|
||||
for (int i = 1; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
|
||||
mountPoint = parent.resolve(basename + "_(" +vaultSettings.getId() + ")_"+i);
|
||||
if (Files.notExists(mountPoint)) {
|
||||
return mountPoint;
|
||||
}
|
||||
}
|
||||
LOG.error("Failed to find feasible mountpoint at {}{}{}_x. Giving up after {} attempts.", parent, File.separator, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
||||
// 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 (SystemUtils.IS_OS_MAC && mountPoint.getParent().equals(Paths.get("/Volumes"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (caller.getMountPointRequirement()) {
|
||||
case PARENT_NO_MOUNT_POINT -> {
|
||||
Files.createDirectories(mountPoint.getParent());
|
||||
LOG.debug("Successfully created folder for mount point: {}", mountPoint);
|
||||
return false;
|
||||
}
|
||||
case EMPTY_MOUNT_POINT -> {
|
||||
Files.createDirectories(mountPoint);
|
||||
LOG.debug("Successfully created mount point: {}", mountPoint);
|
||||
return true;
|
||||
}
|
||||
case NONE -> {
|
||||
//Requirement "NONE" doesn't make any sense here.
|
||||
//No need to prepare/verify a Mountpoint without requiring one...
|
||||
throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
|
||||
}
|
||||
default -> {
|
||||
//Currently the case for "PARENT_OPT_MOUNT_POINT"
|
||||
throw new InvalidMountPointException(new IllegalStateException("Not implemented"));
|
||||
}
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
throw new InvalidMountPointException("IOException while preparing mountpoint", exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Volume caller, Path mountPoint) {
|
||||
try {
|
||||
Files.delete(mountPoint);
|
||||
LOG.debug("Successfully deleted mount point: {}", mountPoint);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not delete mount point: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return PRIORITY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
public enum KeychainBackend {
|
||||
GNOME("org.cryptomator.linux.keychain.SecretServiceKeychainAccess"),
|
||||
KDE("org.cryptomator.linux.keychain.KDEWalletKeychainAccess"),
|
||||
MAC_SYSTEM_KEYCHAIN("org.cryptomator.macos.keychain.MacSystemKeychainAccess"),
|
||||
WIN_SYSTEM_KEYCHAIN("org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess");
|
||||
|
||||
private final String providerClass;
|
||||
|
||||
KeychainBackend(String providerClass) {
|
||||
this.providerClass = providerClass;
|
||||
}
|
||||
|
||||
public String getProviderClass() {
|
||||
return providerClass;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
@@ -20,7 +22,6 @@ import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Settings {
|
||||
@@ -34,8 +35,9 @@ public class Settings {
|
||||
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
|
||||
public static final boolean DEFAULT_DEBUG_MODE = false;
|
||||
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
|
||||
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = SystemUtils.IS_OS_WINDOWS ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
|
||||
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
||||
public static final KeychainBackend DEFAULT_KEYCHAIN_BACKEND = SystemUtils.IS_OS_WINDOWS ? KeychainBackend.WIN_SYSTEM_KEYCHAIN : SystemUtils.IS_OS_MAC ? KeychainBackend.MAC_SYSTEM_KEYCHAIN : KeychainBackend.GNOME;
|
||||
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
|
||||
private static final String DEFAULT_LICENSE_KEY = "";
|
||||
|
||||
@@ -49,6 +51,7 @@ public class Settings {
|
||||
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
||||
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
||||
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
||||
private final ObjectProperty<KeychainBackend> keychainBackend = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_BACKEND);
|
||||
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
||||
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
|
||||
|
||||
@@ -68,6 +71,7 @@ public class Settings {
|
||||
debugMode.addListener(this::somethingChanged);
|
||||
preferredVolumeImpl.addListener(this::somethingChanged);
|
||||
theme.addListener(this::somethingChanged);
|
||||
keychainBackend.addListener(this::somethingChanged);
|
||||
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||
licenseKey.addListener(this::somethingChanged);
|
||||
}
|
||||
@@ -75,7 +79,7 @@ public class Settings {
|
||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||
this.saveCmd = saveCmd;
|
||||
}
|
||||
|
||||
|
||||
private void somethingChanged(@SuppressWarnings("unused") Observable observable) {
|
||||
this.save();
|
||||
}
|
||||
@@ -99,7 +103,7 @@ public class Settings {
|
||||
public BooleanProperty checkForUpdates() {
|
||||
return checkForUpdates;
|
||||
}
|
||||
|
||||
|
||||
public BooleanProperty startHidden() {
|
||||
return startHidden;
|
||||
}
|
||||
@@ -128,6 +132,8 @@ public class Settings {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public ObjectProperty<KeychainBackend> keychainBackend() { return keychainBackend; }
|
||||
|
||||
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
|
||||
return userInterfaceOrientation;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.geometry.NodeOrientation;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -38,6 +38,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
||||
out.name("theme").value(value.theme().get().name());
|
||||
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
||||
out.name("keychainBackend").value(value.keychainBackend().get().name());
|
||||
out.name("licenseKey").value(value.licenseKey().get());
|
||||
out.endObject();
|
||||
}
|
||||
@@ -58,46 +59,23 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
switch (name) {
|
||||
case "directories":
|
||||
settings.getDirectories().addAll(readVaultSettingsArray(in));
|
||||
break;
|
||||
case "askedForUpdateCheck":
|
||||
settings.askedForUpdateCheck().set(in.nextBoolean());
|
||||
break;
|
||||
case "checkForUpdatesEnabled":
|
||||
settings.checkForUpdates().set(in.nextBoolean());
|
||||
break;
|
||||
case "startHidden":
|
||||
settings.startHidden().set(in.nextBoolean());
|
||||
break;
|
||||
case "port":
|
||||
settings.port().set(in.nextInt());
|
||||
break;
|
||||
case "numTrayNotifications":
|
||||
settings.numTrayNotifications().set(in.nextInt());
|
||||
break;
|
||||
case "preferredGvfsScheme":
|
||||
settings.preferredGvfsScheme().set(parseWebDavUrlSchemePrefix(in.nextString()));
|
||||
break;
|
||||
case "debugMode":
|
||||
settings.debugMode().set(in.nextBoolean());
|
||||
break;
|
||||
case "preferredVolumeImpl":
|
||||
settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
||||
break;
|
||||
case "theme":
|
||||
settings.theme().set(parseUiTheme(in.nextString()));
|
||||
break;
|
||||
case "uiOrientation":
|
||||
settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||
break;
|
||||
case "licenseKey":
|
||||
settings.licenseKey().set(in.nextString());
|
||||
break;
|
||||
default:
|
||||
case "directories" -> settings.getDirectories().addAll(readVaultSettingsArray(in));
|
||||
case "askedForUpdateCheck" -> settings.askedForUpdateCheck().set(in.nextBoolean());
|
||||
case "checkForUpdatesEnabled" -> settings.checkForUpdates().set(in.nextBoolean());
|
||||
case "startHidden" -> settings.startHidden().set(in.nextBoolean());
|
||||
case "port" -> settings.port().set(in.nextInt());
|
||||
case "numTrayNotifications" -> settings.numTrayNotifications().set(in.nextInt());
|
||||
case "preferredGvfsScheme" -> settings.preferredGvfsScheme().set(parseWebDavUrlSchemePrefix(in.nextString()));
|
||||
case "debugMode" -> settings.debugMode().set(in.nextBoolean());
|
||||
case "preferredVolumeImpl" -> settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
||||
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
|
||||
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||
case "keychainBackend" -> settings.keychainBackend().set(parseKeychainBackend(in.nextString()));
|
||||
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
||||
default -> {
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
@@ -132,6 +110,15 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
}
|
||||
}
|
||||
|
||||
private KeychainBackend parseKeychainBackend(String backendName) {
|
||||
try {
|
||||
return KeychainBackend.valueOf(backendName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Invalid keychain backend {}. Defaulting to {}.", backendName, Settings.DEFAULT_KEYCHAIN_BACKEND);
|
||||
return Settings.DEFAULT_KEYCHAIN_BACKEND;
|
||||
}
|
||||
}
|
||||
|
||||
private NodeOrientation parseUiOrientation(String uiOrientationName) {
|
||||
try {
|
||||
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
public enum UiTheme {
|
||||
LIGHT("Light"),
|
||||
DARK("Dark");
|
||||
// CUSTOM("Custom (%s)");
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
private String displayName;
|
||||
public enum UiTheme {
|
||||
LIGHT("preferences.general.theme.light"), //
|
||||
DARK("preferences.general.theme.dark"), //
|
||||
AUTOMATIC("preferences.general.theme.automatic");
|
||||
|
||||
public static UiTheme[] applicableValues() {
|
||||
if (SystemUtils.IS_OS_MAC) {
|
||||
return values();
|
||||
} else {
|
||||
return new UiTheme[]{LIGHT, DARK};
|
||||
}
|
||||
}
|
||||
|
||||
private final String displayName;
|
||||
|
||||
UiTheme(String displayName) {
|
||||
this.displayName = displayName;
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
@@ -16,17 +20,15 @@ import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The settings specific to a single vault.
|
||||
* TODO: Change the name of individualMountPath and its derivatives to customMountPath
|
||||
*/
|
||||
public class VaultSettings {
|
||||
|
||||
@@ -36,35 +38,32 @@ public class VaultSettings {
|
||||
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
||||
public static final String DEFAULT_MOUNT_FLAGS = "";
|
||||
public static final int DEFAULT_FILENAME_LENGTH_LIMIT = -1;
|
||||
|
||||
private static final Random RNG = new Random();
|
||||
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
|
||||
|
||||
private static final Random RNG = new Random();
|
||||
|
||||
private final String id;
|
||||
private final ObjectProperty<Path> path = new SimpleObjectProperty();
|
||||
private final StringProperty mountName = new SimpleStringProperty();
|
||||
private final StringProperty displayName = new SimpleStringProperty();
|
||||
private final StringProperty winDriveLetter = new SimpleStringProperty();
|
||||
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
||||
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
|
||||
private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
|
||||
private final StringProperty individualMountPath = new SimpleStringProperty();
|
||||
private final BooleanProperty useCustomMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
|
||||
private final StringProperty customMountPath = new SimpleStringProperty();
|
||||
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
||||
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
||||
private final IntegerProperty filenameLengthLimit = new SimpleIntegerProperty(DEFAULT_FILENAME_LENGTH_LIMIT);
|
||||
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
||||
|
||||
private final StringBinding mountName;
|
||||
|
||||
public VaultSettings(String id) {
|
||||
this.id = Objects.requireNonNull(id);
|
||||
|
||||
EasyBind.subscribe(path, this::deriveMountNameFromPath);
|
||||
this.mountName = Bindings.createStringBinding(this::normalizeDisplayName, displayName);
|
||||
}
|
||||
|
||||
Observable[] observables() {
|
||||
return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, usesIndividualMountPath, individualMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit};
|
||||
}
|
||||
|
||||
private void deriveMountNameFromPath(Path path) {
|
||||
if (path != null && StringUtils.isBlank(mountName.get())) {
|
||||
mountName.set(normalizeMountName(path.getFileName().toString()));
|
||||
}
|
||||
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit, actionAfterUnlock};
|
||||
}
|
||||
|
||||
public static VaultSettings withRandomId() {
|
||||
@@ -77,23 +76,18 @@ public class VaultSettings {
|
||||
return BaseEncoding.base64Url().encode(randomBytes);
|
||||
}
|
||||
|
||||
public static String normalizeMountName(String mountName) {
|
||||
String normalizedMountName = StringUtils.stripAccents(mountName);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (char c : normalizedMountName.toCharArray()) {
|
||||
if (Character.isWhitespace(c)) {
|
||||
if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '_') {
|
||||
builder.append('_');
|
||||
}
|
||||
} else if (c < 127 && Character.isLetterOrDigit(c)) {
|
||||
builder.append(c);
|
||||
} else {
|
||||
if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '_') {
|
||||
builder.append('_');
|
||||
}
|
||||
}
|
||||
//visible for testing
|
||||
String normalizeDisplayName() {
|
||||
var original = displayName.getValueSafe();
|
||||
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
|
||||
return "_";
|
||||
}
|
||||
return builder.toString();
|
||||
|
||||
// replace whitespaces (tabs, linebreaks, ...) by simple space (0x20)
|
||||
var withoutFancyWhitespaces = CharMatcher.whitespace().collapseFrom(original, ' ');
|
||||
|
||||
// replace control chars as well as chars that aren't allowed in file names on standard file systems by underscore
|
||||
return CharMatcher.anyOf("<>:\"/\\|?*").or(CharMatcher.javaIsoControl()).collapseFrom(withoutFancyWhitespaces, '_');
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
@@ -106,7 +100,11 @@ public class VaultSettings {
|
||||
return path;
|
||||
}
|
||||
|
||||
public StringProperty mountName() {
|
||||
public StringProperty displayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public StringBinding mountName() {
|
||||
return mountName;
|
||||
}
|
||||
|
||||
@@ -114,6 +112,14 @@ public class VaultSettings {
|
||||
return winDriveLetter;
|
||||
}
|
||||
|
||||
public Optional<String> getWinDriveLetter() {
|
||||
String current = this.winDriveLetter.get();
|
||||
if (!Strings.isNullOrEmpty(current)) {
|
||||
return Optional.of(current);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public BooleanProperty unlockAfterStartup() {
|
||||
return unlockAfterStartup;
|
||||
}
|
||||
@@ -122,17 +128,17 @@ public class VaultSettings {
|
||||
return revealAfterMount;
|
||||
}
|
||||
|
||||
public BooleanProperty usesIndividualMountPath() {
|
||||
return usesIndividualMountPath;
|
||||
public BooleanProperty useCustomMountPath() {
|
||||
return useCustomMountPath;
|
||||
}
|
||||
|
||||
public StringProperty individualMountPath() {
|
||||
return individualMountPath;
|
||||
public StringProperty customMountPath() {
|
||||
return customMountPath;
|
||||
}
|
||||
|
||||
public Optional<String> getIndividualMountPath() {
|
||||
if (usesIndividualMountPath.get()) {
|
||||
return Optional.ofNullable(Strings.emptyToNull(individualMountPath.get()));
|
||||
public Optional<String> getCustomMountPath() {
|
||||
if (useCustomMountPath.get()) {
|
||||
return Optional.ofNullable(Strings.emptyToNull(customMountPath.get()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -145,11 +151,19 @@ public class VaultSettings {
|
||||
public StringProperty mountFlags() {
|
||||
return mountFlags;
|
||||
}
|
||||
|
||||
|
||||
public IntegerProperty filenameLengthLimit() {
|
||||
return filenameLengthLimit;
|
||||
}
|
||||
|
||||
public ObjectProperty<WhenUnlocked> actionAfterUnlock() {
|
||||
return actionAfterUnlock;
|
||||
}
|
||||
|
||||
public WhenUnlocked getActionAfterUnlock() {
|
||||
return actionAfterUnlock.get();
|
||||
}
|
||||
|
||||
/* Hashcode/Equals */
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,88 +21,85 @@ class VaultSettingsJsonAdapter {
|
||||
out.beginObject();
|
||||
out.name("id").value(value.getId());
|
||||
out.name("path").value(value.path().get().toString());
|
||||
out.name("mountName").value(value.mountName().get());
|
||||
out.name("displayName").value(value.displayName().get());
|
||||
out.name("winDriveLetter").value(value.winDriveLetter().get());
|
||||
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
|
||||
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
||||
out.name("usesIndividualMountPath").value(value.usesIndividualMountPath().get());
|
||||
out.name("individualMountPath").value(value.individualMountPath().get());
|
||||
out.name("useCustomMountPath").value(value.useCustomMountPath().get());
|
||||
out.name("customMountPath").value(value.customMountPath().get());
|
||||
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
||||
out.name("mountFlags").value(value.mountFlags().get());
|
||||
out.name("filenameLengthLimit").value(value.filenameLengthLimit().get());
|
||||
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
public VaultSettings read(JsonReader in) throws IOException {
|
||||
String id = null;
|
||||
String path = null;
|
||||
String mountName = null;
|
||||
String individualMountPath = null;
|
||||
String mountName = null; //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||
String displayName = null;
|
||||
String customMountPath = null;
|
||||
String winDriveLetter = null;
|
||||
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
||||
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
|
||||
boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
||||
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
||||
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
||||
int filenameLengthLimit = VaultSettings.DEFAULT_FILENAME_LENGTH_LIMIT;
|
||||
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
switch (name) {
|
||||
case "id":
|
||||
id = in.nextString();
|
||||
break;
|
||||
case "path":
|
||||
path = in.nextString();
|
||||
break;
|
||||
case "mountName":
|
||||
mountName = in.nextString();
|
||||
break;
|
||||
case "winDriveLetter":
|
||||
winDriveLetter = in.nextString();
|
||||
break;
|
||||
case "unlockAfterStartup":
|
||||
unlockAfterStartup = in.nextBoolean();
|
||||
break;
|
||||
case "revealAfterMount":
|
||||
revealAfterMount = in.nextBoolean();
|
||||
break;
|
||||
case "usesIndividualMountPath":
|
||||
usesIndividualMountPath = in.nextBoolean();
|
||||
break;
|
||||
case "individualMountPath":
|
||||
individualMountPath = in.nextString();
|
||||
break;
|
||||
case "usesReadOnlyMode":
|
||||
usesReadOnlyMode = in.nextBoolean();
|
||||
break;
|
||||
case "mountFlags":
|
||||
mountFlags = in.nextString();
|
||||
break;
|
||||
case "filenameLengthLimit":
|
||||
filenameLengthLimit = in.nextInt();
|
||||
break;
|
||||
default:
|
||||
case "id" -> id = in.nextString();
|
||||
case "path" -> path = in.nextString();
|
||||
case "mountName" -> mountName = in.nextString(); //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||
case "displayName" -> displayName = in.nextString();
|
||||
case "winDriveLetter" -> winDriveLetter = in.nextString();
|
||||
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
|
||||
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
|
||||
case "usesIndividualMountPath", "useCustomMountPath" -> useCustomMountPath = in.nextBoolean();
|
||||
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
|
||||
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
|
||||
case "mountFlags" -> mountFlags = in.nextString();
|
||||
case "filenameLengthLimit" -> filenameLengthLimit = in.nextInt();
|
||||
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
||||
default -> {
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id);
|
||||
vaultSettings.mountName().set(mountName);
|
||||
if (displayName != null) { //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||
vaultSettings.displayName().set(displayName);
|
||||
} else {
|
||||
vaultSettings.displayName().set(mountName);
|
||||
}
|
||||
vaultSettings.path().set(Paths.get(path));
|
||||
vaultSettings.winDriveLetter().set(winDriveLetter);
|
||||
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
|
||||
vaultSettings.revealAfterMount().set(revealAfterMount);
|
||||
vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath);
|
||||
vaultSettings.individualMountPath().set(individualMountPath);
|
||||
vaultSettings.useCustomMountPath().set(useCustomMountPath);
|
||||
vaultSettings.customMountPath().set(customMountPath);
|
||||
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
||||
vaultSettings.mountFlags().set(mountFlags);
|
||||
vaultSettings.filenameLengthLimit().set(filenameLengthLimit);
|
||||
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
||||
return vaultSettings;
|
||||
}
|
||||
|
||||
private WhenUnlocked parseActionAfterUnlock(String actionAfterUnlockName) {
|
||||
try {
|
||||
return WhenUnlocked.valueOf(actionAfterUnlockName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Invalid action after unlock {}. Defaulting to {}.", actionAfterUnlockName, VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK);
|
||||
return VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum VolumeImpl {
|
||||
WEBDAV("WebDAV"),
|
||||
FUSE("FUSE"),
|
||||
@@ -17,18 +15,4 @@ public enum VolumeImpl {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a VolumeImpl by display name.
|
||||
*
|
||||
* @param displayName Display name of the VolumeImpl
|
||||
* @return VolumeImpl with the given <code>displayName</code>.
|
||||
* @throws IllegalArgumentException if not volumeImpl with the given <code>displayName</code> was found.
|
||||
*/
|
||||
public static VolumeImpl forDisplayName(String displayName) throws IllegalArgumentException {
|
||||
return Arrays.stream(values()) //
|
||||
.filter(impl -> impl.displayName.equals(displayName)) //
|
||||
.findAny() //
|
||||
.orElseThrow(IllegalArgumentException::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum WebDavUrlScheme {
|
||||
DAV("dav", "dav:// (Gnome, Nautilus, ...)"),
|
||||
WEBDAV("webdav", "webdav:// (KDE, Dolphin, ...)");
|
||||
@@ -9,7 +7,8 @@ public enum WebDavUrlScheme {
|
||||
private final String prefix;
|
||||
private final String displayName;
|
||||
|
||||
WebDavUrlScheme(String prefix, String displayName) {this.prefix = prefix;
|
||||
WebDavUrlScheme(String prefix, String displayName) {
|
||||
this.prefix = prefix;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
@@ -20,18 +19,4 @@ public enum WebDavUrlScheme {
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a WebDavUrlScheme by prefix.
|
||||
*
|
||||
* @param prefix Prefix of the WebDavUrlScheme
|
||||
* @return WebDavUrlScheme with the given <code>prefix</code>.
|
||||
* @throws IllegalArgumentException if not WebDavUrlScheme with the given <code>prefix</code> was found.
|
||||
*/
|
||||
public static WebDavUrlScheme forPrefix(String prefix) throws IllegalArgumentException {
|
||||
return Arrays.stream(values()) //
|
||||
.filter(impl -> impl.prefix.equals(prefix)) //
|
||||
.findAny() //
|
||||
.orElseThrow(IllegalArgumentException::new);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
public enum WhenUnlocked {
|
||||
IGNORE("vaultOptions.general.actionAfterUnlock.ignore"),
|
||||
REVEAL("vaultOptions.general.actionAfterUnlock.reveal"),
|
||||
ASK("vaultOptions.general.actionAfterUnlock.ask");
|
||||
|
||||
private String displayName;
|
||||
|
||||
WhenUnlocked(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||
import org.cryptomator.common.mountpoint.MountPointChooser;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public abstract class AbstractVolume implements Volume {
|
||||
|
||||
private final SortedSet<MountPointChooser> choosers;
|
||||
|
||||
protected Path mountPoint;
|
||||
|
||||
//Cleanup
|
||||
private boolean cleanupRequired;
|
||||
private MountPointChooser usedChooser;
|
||||
|
||||
public AbstractVolume(SortedSet<MountPointChooser> choosers) {
|
||||
this.choosers = choosers;
|
||||
}
|
||||
|
||||
protected Path determineMountPoint() throws InvalidMountPointException {
|
||||
SortedSet<MountPointChooser> checkedChoosers = new TreeSet<>(); //Natural order
|
||||
for (MountPointChooser chooser : this.choosers) {
|
||||
if (!chooser.isApplicable(this)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Optional<Path> chosenPath = chooser.chooseMountPoint(this);
|
||||
checkedChoosers.add(chooser); //Consider a chooser checked if it's #chooseMountPoint() method was called
|
||||
if (chosenPath.isEmpty()) {
|
||||
//Chooser was applicable, but couldn't find a feasible mountpoint
|
||||
continue;
|
||||
}
|
||||
this.cleanupRequired = chooser.prepare(this, chosenPath.get()); //Fail entirely if an Exception occurs
|
||||
this.usedChooser = chooser;
|
||||
return chosenPath.get();
|
||||
}
|
||||
//SortedSet#stream() should return a sorted stream (that's what it's docs and the docs of #spliterator() say, even if they are not 100% clear for me.)
|
||||
//We want to keep that order, that's why we use ImmutableSet#toImmutableSet() to collect (even if it doesn't implement SortedSet, it's docs promise use encounter ordering.)
|
||||
String checked = Joiner.on(", ").join(checkedChoosers.stream().map((mpc) -> mpc.getClass().getTypeName()).collect(ImmutableSet.toImmutableSet()));
|
||||
throw new InvalidMountPointException(String.format("No feasible MountPoint found! Checked %s", checked.isBlank() ? "<No applicable MPC>" : checked));
|
||||
}
|
||||
|
||||
protected void cleanupMountPoint() {
|
||||
if (this.cleanupRequired) {
|
||||
this.usedChooser.cleanup(this, this.mountPoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> getMountPoint() {
|
||||
return Optional.ofNullable(mountPoint);
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
public @interface DefaultMountFlags {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||
import org.cryptomator.common.mountpoint.MountPointChooser;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
import org.cryptomator.frontend.dokany.Mount;
|
||||
import org.cryptomator.frontend.dokany.MountFactory;
|
||||
@@ -10,84 +12,47 @@ import org.slf4j.Logger;
|
||||
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.Files;
|
||||
import java.nio.file.NotDirectoryException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Named;
|
||||
import java.util.SortedSet;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class DokanyVolume implements Volume {
|
||||
public class DokanyVolume extends AbstractVolume {
|
||||
|
||||
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 = "CryptomatorFS";
|
||||
|
||||
private final VaultSettings vaultSettings;
|
||||
private final MountFactory mountFactory;
|
||||
private final WindowsDriveLetters windowsDriveLetters;
|
||||
|
||||
private Mount mount;
|
||||
private Path mountPoint;
|
||||
|
||||
@Inject
|
||||
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, WindowsDriveLetters windowsDriveLetters) {
|
||||
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, @Named("orderedMountPointChoosers") SortedSet<MountPointChooser> choosers) {
|
||||
super(choosers);
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.mountFactory = new MountFactory(executorService);
|
||||
this.windowsDriveLetters = windowsDriveLetters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return DokanyVolume.isSupportedStatic();
|
||||
public VolumeImpl getImplementationType() {
|
||||
return VolumeImpl.DOKANY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException, IOException {
|
||||
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
|
||||
this.mountPoint = determineMountPoint();
|
||||
String mountName = vaultSettings.mountName().get();
|
||||
try {
|
||||
this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, mountName, FS_TYPE_NAME, mountFlags.strip());
|
||||
this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip());
|
||||
} catch (MountFailedException e) {
|
||||
if (vaultSettings.getIndividualMountPath().isPresent()) {
|
||||
if (vaultSettings.getCustomMountPath().isPresent()) {
|
||||
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPoint);
|
||||
}
|
||||
throw new VolumeException("Unable to mount Filesystem", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Path determineMountPoint() throws VolumeException, IOException {
|
||||
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
|
||||
if (optionalCustomMountPoint.isPresent()) {
|
||||
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
||||
checkProvidedMountPoint(customMountPoint);
|
||||
return customMountPoint;
|
||||
} else if (!Strings.isNullOrEmpty(vaultSettings.winDriveLetter().get())) {
|
||||
return Path.of(vaultSettings.winDriveLetter().get().charAt(0) + ":\\");
|
||||
} else {
|
||||
//auto assign drive letter
|
||||
if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
|
||||
return Path.of(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\");
|
||||
} else {
|
||||
//TODO: Error Handling
|
||||
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
|
||||
public void reveal() throws VolumeException {
|
||||
boolean success = mount.reveal();
|
||||
@@ -99,11 +64,17 @@ public class DokanyVolume implements Volume {
|
||||
@Override
|
||||
public void unmount() {
|
||||
mount.close();
|
||||
cleanupMountPoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> getMountPoint() {
|
||||
return Optional.ofNullable(mountPoint);
|
||||
public boolean isSupported() {
|
||||
return DokanyVolume.isSupportedStatic();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MountPointRequirement getMountPointRequirement() {
|
||||
return MountPointRequirement.EMPTY_MOUNT_POINT;
|
||||
}
|
||||
|
||||
public static boolean isSupportedStatic() {
|
||||
|
||||
@@ -2,8 +2,9 @@ package org.cryptomator.common.vaults;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||
import org.cryptomator.common.mountpoint.MountPointChooser;
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
|
||||
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
|
||||
@@ -15,95 +16,36 @@ import org.slf4j.Logger;
|
||||
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.Files;
|
||||
import java.nio.file.NotDirectoryException;
|
||||
import javax.inject.Named;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.SortedSet;
|
||||
|
||||
public class FuseVolume implements Volume {
|
||||
public class FuseVolume extends AbstractVolume {
|
||||
|
||||
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");
|
||||
|
||||
private final VaultSettings vaultSettings;
|
||||
private final Environment environment;
|
||||
|
||||
private Mount fuseMnt;
|
||||
private Path mountPoint;
|
||||
private boolean createdTemporaryMountPoint;
|
||||
private Mount mount;
|
||||
|
||||
@Inject
|
||||
public FuseVolume(VaultSettings vaultSettings, Environment environment) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.environment = environment;
|
||||
public FuseVolume(@Named("orderedMountPointChoosers") SortedSet<MountPointChooser> choosers) {
|
||||
super(choosers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mount(CryptoFileSystem fs, String mountFlags) throws IOException, FuseNotSupportedException, VolumeException {
|
||||
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
|
||||
if (optionalCustomMountPoint.isPresent()) {
|
||||
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
||||
checkProvidedMountPoint(customMountPoint);
|
||||
this.mountPoint = customMountPoint;
|
||||
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
|
||||
} else {
|
||||
this.mountPoint = prepareTemporaryMountPoint();
|
||||
LOG.debug("Successfully created mount point: {}", mountPoint);
|
||||
}
|
||||
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
|
||||
this.mountPoint = determineMountPoint();
|
||||
|
||||
mount(fs.getPath("/"), mountFlags);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, String mountFlags) throws VolumeException {
|
||||
try {
|
||||
Mounter mounter = FuseMountFactory.getMounter();
|
||||
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
||||
.withFlags(splitFlags(mountFlags)).withMountPoint(mountPoint) //
|
||||
.build();
|
||||
this.fuseMnt = mounter.mount(root, envVars);
|
||||
} catch (CommandFailedException e) {
|
||||
this.mount = mounter.mount(root, envVars);
|
||||
} catch (CommandFailedException | FuseNotSupportedException e) {
|
||||
throw new VolumeException("Unable to mount Filesystem", e);
|
||||
}
|
||||
}
|
||||
@@ -115,7 +57,7 @@ public class FuseVolume implements Volume {
|
||||
@Override
|
||||
public void reveal() throws VolumeException {
|
||||
try {
|
||||
fuseMnt.revealInFileManager();
|
||||
mount.revealInFileManager();
|
||||
} catch (CommandFailedException e) {
|
||||
LOG.debug("Revealing the vault in file manger failed: " + e.getMessage());
|
||||
throw new VolumeException(e);
|
||||
@@ -130,34 +72,23 @@ public class FuseVolume implements Volume {
|
||||
@Override
|
||||
public synchronized void unmountForced() throws VolumeException {
|
||||
try {
|
||||
fuseMnt.unmountForced();
|
||||
fuseMnt.close();
|
||||
mount.unmountForced();
|
||||
mount.close();
|
||||
} catch (CommandFailedException e) {
|
||||
throw new VolumeException(e);
|
||||
}
|
||||
cleanupTemporaryMountPoint();
|
||||
cleanupMountPoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void unmount() throws VolumeException {
|
||||
try {
|
||||
fuseMnt.unmount();
|
||||
fuseMnt.close();
|
||||
mount.unmount();
|
||||
mount.close();
|
||||
} catch (CommandFailedException e) {
|
||||
throw new VolumeException(e);
|
||||
}
|
||||
cleanupTemporaryMountPoint();
|
||||
}
|
||||
|
||||
private void cleanupTemporaryMountPoint() {
|
||||
if (createdTemporaryMountPoint) {
|
||||
try {
|
||||
Files.delete(mountPoint);
|
||||
LOG.debug("Successfully deleted mount point: {}", mountPoint);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not delete mount point: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
cleanupMountPoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -166,12 +97,17 @@ public class FuseVolume implements Volume {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> getMountPoint() {
|
||||
return Optional.ofNullable(mountPoint);
|
||||
public VolumeImpl getImplementationType() {
|
||||
return VolumeImpl.FUSE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MountPointRequirement getMountPointRequirement() {
|
||||
return SystemUtils.IS_OS_WINDOWS ? MountPointRequirement.PARENT_NO_MOUNT_POINT : MountPointRequirement.EMPTY_MOUNT_POINT;
|
||||
}
|
||||
|
||||
public static boolean isSupportedStatic() {
|
||||
return (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) && FuseMountFactory.isFuseSupported();
|
||||
return FuseMountFactory.isFuseSupported();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
/**
|
||||
* Enumeration used to indicate the requirements for mounting a vault
|
||||
* using a specific {@link Volume VolumeProvider}, e.g. {@link FuseVolume}.
|
||||
*/
|
||||
public enum MountPointRequirement {
|
||||
|
||||
/**
|
||||
* No Mountpoint on the local filesystem required. (e.g. WebDAV)
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* A parent folder is required, but the actual Mountpoint must not exist.
|
||||
*/
|
||||
PARENT_NO_MOUNT_POINT,
|
||||
|
||||
/**
|
||||
* A parent folder is required, but the actual Mountpoint may exist.
|
||||
*/
|
||||
PARENT_OPT_MOUNT_POINT,
|
||||
|
||||
/**
|
||||
* The actual Mountpoint must exist and must be empty.
|
||||
*/
|
||||
EMPTY_MOUNT_POINT;
|
||||
}
|
||||
@@ -8,6 +8,6 @@ import java.lang.annotation.RetentionPolicy;
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface PerVault {
|
||||
public @interface PerVault {
|
||||
|
||||
}
|
||||
|
||||
@@ -9,14 +9,11 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.LazyInitializer;
|
||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.vaults.Volume.VolumeException;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
||||
@@ -31,13 +28,20 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.NotDirectoryException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@@ -56,7 +60,7 @@ public class Vault {
|
||||
private final ObjectProperty<VaultState> state;
|
||||
private final ObjectProperty<Exception> lastKnownException;
|
||||
private final VaultStats stats;
|
||||
private final StringBinding displayableName;
|
||||
private final StringBinding displayName;
|
||||
private final StringBinding displayablePath;
|
||||
private final BooleanBinding locked;
|
||||
private final BooleanBinding processing;
|
||||
@@ -66,6 +70,7 @@ public class Vault {
|
||||
private final BooleanBinding unknownError;
|
||||
private final StringBinding accessPoint;
|
||||
private final BooleanBinding accessPointPresent;
|
||||
private final BooleanProperty showingStats;
|
||||
|
||||
private volatile Volume volume;
|
||||
|
||||
@@ -78,7 +83,7 @@ public class Vault {
|
||||
this.state = state;
|
||||
this.lastKnownException = lastKnownException;
|
||||
this.stats = stats;
|
||||
this.displayableName = Bindings.createStringBinding(this::getDisplayableName, vaultSettings.path());
|
||||
this.displayName = Bindings.createStringBinding(this::getDisplayName, vaultSettings.displayName());
|
||||
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
||||
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
||||
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
||||
@@ -88,6 +93,7 @@ public class Vault {
|
||||
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
||||
this.accessPoint = Bindings.createStringBinding(this::getAccessPoint, state);
|
||||
this.accessPointPresent = this.accessPoint.isNotEmpty();
|
||||
this.showingStats = new SimpleBooleanProperty(false);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
@@ -120,16 +126,13 @@ public class Vault {
|
||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||
}
|
||||
|
||||
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
|
||||
if (vaultSettings.usesIndividualMountPath().get() && Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
|
||||
throw new NotDirectoryException("");
|
||||
}
|
||||
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
|
||||
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
|
||||
volume = volumeProvider.get();
|
||||
volume.mount(fs, getEffectiveMountFlags());
|
||||
}
|
||||
|
||||
public synchronized void lock(boolean forced) throws Volume.VolumeException {
|
||||
public synchronized void lock(boolean forced) throws VolumeException {
|
||||
if (forced && volume.supportsForcedUnmount()) {
|
||||
volume.unmountForced();
|
||||
} else {
|
||||
@@ -145,7 +148,7 @@ public class Vault {
|
||||
}
|
||||
}
|
||||
|
||||
public void reveal() throws Volume.VolumeException {
|
||||
public void reveal() throws VolumeException {
|
||||
volume.reveal();
|
||||
}
|
||||
|
||||
@@ -225,13 +228,12 @@ public class Vault {
|
||||
return state.get() == VaultState.ERROR;
|
||||
}
|
||||
|
||||
public StringBinding displayableNameProperty() {
|
||||
return displayableName;
|
||||
public StringBinding displayNameProperty() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public String getDisplayableName() {
|
||||
Path p = vaultSettings.path().get();
|
||||
return p.getFileName().toString();
|
||||
public String getDisplayName() {
|
||||
return vaultSettings.displayName().get();
|
||||
}
|
||||
|
||||
public StringBinding accessPointProperty() {
|
||||
@@ -270,6 +272,15 @@ public class Vault {
|
||||
}
|
||||
}
|
||||
|
||||
public BooleanProperty showingStatsProperty() {
|
||||
return showingStats;
|
||||
}
|
||||
|
||||
public boolean isShowingStats() {
|
||||
return accessPointPresent.get();
|
||||
}
|
||||
|
||||
|
||||
// ******************************************************************************
|
||||
// Getter/Setter
|
||||
// *******************************************************************************/
|
||||
@@ -319,6 +330,10 @@ public class Vault {
|
||||
return vaultSettings.getId();
|
||||
}
|
||||
|
||||
public Optional<Volume> getVolume() {
|
||||
return Optional.ofNullable(this.volume);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
// Hashcode / Equals
|
||||
// *******************************************************************************/
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.common.mountpoint.MountPointChooserModule;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Named;
|
||||
|
||||
@PerVault
|
||||
@Subcomponent(modules = {VaultModule.class})
|
||||
@Subcomponent(modules = {VaultModule.class, MountPointChooserModule.class})
|
||||
public interface VaultComponent {
|
||||
|
||||
Vault vault();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -20,7 +20,7 @@ class VaultListChangeListener implements ListChangeListener<Vault> {
|
||||
|
||||
@Override
|
||||
public void onChanged(Change<? extends Vault> c) {
|
||||
while(c.next()) {
|
||||
while (c.next()) {
|
||||
if (c.wasAdded()) {
|
||||
List<VaultSettings> addedSettings = c.getAddedSubList().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
|
||||
vaultSettingsList.addAll(c.getFrom(), addedSettings);
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
@@ -19,12 +17,14 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
@@ -36,10 +36,12 @@ public class VaultListManager {
|
||||
|
||||
private final VaultComponent.Builder vaultComponentBuilder;
|
||||
private final ObservableList<Vault> vaultList;
|
||||
private final String defaultVaultName;
|
||||
|
||||
@Inject
|
||||
public VaultListManager(VaultComponent.Builder vaultComponentBuilder, Settings settings) {
|
||||
public VaultListManager(VaultComponent.Builder vaultComponentBuilder, ResourceBundle resourceBundle, Settings settings) {
|
||||
this.vaultComponentBuilder = vaultComponentBuilder;
|
||||
this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName");
|
||||
this.vaultList = FXCollections.observableArrayList(Vault::observables);
|
||||
|
||||
addAll(settings.getDirectories());
|
||||
@@ -59,14 +61,23 @@ public class VaultListManager {
|
||||
if (alreadyExistingVault.isPresent()) {
|
||||
return alreadyExistingVault.get();
|
||||
} else {
|
||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||
vaultSettings.path().set(normalizedPathToVault);
|
||||
Vault newVault = create(vaultSettings);
|
||||
Vault newVault = create(newVaultSettings(normalizedPathToVault));
|
||||
vaultList.add(newVault);
|
||||
return newVault;
|
||||
}
|
||||
}
|
||||
|
||||
private VaultSettings newVaultSettings(Path path) {
|
||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||
vaultSettings.path().set(path);
|
||||
if (path.getFileName() != null) {
|
||||
vaultSettings.displayName().set(path.getFileName().toString());
|
||||
} else {
|
||||
vaultSettings.displayName().set(defaultVaultName);
|
||||
}
|
||||
return vaultSettings;
|
||||
}
|
||||
|
||||
private void addAll(Collection<VaultSettings> vaultSettings) {
|
||||
Collection<Vault> vaults = vaultSettings.stream().map(this::create).collect(Collectors.toList());
|
||||
vaultList.addAll(vaults);
|
||||
@@ -93,7 +104,26 @@ public class VaultListManager {
|
||||
return compBuilder.build().vault();
|
||||
}
|
||||
|
||||
public static VaultState determineVaultState(Path pathToVault) throws IOException {
|
||||
public static VaultState redetermineVaultState(Vault vault) {
|
||||
VaultState previousState = vault.getState();
|
||||
return switch (previousState) {
|
||||
case LOCKED, NEEDS_MIGRATION, MISSING -> {
|
||||
try {
|
||||
VaultState determinedState = determineVaultState(vault.getPath());
|
||||
vault.setState(determinedState);
|
||||
yield determinedState;
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
|
||||
vault.setState(VaultState.ERROR);
|
||||
vault.setLastKnownException(e);
|
||||
yield VaultState.ERROR;
|
||||
}
|
||||
}
|
||||
case ERROR, UNLOCKED, PROCESSING -> previousState;
|
||||
};
|
||||
}
|
||||
|
||||
private static VaultState determineVaultState(Path pathToVault) throws IOException {
|
||||
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
|
||||
return VaultState.MISSING;
|
||||
} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {
|
||||
|
||||
@@ -7,14 +7,6 @@ package org.cryptomator.common.vaults;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
@@ -25,6 +17,12 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Named;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -77,7 +75,7 @@ public class VaultModule {
|
||||
@DefaultMountFlags
|
||||
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
||||
ObjectProperty<VolumeImpl> preferredVolumeImpl = settings.preferredVolumeImpl();
|
||||
StringProperty mountName = vaultSettings.mountName();
|
||||
StringBinding mountName = vaultSettings.mountName();
|
||||
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
|
||||
|
||||
return Bindings.createStringBinding(() -> {
|
||||
@@ -86,6 +84,8 @@ public class VaultModule {
|
||||
return getMacFuseDefaultMountFlags(mountName, readOnly);
|
||||
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_LINUX) {
|
||||
return getLinuxFuseDefaultMountFlags(readOnly);
|
||||
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_WINDOWS) {
|
||||
return getWindowsFuseDefaultMountFlags(mountName, readOnly);
|
||||
} else if (v == VolumeImpl.DOKANY && SystemUtils.IS_OS_WINDOWS) {
|
||||
return getDokanyDefaultMountFlags(readOnly);
|
||||
} else {
|
||||
@@ -95,7 +95,7 @@ public class VaultModule {
|
||||
}
|
||||
|
||||
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
|
||||
private String getMacFuseDefaultMountFlags(ReadOnlyStringProperty mountName, ReadOnlyBooleanProperty readOnly) {
|
||||
private String getMacFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
|
||||
assert SystemUtils.IS_OS_MAC_OSX;
|
||||
StringBuilder flags = new StringBuilder();
|
||||
if (readOnly.get()) {
|
||||
@@ -144,6 +144,28 @@ public class VaultModule {
|
||||
return flags.toString().strip();
|
||||
}
|
||||
|
||||
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse_main.c#L53-L62 for syntax guide
|
||||
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse.c#L295-L319 for options (-o <...>)
|
||||
// see https://github.com/billziss-gh/winfsp/wiki/Frequently-Asked-Questions/5ba00e4be4f5e938eaae6ef1500b331de12dee77 (FUSE 4.) on why the given defaults were choosen
|
||||
private String getWindowsFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
|
||||
assert SystemUtils.IS_OS_WINDOWS;
|
||||
StringBuilder flags = new StringBuilder();
|
||||
|
||||
//WinFSP has no explicit "readonly"-option, nut not setting the group/user-id has the same effect, tho.
|
||||
//So for the time being not setting them is the way to go...
|
||||
//See: https://github.com/billziss-gh/winfsp/issues/319
|
||||
if (!readOnly.get()) {
|
||||
flags.append(" -ouid=-1");
|
||||
flags.append(" -ogid=-1");
|
||||
}
|
||||
flags.append(" -ovolname=").append(mountName.get());
|
||||
//Dokany requires this option to be set, WinFSP doesn't seem to share this peculiarity,
|
||||
//but the option exists. Let's keep this here in case we need it.
|
||||
// flags.append(" -oThreadCount=").append(5);
|
||||
|
||||
return flags.toString().strip();
|
||||
}
|
||||
|
||||
// see https://github.com/cryptomator/dokany-nio-adapter/blob/develop/src/main/java/org/cryptomator/frontend/dokany/MountUtil.java#L30-L34
|
||||
private String getDokanyDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
|
||||
assert SystemUtils.IS_OS_WINDOWS;
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.cryptomator.common.vaults;
|
||||
|
||||
public enum VaultState {
|
||||
/**
|
||||
* No vault found at the provided path
|
||||
* No vault found at the provided path
|
||||
*/
|
||||
MISSING,
|
||||
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.util.Duration;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemStats;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.util.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -28,6 +30,15 @@ public class VaultStats {
|
||||
private final ScheduledService<Optional<CryptoFileSystemStats>> updateService;
|
||||
private final LongProperty bytesPerSecondRead = new SimpleLongProperty();
|
||||
private final LongProperty bytesPerSecondWritten = new SimpleLongProperty();
|
||||
private final LongProperty bytesPerSecondEncrypted = new SimpleLongProperty();
|
||||
private final LongProperty bytesPerSecondDecrypted = new SimpleLongProperty();
|
||||
private final DoubleProperty cacheHitRate = new SimpleDoubleProperty();
|
||||
private final LongProperty toalBytesRead = new SimpleLongProperty();
|
||||
private final LongProperty toalBytesWritten = new SimpleLongProperty();
|
||||
private final LongProperty totalBytesEncrypted = new SimpleLongProperty();
|
||||
private final LongProperty totalBytesDecrypted = new SimpleLongProperty();
|
||||
private final LongProperty filesRead = new SimpleLongProperty();
|
||||
private final LongProperty filesWritten = new SimpleLongProperty();
|
||||
|
||||
@Inject
|
||||
VaultStats(AtomicReference<CryptoFileSystem> fs, ObjectProperty<VaultState> state, ExecutorService executor) {
|
||||
@@ -53,12 +64,36 @@ public class VaultStats {
|
||||
|
||||
private void updateStats(Optional<CryptoFileSystemStats> stats) {
|
||||
assert Platform.isFxApplicationThread();
|
||||
bytesPerSecondRead.set(stats.map(CryptoFileSystemStats::pollBytesRead).orElse(0l));
|
||||
bytesPerSecondWritten.set(stats.map(CryptoFileSystemStats::pollBytesWritten).orElse(0l));
|
||||
bytesPerSecondRead.set(stats.map(CryptoFileSystemStats::pollBytesRead).orElse(0L));
|
||||
bytesPerSecondWritten.set(stats.map(CryptoFileSystemStats::pollBytesWritten).orElse(0L));
|
||||
cacheHitRate.set(stats.map(this::getCacheHitRate).orElse(0.0));
|
||||
bytesPerSecondDecrypted.set(stats.map(CryptoFileSystemStats::pollBytesDecrypted).orElse(0L));
|
||||
bytesPerSecondEncrypted.set(stats.map(CryptoFileSystemStats::pollBytesEncrypted).orElse(0L));
|
||||
toalBytesRead.set(stats.map(CryptoFileSystemStats::pollTotalBytesRead).orElse(0L));
|
||||
toalBytesWritten.set(stats.map(CryptoFileSystemStats::pollTotalBytesWritten).orElse(0L));
|
||||
totalBytesEncrypted.set(stats.map(CryptoFileSystemStats::pollTotalBytesEncrypted).orElse(0L));
|
||||
totalBytesDecrypted.set(stats.map(CryptoFileSystemStats::pollTotalBytesDecrypted).orElse(0L));
|
||||
filesRead.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesRead).orElse(0L));
|
||||
filesWritten.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesWritten).orElse(0L));
|
||||
|
||||
}
|
||||
|
||||
private double getCacheHitRate(CryptoFileSystemStats stats) {
|
||||
long accesses = stats.pollChunkCacheAccesses();
|
||||
long hits = stats.pollChunkCacheHits();
|
||||
if (accesses == 0) {
|
||||
return 0.0;
|
||||
} else {
|
||||
return hits / (double) accesses;
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateStatsService extends ScheduledService<Optional<CryptoFileSystemStats>> {
|
||||
|
||||
private UpdateStatsService() {
|
||||
setOnFailed(event -> LOG.error("Error in UpdateStateService.", getException()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task<Optional<CryptoFileSystemStats>> createTask() {
|
||||
return new Task<>() {
|
||||
@@ -94,4 +129,50 @@ public class VaultStats {
|
||||
public long getBytesPerSecondWritten() {
|
||||
return bytesPerSecondWritten.get();
|
||||
}
|
||||
|
||||
public LongProperty bytesPerSecondEncryptedProperty() {
|
||||
return bytesPerSecondEncrypted;
|
||||
}
|
||||
|
||||
public long getBytesPerSecondEnrypted() {
|
||||
return bytesPerSecondEncrypted.get();
|
||||
}
|
||||
|
||||
public LongProperty bytesPerSecondDecryptedProperty() {
|
||||
return bytesPerSecondDecrypted;
|
||||
}
|
||||
|
||||
public long getBytesPerSecondDecrypted() {
|
||||
return bytesPerSecondDecrypted.get();
|
||||
}
|
||||
|
||||
public DoubleProperty cacheHitRateProperty() { return cacheHitRate; }
|
||||
|
||||
public double getCacheHitRate() {
|
||||
return cacheHitRate.get();
|
||||
}
|
||||
|
||||
public LongProperty toalBytesReadProperty() {return toalBytesRead;}
|
||||
|
||||
public long getTotalBytesRead() { return toalBytesRead.get();}
|
||||
|
||||
public LongProperty toalBytesWrittenProperty() {return toalBytesWritten;}
|
||||
|
||||
public long getTotalBytesWritten() { return toalBytesWritten.get();}
|
||||
|
||||
public LongProperty totalBytesEncryptedProperty() {return totalBytesEncrypted;}
|
||||
|
||||
public long getTotalBytesEncrypted() { return totalBytesEncrypted.get();}
|
||||
|
||||
public LongProperty totalBytesDecryptedProperty() {return totalBytesDecrypted;}
|
||||
|
||||
public long getTotalBytesDecrypted() { return totalBytesDecrypted.get();}
|
||||
|
||||
public LongProperty filesRead() { return filesRead;}
|
||||
|
||||
public long getFilesRead() { return filesRead.get();}
|
||||
|
||||
public LongProperty filesWritten() {return filesWritten;}
|
||||
|
||||
public long getFilesWritten() {return filesWritten.get();}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
|
||||
@@ -20,11 +21,18 @@ public interface Volume {
|
||||
*/
|
||||
boolean isSupported();
|
||||
|
||||
/**
|
||||
* Gets the coresponding enum type of the {@link VolumeImpl volume implementation ("VolumeImpl")} that is implemented by this Volume.
|
||||
*
|
||||
* @return the type of implementation as defined by the {@link VolumeImpl VolumeImpl enum}
|
||||
*/
|
||||
VolumeImpl getImplementationType();
|
||||
|
||||
/**
|
||||
* @param fs
|
||||
* @throws IOException
|
||||
*/
|
||||
void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException;
|
||||
void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException, InvalidMountPointException;
|
||||
|
||||
void reveal() throws VolumeException;
|
||||
|
||||
@@ -32,6 +40,8 @@ public interface Volume {
|
||||
|
||||
Optional<Path> getMountPoint();
|
||||
|
||||
MountPointRequirement getMountPointRequirement();
|
||||
|
||||
// optional forced unmounting:
|
||||
|
||||
default boolean supportsForcedUnmount() {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.cryptomator.frontend.webdav.mount.MountParams;
|
||||
@@ -44,7 +46,9 @@ public class WebDavVolume implements Volume {
|
||||
if (!server.isRunning()) {
|
||||
server.start();
|
||||
}
|
||||
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get());
|
||||
CharMatcher acceptable = CharMatcher.inRange('0', '9').or(CharMatcher.inRange('A', 'Z')).or(CharMatcher.inRange('a', 'z'));
|
||||
String urlConformMountName = acceptable.negate().collapseFrom(vaultSettings.mountName().get(), '_');
|
||||
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + urlConformMountName);
|
||||
servlet.start();
|
||||
mount();
|
||||
}
|
||||
@@ -98,7 +102,12 @@ public class WebDavVolume implements Volume {
|
||||
|
||||
@Override
|
||||
public Optional<Path> getMountPoint() {
|
||||
return Optional.ofNullable(mountPoint);
|
||||
return Optional.ofNullable(mountPoint); //TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public MountPointRequirement getMountPointRequirement() {
|
||||
return MountPointRequirement.NONE;
|
||||
}
|
||||
|
||||
private String getLocalhostAliasOrNull() {
|
||||
@@ -126,6 +135,11 @@ public class WebDavVolume implements Volume {
|
||||
return WebDavVolume.isSupportedStatic();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VolumeImpl getImplementationType() {
|
||||
return VolumeImpl.WEBDAV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsForcedUnmount() {
|
||||
return mount != null && mount.forced().isPresent();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
@@ -12,6 +13,7 @@ import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
@@ -24,7 +26,7 @@ public final class WindowsDriveLetters {
|
||||
|
||||
static {
|
||||
try (IntStream stream = IntStream.rangeClosed('C', 'Z')) {
|
||||
C_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(Collectors.toSet());
|
||||
C_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(ImmutableSet.toImmutableSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +48,18 @@ public final class WindowsDriveLetters {
|
||||
}
|
||||
|
||||
public Set<String> getAvailableDriveLetters() {
|
||||
return Sets.difference(C_TO_Z, getOccupiedDriveLetters());
|
||||
return Sets.difference(getAllDriveLetters(), getOccupiedDriveLetters());
|
||||
}
|
||||
|
||||
public Optional<String> getAvailableDriveLetter() {
|
||||
return getAvailableDriveLetters().stream().findFirst();
|
||||
}
|
||||
|
||||
public Optional<Path> getAvailableDriveLetterPath() {
|
||||
return getAvailableDriveLetter().map(this::toPath);
|
||||
}
|
||||
|
||||
public Path toPath(String driveLetter) {
|
||||
return Path.of(driveLetter + ":\\");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class EnvironmentTest {
|
||||
|
||||
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"),
|
||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"), //
|
||||
Paths.get("/home/testuser/.Cryptomator/settings.json")));
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class EnvironmentTest {
|
||||
|
||||
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"),
|
||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"), //
|
||||
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
|
||||
}
|
||||
|
||||
@@ -123,8 +123,8 @@ class EnvironmentTest {
|
||||
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"),
|
||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"), //
|
||||
Paths.get("/home/testuser/test2"), //
|
||||
Paths.get("/foo/bar/test")));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import org.cryptomator.common.LicenseChecker;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -14,7 +13,7 @@ class LicenseCheckerTest {
|
||||
+ "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" //
|
||||
+ "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" //
|
||||
+ "Al8G7CqwoJOsW7Kddns=";
|
||||
|
||||
|
||||
private LicenseChecker licenseChecker;
|
||||
|
||||
@BeforeEach
|
||||
@@ -25,9 +24,9 @@ class LicenseCheckerTest {
|
||||
@Test
|
||||
public void testCheckValidLicense() {
|
||||
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
||||
|
||||
|
||||
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
||||
|
||||
|
||||
Assertions.assertTrue(decoded.isPresent());
|
||||
Assertions.assertEquals("cryptobot@example.com", decoded.get().getSubject());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.cryptomator.common.keychain;
|
||||
|
||||
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
|
||||
public class KeychainManagerTest {
|
||||
|
||||
@Test
|
||||
public void testStoreAndLoad() throws KeychainAccessException {
|
||||
KeychainManager keychainManager = new KeychainManager(new SimpleObjectProperty<>(new MapKeychainAccess()));
|
||||
keychainManager.storePassphrase("test", "asd");
|
||||
Assertions.assertArrayEquals("asd".toCharArray(), keychainManager.loadPassphrase("test"));
|
||||
}
|
||||
|
||||
@Nested
|
||||
public static class WhenObservingProperties {
|
||||
|
||||
@BeforeAll
|
||||
public static void startup() throws InterruptedException {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Platform.startup(latch::countDown);
|
||||
latch.await(5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPropertyChangesWhenStoringPassword() throws KeychainAccessException, InterruptedException {
|
||||
KeychainManager keychainManager = new KeychainManager(new SimpleObjectProperty<>(new MapKeychainAccess()));
|
||||
ReadOnlyBooleanProperty property = keychainManager.getPassphraseStoredProperty("test");
|
||||
Assertions.assertEquals(false, property.get());
|
||||
|
||||
keychainManager.storePassphrase("test", "bar");
|
||||
|
||||
AtomicBoolean result = new AtomicBoolean(false);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Platform.runLater(() -> {
|
||||
result.set(property.get());
|
||||
latch.countDown();
|
||||
});
|
||||
latch.await(1, TimeUnit.SECONDS);
|
||||
Assertions.assertEquals(true, result.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,12 +3,14 @@
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
package org.cryptomator.common.keychain;
|
||||
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
class MapKeychainAccess implements KeychainAccessStrategy {
|
||||
class MapKeychainAccess implements KeychainAccessProvider {
|
||||
|
||||
private final Map<String, char[]> map = new HashMap<>();
|
||||
|
||||
@@ -31,6 +33,12 @@ class MapKeychainAccess implements KeychainAccessStrategy {
|
||||
map.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changePassphrase(String key, CharSequence passphrase) {
|
||||
map.get(key);
|
||||
storePassphrase(key, passphrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return true;
|
||||
@@ -37,10 +37,10 @@ public class SettingsJsonAdapterTest {
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "fromJson() should throw IOException for input: {0}")
|
||||
@ValueSource(strings = {
|
||||
"",
|
||||
"<html>",
|
||||
"{invalidjson}"
|
||||
@ValueSource(strings = { //
|
||||
"", //
|
||||
"<html>", //
|
||||
"{invalidjson}" //
|
||||
})
|
||||
public void testDeserializeMalformed(String input) {
|
||||
Assertions.assertThrows(IOException.class, () -> {
|
||||
|
||||
@@ -29,7 +29,7 @@ public class SettingsTest {
|
||||
Mockito.verify(changeListener, Mockito.times(2)).accept(settings);
|
||||
|
||||
// third change (to property of list item):
|
||||
vaultSettings.mountName().set("asd");
|
||||
vaultSettings.displayName().set("asd");
|
||||
Mockito.verify(changeListener, Mockito.times(3)).accept(settings);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class VaultSettingsJsonAdapterTest {
|
||||
|
||||
@@ -24,15 +23,15 @@ public class VaultSettingsJsonAdapterTest {
|
||||
|
||||
@Test
|
||||
public void testDeserialize() throws IOException {
|
||||
String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":\"--foo --bar\"}";
|
||||
String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"displayName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":\"--foo --bar\"}";
|
||||
JsonReader jsonReader = new JsonReader(new StringReader(json));
|
||||
|
||||
VaultSettings vaultSettings = adapter.read(jsonReader);
|
||||
Assertions.assertEquals("foo", vaultSettings.getId());
|
||||
Assertions.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
||||
Assertions.assertEquals("test", vaultSettings.mountName().get());
|
||||
Assertions.assertEquals("test", vaultSettings.displayName().get());
|
||||
Assertions.assertEquals("X", vaultSettings.winDriveLetter().get());
|
||||
Assertions.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get());
|
||||
Assertions.assertEquals("/home/test/crypto", vaultSettings.customMountPath().get());
|
||||
Assertions.assertEquals("--foo --bar", vaultSettings.mountFlags().get());
|
||||
|
||||
|
||||
@@ -42,7 +41,7 @@ public class VaultSettingsJsonAdapterTest {
|
||||
public void testSerialize() throws IOException {
|
||||
VaultSettings vaultSettings = new VaultSettings("test");
|
||||
vaultSettings.path().set(Paths.get("/foo/bar"));
|
||||
vaultSettings.mountName().set("mountyMcMountFace");
|
||||
vaultSettings.displayName().set("mountyMcMountFace");
|
||||
vaultSettings.mountFlags().set("--foo --bar");
|
||||
|
||||
StringWriter buf = new StringWriter();
|
||||
@@ -51,12 +50,12 @@ public class VaultSettingsJsonAdapterTest {
|
||||
String result = buf.toString();
|
||||
|
||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"id\":\"test\""));
|
||||
if(System.getProperty("os.name").contains("Windows")){
|
||||
if (System.getProperty("os.name").contains("Windows")) {
|
||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"\\\\foo\\\\bar\""));
|
||||
} else {
|
||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"/foo/bar\""));
|
||||
}
|
||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountName\":\"mountyMcMountFace\""));
|
||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"displayName\":\"mountyMcMountFace\""));
|
||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountFlags\":\"--foo --bar\""));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,19 +8,19 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class VaultSettingsTest {
|
||||
|
||||
@Test
|
||||
public void testNormalize() throws Exception {
|
||||
assertEquals("_", VaultSettings.normalizeMountName(" "));
|
||||
assertEquals("a", VaultSettings.normalizeMountName("ä"));
|
||||
assertEquals("C", VaultSettings.normalizeMountName("Ĉ"));
|
||||
assertEquals("_", VaultSettings.normalizeMountName(":"));
|
||||
assertEquals("_", VaultSettings.normalizeMountName("汉语"));
|
||||
@ParameterizedTest
|
||||
@CsvSource({"a\u000Fa,a_a", ": \\,_ _", "汉语,汉语", "..,_", "a\ta,a\u0020a", "\t\n\r,_"})
|
||||
public void testNormalize(String test, String expected) {
|
||||
VaultSettings settings = new VaultSettings("id");
|
||||
settings.displayName().setValue(test);
|
||||
assertEquals(expected, settings.normalizeDisplayName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
@@ -17,6 +13,11 @@ import org.junit.jupiter.api.condition.OS;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class VaultModuleTest {
|
||||
@@ -28,8 +29,9 @@ public class VaultModuleTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setup(@TempDir Path tmpDir) {
|
||||
Mockito.when(vaultSettings.mountName()).thenReturn(new SimpleStringProperty("TEST"));
|
||||
Mockito.when(vaultSettings.mountName()).thenReturn(Bindings.createStringBinding(() -> "TEST"));
|
||||
Mockito.when(vaultSettings.usesReadOnlyMode()).thenReturn(new SimpleBooleanProperty(true));
|
||||
Mockito.when(vaultSettings.displayName()).thenReturn(new SimpleStringProperty("Vault"));
|
||||
System.setProperty("user.home", tmpDir.toString());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +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.5.2</version>
|
||||
</parent>
|
||||
<artifactId>keychain</artifactId>
|
||||
<name>System Keychain Access</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Google -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DI -->
|
||||
<dependency>
|
||||
<groupId>com.google.dagger</groupId>
|
||||
<artifactId>dagger</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- secret-service lib -->
|
||||
<dependency>
|
||||
<groupId>de.swiesend</groupId>
|
||||
<artifactId>secret-service</artifactId>
|
||||
<version>1.0.0-RC.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,31 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
public interface KeychainAccess {
|
||||
|
||||
/**
|
||||
* Associates a passphrase with a given key.
|
||||
*
|
||||
* @param key Key used to retrieve the passphrase via {@link #loadPassphrase(String)}.
|
||||
* @param passphrase The secret to store in this keychain.
|
||||
*/
|
||||
void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;
|
||||
|
||||
/**
|
||||
* @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
|
||||
* @return The stored passphrase for the given key or <code>null</code> if no value for the given key could be found.
|
||||
*/
|
||||
char[] loadPassphrase(String key) throws KeychainAccessException;
|
||||
|
||||
/**
|
||||
* Deletes a passphrase with a given key.
|
||||
*
|
||||
* @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
|
||||
*/
|
||||
void deletePassphrase(String key) throws KeychainAccessException;
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
/**
|
||||
* Indicates an error during communication with the operating system's keychain.
|
||||
*/
|
||||
public class KeychainAccessException extends Exception {
|
||||
|
||||
KeychainAccessException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
interface KeychainAccessStrategy extends KeychainAccess {
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if this KeychainAccessStrategy works on the current machine.
|
||||
* @implNote This method must not throw any exceptions and should fail fast
|
||||
* returning <code>false</code> if it can't determine availability of the checked strategy
|
||||
*/
|
||||
boolean isSupported();
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.ElementsIntoSet;
|
||||
import org.cryptomator.common.JniModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@Module(includes = {JniModule.class})
|
||||
public class KeychainModule {
|
||||
|
||||
@Provides
|
||||
@ElementsIntoSet
|
||||
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
|
||||
return Sets.newHashSet(macKeychain, winKeychain, linKeychain);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public Optional<KeychainAccess> provideSupportedKeychain(Set<KeychainAccessStrategy> keychainAccessStrategies) {
|
||||
return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).map(KeychainAccess.class::cast).findFirst();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A facade to LinuxSecretServiceKeychainAccessImpl that doesn't depend on libraries that are unavailable on Mac and Windows.
|
||||
*/
|
||||
public class LinuxSecretServiceKeychainAccess implements KeychainAccessStrategy {
|
||||
|
||||
// the actual implementation is hidden in this delegate object which is loaded via reflection,
|
||||
// as it depends on libraries that aren't necessarily available:
|
||||
private final Optional<KeychainAccessStrategy> delegate;
|
||||
|
||||
@Inject
|
||||
public LinuxSecretServiceKeychainAccess() {
|
||||
this.delegate = constructGnomeKeyringKeychainAccess();
|
||||
}
|
||||
|
||||
private static Optional<KeychainAccessStrategy> constructGnomeKeyringKeychainAccess() {
|
||||
try {
|
||||
Class<?> clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl");
|
||||
KeychainAccessStrategy instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
|
||||
return Optional.of(instance);
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return SystemUtils.IS_OS_LINUX && delegate.map(KeychainAccessStrategy::isSupported).orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||
delegate.orElseThrow(IllegalStateException::new).storePassphrase(key, passphrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] loadPassphrase(String key) throws KeychainAccessException {
|
||||
return delegate.orElseThrow(IllegalStateException::new).loadPassphrase(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePassphrase(String key) throws KeychainAccessException {
|
||||
delegate.orElseThrow(IllegalStateException::new).deletePassphrase(key);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import org.freedesktop.secret.simple.SimpleCollection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class LinuxSecretServiceKeychainAccessImpl implements KeychainAccessStrategy {
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
try (@SuppressWarnings("unused") SimpleCollection keyring = new SimpleCollection()) {
|
||||
// seems like we're able to access the keyring.
|
||||
return true;
|
||||
} catch (IOException | RuntimeException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||
try (SimpleCollection keyring = new SimpleCollection()) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list == null) {
|
||||
keyring.createItem("Cryptomator", passphrase, createAttributes(key));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new KeychainAccessException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] loadPassphrase(String key) throws KeychainAccessException {
|
||||
try (SimpleCollection keyring = new SimpleCollection()) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list != null) {
|
||||
return keyring.getSecret(list.get(0));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new KeychainAccessException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePassphrase(String key) throws KeychainAccessException {
|
||||
try (SimpleCollection keyring = new SimpleCollection()) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list != null) {
|
||||
keyring.deleteItem(list.get(0));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new KeychainAccessException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> createAttributes(String key) {
|
||||
Map<String, String> attributes = new HashMap();
|
||||
attributes.put("Vault", key);
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.jni.MacFunctions;
|
||||
import org.cryptomator.jni.MacKeychainAccess;
|
||||
|
||||
class MacSystemKeychainAccess implements KeychainAccessStrategy {
|
||||
|
||||
private final Optional<MacFunctions> macFunctions;
|
||||
|
||||
@Inject
|
||||
public MacSystemKeychainAccess(Optional<MacFunctions> macFunctions) {
|
||||
this.macFunctions = macFunctions;
|
||||
}
|
||||
|
||||
private MacKeychainAccess keychain() {
|
||||
return macFunctions.orElseThrow(IllegalStateException::new).keychainAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePassphrase(String key, CharSequence passphrase) {
|
||||
keychain().storePassword(key, passphrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] loadPassphrase(String key) {
|
||||
return keychain().loadPassword(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return SystemUtils.IS_OS_MAC_OSX && macFunctions.isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePassphrase(String key) {
|
||||
keychain().deletePassword(key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.jni.WinDataProtection;
|
||||
import org.cryptomator.jni.WinFunctions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WindowsProtectedKeychainAccess.class);
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting() //
|
||||
.registerTypeHierarchyAdapter(byte[].class, new ByteArrayJsonAdapter()) //
|
||||
.disableHtmlEscaping().create();
|
||||
|
||||
private final Optional<WinFunctions> winFunctions;
|
||||
private final List<Path> keychainPaths;
|
||||
private Map<String, KeychainEntry> keychainEntries;
|
||||
|
||||
@Inject
|
||||
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions, Environment environment) {
|
||||
this.winFunctions = winFunctions;
|
||||
this.keychainPaths = environment.getKeychainPath().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private WinDataProtection dataProtection() {
|
||||
return winFunctions.orElseThrow(IllegalStateException::new).dataProtection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePassphrase(String key, CharSequence passphrase) {
|
||||
loadKeychainEntriesIfNeeded();
|
||||
ByteBuffer buf = UTF_8.encode(CharBuffer.wrap(passphrase));
|
||||
byte[] cleartext = new byte[buf.remaining()];
|
||||
buf.get(cleartext);
|
||||
KeychainEntry entry = new KeychainEntry();
|
||||
entry.salt = generateSalt();
|
||||
entry.ciphertext = dataProtection().protect(cleartext, entry.salt);
|
||||
Arrays.fill(buf.array(), (byte) 0x00);
|
||||
Arrays.fill(cleartext, (byte) 0x00);
|
||||
keychainEntries.put(key, entry);
|
||||
saveKeychainEntries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] loadPassphrase(String key) {
|
||||
loadKeychainEntriesIfNeeded();
|
||||
KeychainEntry entry = keychainEntries.get(key);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] cleartext = dataProtection().unprotect(entry.ciphertext, entry.salt);
|
||||
if (cleartext == null) {
|
||||
return null;
|
||||
}
|
||||
CharBuffer buf = UTF_8.decode(ByteBuffer.wrap(cleartext));
|
||||
char[] passphrase = new char[buf.remaining()];
|
||||
buf.get(passphrase);
|
||||
Arrays.fill(cleartext, (byte) 0x00);
|
||||
Arrays.fill(buf.array(), (char) 0x00);
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePassphrase(String key) {
|
||||
loadKeychainEntriesIfNeeded();
|
||||
keychainEntries.remove(key);
|
||||
saveKeychainEntries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && !keychainPaths.isEmpty();
|
||||
}
|
||||
|
||||
private byte[] generateSalt() {
|
||||
byte[] result = new byte[2 * Long.BYTES];
|
||||
UUID uuid = UUID.randomUUID();
|
||||
ByteBuffer buf = ByteBuffer.wrap(result);
|
||||
buf.putLong(uuid.getMostSignificantBits());
|
||||
buf.putLong(uuid.getLeastSignificantBits());
|
||||
return result;
|
||||
}
|
||||
|
||||
private void loadKeychainEntriesIfNeeded() {
|
||||
if (keychainEntries == null) {
|
||||
for (Path keychainPath : keychainPaths) {
|
||||
Optional<Map<String, KeychainEntry>> keychain = loadKeychainEntries(keychainPath);
|
||||
if (keychain.isPresent()) {
|
||||
keychainEntries = keychain.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (keychainEntries == null) {
|
||||
LOG.info("Unable to load existing keychain file, creating new keychain.");
|
||||
keychainEntries = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Map<String, KeychainEntry>> loadKeychainEntries(Path keychainPath) {
|
||||
LOG.debug("Attempting to load keychain from {}", keychainPath);
|
||||
Type type = new TypeToken<Map<String, KeychainEntry>>() {
|
||||
}.getType();
|
||||
try (InputStream in = Files.newInputStream(keychainPath, StandardOpenOption.READ); //
|
||||
Reader reader = new InputStreamReader(in, UTF_8)) {
|
||||
return Optional.of(GSON.fromJson(reader, type));
|
||||
} catch (NoSuchFileException | JsonParseException e) {
|
||||
return Optional.empty();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveKeychainEntries() {
|
||||
if (keychainPaths.isEmpty()) {
|
||||
throw new IllegalStateException("Can't save keychain if no keychain path is specified.");
|
||||
}
|
||||
saveKeychainEntries(keychainPaths.get(0));
|
||||
}
|
||||
|
||||
private void saveKeychainEntries(Path keychainPath) {
|
||||
try (OutputStream out = Files.newOutputStream(keychainPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
|
||||
Writer writer = new OutputStreamWriter(out, UTF_8)) {
|
||||
GSON.toJson(keychainEntries, writer);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class KeychainEntry {
|
||||
|
||||
@SerializedName("ciphertext")
|
||||
byte[] ciphertext;
|
||||
@SerializedName("salt")
|
||||
byte[] salt;
|
||||
}
|
||||
|
||||
private static class ByteArrayJsonAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
|
||||
|
||||
private static final BaseEncoding BASE64 = BaseEncoding.base64();
|
||||
|
||||
@Override
|
||||
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
return BASE64.decode(json.getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(BASE64.encode(src));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class KeychainModuleTest {
|
||||
|
||||
@Test
|
||||
public void testGetKeychain() throws KeychainAccessException {
|
||||
Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess();
|
||||
Assertions.assertTrue(keychainAccess.isPresent());
|
||||
Assertions.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
|
||||
keychainAccess.get().storePassphrase("test", "asd");
|
||||
Assertions.assertArrayEquals("asd".toCharArray(), keychainAccess.get().loadPassphrase("test"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = KeychainModule.class)
|
||||
interface TestKeychainComponent {
|
||||
|
||||
Optional<KeychainAccess> keychainAccess();
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class TestKeychainModule extends KeychainModule {
|
||||
|
||||
@Override
|
||||
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
|
||||
return Set.of(new MapKeychainAccess());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.jni.WinDataProtection;
|
||||
import org.cryptomator.jni.WinFunctions;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class WindowsProtectedKeychainAccessTest {
|
||||
|
||||
private WindowsProtectedKeychainAccess keychain;
|
||||
|
||||
@BeforeEach
|
||||
public void setup(@TempDir Path tempDir) {
|
||||
Path keychainPath = tempDir.resolve("keychainfile.tmp");
|
||||
Environment env = Mockito.mock(Environment.class);
|
||||
Mockito.when(env.getKeychainPath()).thenReturn(Stream.of(keychainPath));
|
||||
WinFunctions winFunctions = Mockito.mock(WinFunctions.class);
|
||||
WinDataProtection winDataProtection = Mockito.mock(WinDataProtection.class);
|
||||
Mockito.when(winFunctions.dataProtection()).thenReturn(winDataProtection);
|
||||
Answer<byte[]> answerReturningFirstArg = invocation -> ((byte[]) invocation.getArgument(0)).clone();
|
||||
Mockito.when(winDataProtection.protect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
|
||||
Mockito.when(winDataProtection.unprotect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
|
||||
keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions), env);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreAndLoad() {
|
||||
String storedPw1 = "topSecret";
|
||||
String storedPw2 = "bottomSecret";
|
||||
keychain.storePassphrase("myPassword", storedPw1);
|
||||
keychain.storePassphrase("myOtherPassword", storedPw2);
|
||||
String loadedPw1 = new String(keychain.loadPassphrase("myPassword"));
|
||||
String loadedPw2 = new String(keychain.loadPassphrase("myOtherPassword"));
|
||||
Assertions.assertEquals(storedPw1, loadedPw1);
|
||||
Assertions.assertEquals(storedPw2, loadedPw2);
|
||||
keychain.deletePassphrase("myPassword");
|
||||
Assertions.assertNull(keychain.loadPassphrase("myPassword"));
|
||||
Assertions.assertNotNull(keychain.loadPassphrase("myOtherPassword"));
|
||||
Assertions.assertNull(keychain.loadPassphrase("nonExistingPassword"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
Copyright (c) 2014 Markus Kreusch
|
||||
This file is licensed under the terms of the MIT license.
|
||||
See the LICENSE.txt file for more info.
|
||||
|
||||
Contributors:
|
||||
Sebastian Stenzel - log4j config for WebDAV unit tests
|
||||
-->
|
||||
<Configuration status="WARN">
|
||||
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
|
||||
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
|
||||
</Console>
|
||||
<Console name="StdErr" target="SYSTEM_ERR">
|
||||
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
|
||||
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
|
||||
</Console>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<!-- show our own debug messages: -->
|
||||
<Logger name="org.cryptomator" level="DEBUG" />
|
||||
<!-- mute dependencies: -->
|
||||
<Root level="INFO">
|
||||
<AppenderRef ref="Console" />
|
||||
<AppenderRef ref="StdErr" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
|
||||
</Configuration>
|
||||
@@ -1,10 +1,10 @@
|
||||
<?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">
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" 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.2</version>
|
||||
<version>1.5.9</version>
|
||||
</parent>
|
||||
<artifactId>launcher</artifactId>
|
||||
<name>Cryptomator Launcher</name>
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@Module
|
||||
class CryptomatorModule {
|
||||
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("shutdownLatch")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved.
|
||||
*
|
||||
*
|
||||
* This class is licensed under the LGPL 3.0 (https://www.gnu.org/licenses/lgpl-3.0.de.html).
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
@@ -25,7 +25,6 @@ import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Singleton
|
||||
class FileOpenRequestHandler {
|
||||
|
||||
@@ -10,7 +10,6 @@ import javax.inject.Singleton;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Singleton
|
||||
class IpcProtocolImpl implements IpcProtocol {
|
||||
|
||||
@@ -8,12 +8,12 @@ package org.cryptomator.logging;
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.cryptomator.logging;
|
||||
|
||||
import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
|
||||
import ch.qos.logback.core.rolling.TriggeringPolicyBase;
|
||||
import ch.qos.logback.core.util.FileSize;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Triggers a roll-over either on the first log event or if watched log file reaches a certain size
|
||||
*
|
||||
* @param <E> Event type the policy possibly reacts to
|
||||
*/
|
||||
public class LaunchAndSizeBasedTriggerinPolicy<E> extends TriggeringPolicyBase<E> {
|
||||
|
||||
LaunchBasedTriggeringPolicy<E> launchBasedTriggeringPolicy;
|
||||
SizeBasedTriggeringPolicy<E> sizeBasedTriggeringPolicy;
|
||||
|
||||
public LaunchAndSizeBasedTriggerinPolicy(FileSize threshold) {
|
||||
this.launchBasedTriggeringPolicy = new LaunchBasedTriggeringPolicy<>();
|
||||
this.sizeBasedTriggeringPolicy = new SizeBasedTriggeringPolicy<>();
|
||||
sizeBasedTriggeringPolicy.setMaxFileSize(threshold);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTriggeringEvent(File activeFile, E event) {
|
||||
return launchBasedTriggeringPolicy.isTriggeringEvent(activeFile, event) || sizeBasedTriggeringPolicy.isTriggeringEvent(activeFile, event);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,11 +5,11 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.logging;
|
||||
|
||||
import ch.qos.logback.core.rolling.TriggeringPolicyBase;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import ch.qos.logback.core.rolling.TriggeringPolicyBase;
|
||||
|
||||
/**
|
||||
* Triggers a roll-over on the first log event, so each launched application instance will rotate the log.
|
||||
*/
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class LoggerConfiguration {
|
||||
|
||||
|
||||
private final LoggerContext context;
|
||||
private final Environment environment;
|
||||
private final Appender<ILoggingEvent> stdout;
|
||||
|
||||
@@ -9,10 +9,9 @@ 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 ch.qos.logback.core.util.FileSize;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.Environment;
|
||||
@@ -33,6 +32,8 @@ public class LoggerModule {
|
||||
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";
|
||||
private static final String LOG_MAX_SIZE = "100mb";
|
||||
|
||||
static final Map<String, Level> DEFAULT_LOG_LEVELS = Map.of( //
|
||||
Logger.ROOT_LOGGER_NAME, Level.INFO, //
|
||||
"org.cryptomator", Level.INFO //
|
||||
@@ -84,7 +85,7 @@ public class LoggerModule {
|
||||
appender.setContext(context);
|
||||
appender.setFile(logDir.resolve(LOGFILE_NAME).toString());
|
||||
appender.setEncoder(encoder);
|
||||
LaunchBasedTriggeringPolicy triggeringPolicy = new LaunchBasedTriggeringPolicy();
|
||||
LaunchAndSizeBasedTriggerinPolicy triggeringPolicy = new LaunchAndSizeBasedTriggerinPolicy(FileSize.valueOf(LOG_MAX_SIZE));
|
||||
triggeringPolicy.setContext(context);
|
||||
triggeringPolicy.start();
|
||||
appender.setTriggeringPolicy(triggeringPolicy);
|
||||
|
||||
@@ -62,8 +62,8 @@ public class IpcFactoryTest {
|
||||
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"});
|
||||
endpoint2.getRemote().handleLaunchArgs(new String[]{"foo"});
|
||||
Mockito.verify(protocolHandler).handleLaunchArgs(new String[]{"foo"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
152
main/pom.xml
152
main/pom.xml
@@ -1,9 +1,9 @@
|
||||
<?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">
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" 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>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.5.9</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
@@ -24,26 +24,29 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptofs.version>1.9.9</cryptomator.cryptofs.version>
|
||||
<cryptomator.jni.version>2.2.2</cryptomator.jni.version>
|
||||
<cryptomator.fuse.version>1.2.3</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.1.14</cryptomator.dokany.version>
|
||||
<cryptomator.webdav.version>1.0.11</cryptomator.webdav.version>
|
||||
<cryptomator.cryptofs.version>1.9.13</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>0.1.6</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>0.1.0-beta1</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>0.1.0-beta3</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>0.1.0-beta2</cryptomator.integrations.linux.version>
|
||||
<cryptomator.fuse.version>1.2.5</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.2.0</cryptomator.dokany.version>
|
||||
<cryptomator.webdav.version>1.0.13</cryptomator.webdav.version>
|
||||
|
||||
<!-- 3rd party dependencies -->
|
||||
<javafx.version>14</javafx.version>
|
||||
<commons-lang3.version>3.9</commons-lang3.version>
|
||||
<jwt.version>3.10.2</jwt.version>
|
||||
<easybind.version>1.0.3</easybind.version>
|
||||
<guava.version>28.2-jre</guava.version>
|
||||
<dagger.version>2.27</dagger.version>
|
||||
<javafx.version>15</javafx.version>
|
||||
<commons-lang3.version>3.11</commons-lang3.version>
|
||||
<jwt.version>3.11.0</jwt.version>
|
||||
<easybind.version>2.1.0</easybind.version>
|
||||
<guava.version>30.0-jre</guava.version>
|
||||
<dagger.version>2.29.1</dagger.version>
|
||||
<gson.version>2.8.6</gson.version>
|
||||
<slf4j.version>1.7.30</slf4j.version>
|
||||
<logback.version>1.2.3</logback.version>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<junit.jupiter.version>5.6.1</junit.jupiter.version>
|
||||
<mockito.version>3.3.3</mockito.version>
|
||||
<junit.jupiter.version>5.7.0</junit.jupiter.version>
|
||||
<mockito.version>3.6.0</mockito.version>
|
||||
<hamcrest.version>2.2</hamcrest.version>
|
||||
</properties>
|
||||
|
||||
@@ -62,11 +65,6 @@
|
||||
<artifactId>commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>keychain</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>ui</artifactId>
|
||||
@@ -101,8 +99,23 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>jni</artifactId>
|
||||
<version>${cryptomator.jni.version}</version>
|
||||
<artifactId>integrations-api</artifactId>
|
||||
<version>${cryptomator.integrations.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>integrations-win</artifactId>
|
||||
<version>${cryptomator.integrations.win.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>integrations-mac</artifactId>
|
||||
<version>${cryptomator.integrations.mac.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>integrations-linux</artifactId>
|
||||
<version>${cryptomator.integrations.linux.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JavaFX -->
|
||||
@@ -155,7 +168,7 @@
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
@@ -165,7 +178,7 @@
|
||||
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
<groupId>com.tobiasdiez</groupId>
|
||||
<artifactId>easybind</artifactId>
|
||||
<version>${easybind.version}</version>
|
||||
</dependency>
|
||||
@@ -222,6 +235,7 @@
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
@@ -237,7 +251,6 @@
|
||||
|
||||
<modules>
|
||||
<module>commons</module>
|
||||
<module>keychain</module>
|
||||
<module>ui</module>
|
||||
<module>launcher</module>
|
||||
</modules>
|
||||
@@ -260,26 +273,87 @@
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>mac</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>mac</family>
|
||||
</os>
|
||||
<property>
|
||||
<name>idea.version</name>
|
||||
</property>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>integrations-mac</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>linux</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>unix</family>
|
||||
<name>Linux</name>
|
||||
</os>
|
||||
<property>
|
||||
<name>idea.version</name>
|
||||
</property>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>integrations-linux</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>windows</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>windows</family>
|
||||
</os>
|
||||
<property>
|
||||
<name>idea.version</name>
|
||||
</property>
|
||||
</activation>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>integrations-win</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-libs</id>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||
<includeScope>runtime</includeScope>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
@@ -302,7 +376,7 @@
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.5</version>
|
||||
<version>0.8.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
@@ -329,7 +403,6 @@
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<release>14</release>
|
||||
<annotationProcessorPaths>
|
||||
@@ -344,7 +417,6 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
<?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">
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" 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.2</version>
|
||||
<version>1.5.9</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>keychain</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>jni</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- JavaFx -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
@@ -35,7 +27,7 @@
|
||||
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
<groupId>com.tobiasdiez</groupId>
|
||||
<artifactId>easybind</artifactId>
|
||||
</dependency>
|
||||
|
||||
@@ -67,14 +59,14 @@
|
||||
<artifactId>zxcvbn</artifactId>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Testing -->
|
||||
<dependency>
|
||||
<groupId>com.google.jimfs</groupId>
|
||||
@@ -88,7 +80,7 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
@@ -20,16 +20,16 @@ along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
Cryptomator uses ${dependencyMap?size} third-party dependencies under the following licenses:
|
||||
<#list licenseMap as e>
|
||||
<#assign license = e.getKey()/>
|
||||
<#assign projects = e.getValue()/>
|
||||
<#if projects?size > 0>
|
||||
${license}:
|
||||
<#list projects as project>
|
||||
- ${artifactFormat(project)}
|
||||
</#list>
|
||||
</#if>
|
||||
<#assign license = e.getKey()/>
|
||||
<#assign projects = e.getValue()/>
|
||||
<#if projects?size > 0>
|
||||
${license}:
|
||||
<#list projects as project>
|
||||
- ${artifactFormat(project)}
|
||||
</#list>
|
||||
</#if>
|
||||
</#list>
|
||||
|
||||
Cryptomator uses other third-party assets under the following licenses:
|
||||
SIL OFL 1.1 License:
|
||||
- Font Awesome 5.12.0 (https://fontawesome.com/)
|
||||
SIL OFL 1.1 License:
|
||||
- Font Awesome 5.12.0 (https://fontawesome.com/)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user