mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 16:04:28 +00:00
Compare commits
304
Commits
1.5.3
..
1.5.9-beta2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
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 |
@@ -5,6 +5,7 @@ labels: type:bug
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
⚠️⚠️⚠️ READ CAREFULLY ⚠️⚠️⚠️
|
||||||
|
|
||||||
Do you want to ask a QUESTION? Are you looking for SUPPORT?
|
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
|
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=
|
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
|
### 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]
|
* Operating system and version: [Windows/macOS/Linux + Version]
|
||||||
* Cryptomator version: [Shown in the settings]
|
* Cryptomator version: [Shown in the settings]
|
||||||
* Drive: [Dokany/FUSE/WebDAV]
|
* Volume type: [Dokany/FUSE/WebDAV, shown in the settings]
|
||||||
|
|
||||||
### Steps to Reproduce
|
### Steps to Reproduce
|
||||||
|
|
||||||
@@ -58,4 +61,4 @@ Log file location:
|
|||||||
- macOS: ~/Library/Logs/Cryptomator
|
- macOS: ~/Library/Logs/Cryptomator
|
||||||
- Linux: ~/.local/share/Cryptomator/logs
|
- Linux: ~/.local/share/Cryptomator/logs
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
name: Build and Test
|
name: Build and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
@@ -27,24 +28,27 @@ jobs:
|
|||||||
run: |
|
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
|
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/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/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 report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
|
||||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
|
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
|
||||||
env:
|
env:
|
||||||
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
||||||
- name: Assemble Buildkit
|
- name: Assemble buildkit-linux.zip
|
||||||
run: mvn -B package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease
|
run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,linux
|
||||||
- name: Upload buildkit-linux.zip
|
- name: Upload buildkit-linux.zip
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: buildkit-linux.zip
|
name: buildkit-linux.zip
|
||||||
path: main/buildkit/target/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
|
- name: Upload buildkit-mac.zip
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: buildkit-mac.zip
|
name: buildkit-mac.zip
|
||||||
path: main/buildkit/target/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
|
- name: Upload buildkit-win.zip
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -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._
|
||||||
@@ -18,5 +18,8 @@ pom.xml.versionsBackup
|
|||||||
.idea/**/workspace.xml
|
.idea/**/workspace.xml
|
||||||
.idea/**/tasks.xml
|
.idea/**/tasks.xml
|
||||||
.idea/dictionaries
|
.idea/dictionaries
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/encodings.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
.idea/**/libraries/
|
.idea/**/libraries/
|
||||||
*.iml
|
*.iml
|
||||||
Generated
+28
-1
@@ -9,14 +9,36 @@
|
|||||||
<option name="RIGHT_MARGIN" value="220" />
|
<option name="RIGHT_MARGIN" value="220" />
|
||||||
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
||||||
<JavaCodeStyleSettings>
|
<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="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="10" />
|
||||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||||
<value />
|
<value />
|
||||||
</option>
|
</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_PARAM_COMMENTS" value="false" />
|
||||||
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
|
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
|
||||||
</JavaCodeStyleSettings>
|
</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">
|
<codeStyleSettings language="Groovy">
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="USE_TAB_CHARACTER" value="true" />
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
@@ -30,12 +52,17 @@
|
|||||||
<codeStyleSettings language="JAVA">
|
<codeStyleSettings language="JAVA">
|
||||||
<option name="KEEP_LINE_BREAKS" value="false" />
|
<option name="KEEP_LINE_BREAKS" value="false" />
|
||||||
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
|
<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_BLOCKS_IN_ONE_LINE" value="true" />
|
||||||
<option name="KEEP_SIMPLE_METHODS_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="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="ENUM_CONSTANTS_WRAP" value="2" />
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="USE_TAB_CHARACTER" value="true" />
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
</indentOptions>
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules />
|
||||||
|
</arrangement>
|
||||||
</codeStyleSettings>
|
</codeStyleSettings>
|
||||||
<codeStyleSettings language="JSON">
|
<codeStyleSettings language="JSON">
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
|
|||||||
Generated
-41
@@ -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>
|
|
||||||
Generated
-13
@@ -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>
|
|
||||||
Generated
-30
@@ -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
@@ -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>
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
|
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
|
||||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
<module name="launcher" />
|
<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">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+10
@@ -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
@@ -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
|
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.
|
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"?>
|
<?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">
|
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||||
<id>tarball</id>
|
<id>tarball</id>
|
||||||
<includeBaseDirectory>false</includeBaseDirectory>
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
@@ -14,13 +14,6 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>target/</directory>
|
|
||||||
<includes>
|
|
||||||
<include>ffi-version.txt</include>
|
|
||||||
</includes>
|
|
||||||
<outputDirectory>libs</outputDirectory>
|
|
||||||
</fileSet>
|
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target/</directory>
|
<directory>target/</directory>
|
||||||
<includes>
|
<includes>
|
||||||
@@ -43,12 +36,5 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>target/linux-libs</directory>
|
|
||||||
<includes>
|
|
||||||
<include>*.jar</include>
|
|
||||||
</includes>
|
|
||||||
<outputDirectory>libs</outputDirectory>
|
|
||||||
</fileSet>
|
|
||||||
</fileSets>
|
</fileSets>
|
||||||
</assembly>
|
</assembly>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||||
<id>tarball</id>
|
<id>tarball</id>
|
||||||
<includeBaseDirectory>false</includeBaseDirectory>
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
@@ -14,13 +14,6 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>target/</directory>
|
|
||||||
<includes>
|
|
||||||
<include>ffi-version.txt</include>
|
|
||||||
</includes>
|
|
||||||
<outputDirectory>libs</outputDirectory>
|
|
||||||
</fileSet>
|
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target/</directory>
|
<directory>target/</directory>
|
||||||
<includes>
|
<includes>
|
||||||
@@ -43,12 +36,5 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>target/mac-libs</directory>
|
|
||||||
<includes>
|
|
||||||
<include>*.jar</include>
|
|
||||||
</includes>
|
|
||||||
<outputDirectory>libs</outputDirectory>
|
|
||||||
</fileSet>
|
|
||||||
</fileSets>
|
</fileSets>
|
||||||
</assembly>
|
</assembly>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||||
<id>tarball</id>
|
<id>tarball</id>
|
||||||
<includeBaseDirectory>false</includeBaseDirectory>
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
@@ -14,13 +14,6 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>target/</directory>
|
|
||||||
<includes>
|
|
||||||
<include>ffi-version.txt</include>
|
|
||||||
</includes>
|
|
||||||
<outputDirectory>libs</outputDirectory>
|
|
||||||
</fileSet>
|
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target/</directory>
|
<directory>target/</directory>
|
||||||
<includes>
|
<includes>
|
||||||
@@ -43,12 +36,5 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>target/win-libs</directory>
|
|
||||||
<includes>
|
|
||||||
<include>*.jar</include>
|
|
||||||
</includes>
|
|
||||||
<outputDirectory>libs</outputDirectory>
|
|
||||||
</fileSet>
|
|
||||||
</fileSets>
|
</fileSets>
|
||||||
</assembly>
|
</assembly>
|
||||||
+144
-102
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.3</version>
|
<version>1.5.9</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>buildkit</artifactId>
|
<artifactId>buildkit</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-resources-plugin</artifactId>
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
<version>3.1.0</version>
|
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>copy-resources</id>
|
<id>copy-resources</id>
|
||||||
@@ -55,8 +54,8 @@
|
|||||||
|
|
||||||
<!-- copy libraries to target/libs/: -->
|
<!-- copy libraries to target/libs/: -->
|
||||||
<plugin>
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-dependency-plugin</artifactId>
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
<version>3.1.1</version>
|
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>copy-libs</id>
|
<id>copy-libs</id>
|
||||||
@@ -65,110 +64,153 @@
|
|||||||
<goal>copy-dependencies</goal>
|
<goal>copy-dependencies</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
<includeScope>runtime</includeScope>
|
||||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||||
<excludeClassifiers>linux,mac,win</excludeClassifiers>
|
<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>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</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>
|
</project>
|
||||||
@@ -1 +0,0 @@
|
|||||||
${cryptomator.jni.version}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
cd $(dirname $0)
|
||||||
java \
|
java \
|
||||||
-cp "libs/*" \
|
-cp "libs/*" \
|
||||||
-Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" \
|
-Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" \
|
||||||
@@ -6,6 +7,6 @@ java \
|
|||||||
-Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \
|
-Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \
|
||||||
-Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \
|
-Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \
|
||||||
-Djdk.gtk.version=2 \
|
-Djdk.gtk.version=2 \
|
||||||
-Xss20m \
|
-Xss2m \
|
||||||
-Xmx512m \
|
-Xmx512m \
|
||||||
org.cryptomator.launcher.Cryptomator
|
org.cryptomator.launcher.Cryptomator
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
cd $(dirname $0)
|
||||||
java \
|
java \
|
||||||
-cp "libs/*" \
|
-cp "libs/*" \
|
||||||
-Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" \
|
-Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" \
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ java ^
|
|||||||
-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" ^
|
-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" ^
|
||||||
-Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" ^
|
-Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" ^
|
||||||
-Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" ^
|
-Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" ^
|
||||||
|
-Dcryptomator.mountPointsDir="~/Cryptomator" ^
|
||||||
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
|
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
|
||||||
-Xss20m ^
|
-Xss20m ^
|
||||||
-Xmx512m ^
|
-Xmx512m ^
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.3</version>
|
<version>1.5.9</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
<name>Cryptomator Commons</name>
|
<name>Cryptomator Commons</name>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>jni</artifactId>
|
<artifactId>integrations-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JavaFx -->
|
<!-- JavaFx -->
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<!-- EasyBind -->
|
<!-- EasyBind -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.fxmisc.easybind</groupId>
|
<groupId>com.tobiasdiez</groupId>
|
||||||
<artifactId>easybind</artifactId>
|
<artifactId>easybind</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -5,24 +5,25 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common;
|
package org.cryptomator.common;
|
||||||
|
|
||||||
|
import com.tobiasdiez.easybind.EasyBind;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
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.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.keychain.KeychainModule;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.SettingsProvider;
|
import org.cryptomator.common.settings.SettingsProvider;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultComponent;
|
import org.cryptomator.common.vaults.VaultComponent;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import javafx.beans.binding.Binding;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@@ -33,7 +34,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@Module(subcomponents = {VaultComponent.class})
|
@Module(subcomponents = {VaultComponent.class}, includes = {KeychainModule.class})
|
||||||
public abstract class CommonsModule {
|
public abstract class CommonsModule {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
|
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.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
||||||
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
|
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
|
||||||
LOG.debug("cryptomator.buildNumber: {}", System.getProperty("cryptomator.buildNumber"));
|
LOG.debug("cryptomator.buildNumber: {}", System.getProperty("cryptomator.buildNumber"));
|
||||||
|
LOG.debug("fuse.experimental: {}", Boolean.getBoolean("fuse.experimental"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean useCustomLogbackConfig() {
|
public boolean useCustomLogbackConfig() {
|
||||||
@@ -74,6 +75,10 @@ public class Environment {
|
|||||||
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
|
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean useExperimentalFuse() {
|
||||||
|
return Boolean.getBoolean("fuse.experimental");
|
||||||
|
}
|
||||||
|
|
||||||
private int getInt(String propertyName, int defaultValue) {
|
private int getInt(String propertyName, int defaultValue) {
|
||||||
String value = System.getProperty(propertyName);
|
String value = System.getProperty(propertyName);
|
||||||
try {
|
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;
|
package org.cryptomator.common;
|
||||||
|
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
|
||||||
|
|
||||||
public final class LazyInitializer {
|
public final class LazyInitializer {
|
||||||
|
|
||||||
private 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.
|
* 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 <T> Type of the value
|
||||||
* @param reference A reference to a maybe not yet initialized 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.
|
* @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
|
* Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509
|
||||||
*
|
*
|
||||||
* @param <T> Type of the value
|
* @param <T> Type of the value
|
||||||
* @param <E> Type of the any expected exception that may occur during initialization
|
* @param <E> Type of the any expected exception that may occur during initialization
|
||||||
* @param reference A reference to a maybe not yet initialized value.
|
* @param reference A reference to a maybe not yet initialized value.
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
package org.cryptomator.common;
|
package org.cryptomator.common;
|
||||||
|
|
||||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
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.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.binding.StringBinding;
|
import javafx.beans.binding.StringBinding;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common;
|
package org.cryptomator.common;
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
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>.
|
* 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+13
@@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+101
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+64
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+121
@@ -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;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
@@ -20,7 +22,6 @@ import javafx.beans.property.StringProperty;
|
|||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.geometry.NodeOrientation;
|
import javafx.geometry.NodeOrientation;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class Settings {
|
public class Settings {
|
||||||
@@ -34,8 +35,9 @@ public class Settings {
|
|||||||
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||||
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
|
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
|
||||||
public static final boolean DEFAULT_DEBUG_MODE = false;
|
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 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;
|
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
|
||||||
private static final String DEFAULT_LICENSE_KEY = "";
|
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 BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
||||||
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
||||||
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
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 ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
||||||
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
|
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
|
||||||
|
|
||||||
@@ -68,6 +71,7 @@ public class Settings {
|
|||||||
debugMode.addListener(this::somethingChanged);
|
debugMode.addListener(this::somethingChanged);
|
||||||
preferredVolumeImpl.addListener(this::somethingChanged);
|
preferredVolumeImpl.addListener(this::somethingChanged);
|
||||||
theme.addListener(this::somethingChanged);
|
theme.addListener(this::somethingChanged);
|
||||||
|
keychainBackend.addListener(this::somethingChanged);
|
||||||
userInterfaceOrientation.addListener(this::somethingChanged);
|
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||||
licenseKey.addListener(this::somethingChanged);
|
licenseKey.addListener(this::somethingChanged);
|
||||||
}
|
}
|
||||||
@@ -75,7 +79,7 @@ public class Settings {
|
|||||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||||
this.saveCmd = saveCmd;
|
this.saveCmd = saveCmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void somethingChanged(@SuppressWarnings("unused") Observable observable) {
|
private void somethingChanged(@SuppressWarnings("unused") Observable observable) {
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
@@ -99,7 +103,7 @@ public class Settings {
|
|||||||
public BooleanProperty checkForUpdates() {
|
public BooleanProperty checkForUpdates() {
|
||||||
return checkForUpdates;
|
return checkForUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanProperty startHidden() {
|
public BooleanProperty startHidden() {
|
||||||
return startHidden;
|
return startHidden;
|
||||||
}
|
}
|
||||||
@@ -128,6 +132,8 @@ public class Settings {
|
|||||||
return theme;
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<KeychainBackend> keychainBackend() { return keychainBackend; }
|
||||||
|
|
||||||
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
|
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
|
||||||
return userInterfaceOrientation;
|
return userInterfaceOrientation;
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-39
@@ -9,10 +9,10 @@ import com.google.gson.TypeAdapter;
|
|||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.JsonToken;
|
import com.google.gson.stream.JsonToken;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
import javafx.geometry.NodeOrientation;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javafx.geometry.NodeOrientation;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -38,6 +38,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
||||||
out.name("theme").value(value.theme().get().name());
|
out.name("theme").value(value.theme().get().name());
|
||||||
out.name("uiOrientation").value(value.userInterfaceOrientation().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.name("licenseKey").value(value.licenseKey().get());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
@@ -58,46 +59,23 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
String name = in.nextName();
|
String name = in.nextName();
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "directories":
|
case "directories" -> settings.getDirectories().addAll(readVaultSettingsArray(in));
|
||||||
settings.getDirectories().addAll(readVaultSettingsArray(in));
|
case "askedForUpdateCheck" -> settings.askedForUpdateCheck().set(in.nextBoolean());
|
||||||
break;
|
case "checkForUpdatesEnabled" -> settings.checkForUpdates().set(in.nextBoolean());
|
||||||
case "askedForUpdateCheck":
|
case "startHidden" -> settings.startHidden().set(in.nextBoolean());
|
||||||
settings.askedForUpdateCheck().set(in.nextBoolean());
|
case "port" -> settings.port().set(in.nextInt());
|
||||||
break;
|
case "numTrayNotifications" -> settings.numTrayNotifications().set(in.nextInt());
|
||||||
case "checkForUpdatesEnabled":
|
case "preferredGvfsScheme" -> settings.preferredGvfsScheme().set(parseWebDavUrlSchemePrefix(in.nextString()));
|
||||||
settings.checkForUpdates().set(in.nextBoolean());
|
case "debugMode" -> settings.debugMode().set(in.nextBoolean());
|
||||||
break;
|
case "preferredVolumeImpl" -> settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
||||||
case "startHidden":
|
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
|
||||||
settings.startHidden().set(in.nextBoolean());
|
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||||
break;
|
case "keychainBackend" -> settings.keychainBackend().set(parseKeychainBackend(in.nextString()));
|
||||||
case "port":
|
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
||||||
settings.port().set(in.nextInt());
|
default -> {
|
||||||
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:
|
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in.endObject();
|
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) {
|
private NodeOrientation parseUiOrientation(String uiOrientationName) {
|
||||||
try {
|
try {
|
||||||
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());
|
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
public enum UiTheme {
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
LIGHT("preferences.general.theme.light"),
|
|
||||||
DARK("preferences.general.theme.dark");
|
|
||||||
// CUSTOM("Custom (%s)");
|
|
||||||
|
|
||||||
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) {
|
UiTheme(String displayName) {
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
|
|||||||
@@ -5,9 +5,13 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
@@ -16,17 +20,15 @@ import javafx.beans.property.SimpleIntegerProperty;
|
|||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The settings specific to a single vault.
|
* The settings specific to a single vault.
|
||||||
* TODO: Change the name of individualMountPath and its derivatives to customMountPath
|
|
||||||
*/
|
*/
|
||||||
public class VaultSettings {
|
public class VaultSettings {
|
||||||
|
|
||||||
@@ -36,35 +38,32 @@ public class VaultSettings {
|
|||||||
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
||||||
public static final String DEFAULT_MOUNT_FLAGS = "";
|
public static final String DEFAULT_MOUNT_FLAGS = "";
|
||||||
public static final int DEFAULT_FILENAME_LENGTH_LIMIT = -1;
|
public static final int DEFAULT_FILENAME_LENGTH_LIMIT = -1;
|
||||||
|
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
|
||||||
private static final Random RNG = new Random();
|
|
||||||
|
private static final Random RNG = new Random();
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
private final ObjectProperty<Path> path = new SimpleObjectProperty();
|
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 StringProperty winDriveLetter = new SimpleStringProperty();
|
||||||
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
||||||
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
|
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
|
||||||
private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
|
private final BooleanProperty useCustomMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
|
||||||
private final StringProperty individualMountPath = new SimpleStringProperty();
|
private final StringProperty customMountPath = new SimpleStringProperty();
|
||||||
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
||||||
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
||||||
private final IntegerProperty filenameLengthLimit = new SimpleIntegerProperty(DEFAULT_FILENAME_LENGTH_LIMIT);
|
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) {
|
public VaultSettings(String id) {
|
||||||
this.id = Objects.requireNonNull(id);
|
this.id = Objects.requireNonNull(id);
|
||||||
|
this.mountName = Bindings.createStringBinding(this::normalizeDisplayName, displayName);
|
||||||
EasyBind.subscribe(path, this::deriveMountNameFromPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Observable[] observables() {
|
Observable[] observables() {
|
||||||
return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, usesIndividualMountPath, individualMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit};
|
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit, actionAfterUnlock};
|
||||||
}
|
|
||||||
|
|
||||||
private void deriveMountNameFromPath(Path path) {
|
|
||||||
if (path != null && StringUtils.isBlank(mountName.get())) {
|
|
||||||
mountName.set(normalizeMountName(path.getFileName().toString()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VaultSettings withRandomId() {
|
public static VaultSettings withRandomId() {
|
||||||
@@ -77,23 +76,18 @@ public class VaultSettings {
|
|||||||
return BaseEncoding.base64Url().encode(randomBytes);
|
return BaseEncoding.base64Url().encode(randomBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String normalizeMountName(String mountName) {
|
//visible for testing
|
||||||
String normalizedMountName = StringUtils.stripAccents(mountName);
|
String normalizeDisplayName() {
|
||||||
StringBuilder builder = new StringBuilder();
|
var original = displayName.getValueSafe();
|
||||||
for (char c : normalizedMountName.toCharArray()) {
|
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
|
||||||
if (Character.isWhitespace(c)) {
|
return "_";
|
||||||
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('_');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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 */
|
/* Getter/Setter */
|
||||||
@@ -106,7 +100,11 @@ public class VaultSettings {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty mountName() {
|
public StringProperty displayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBinding mountName() {
|
||||||
return mountName;
|
return mountName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,6 +112,14 @@ public class VaultSettings {
|
|||||||
return winDriveLetter;
|
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() {
|
public BooleanProperty unlockAfterStartup() {
|
||||||
return unlockAfterStartup;
|
return unlockAfterStartup;
|
||||||
}
|
}
|
||||||
@@ -122,17 +128,17 @@ public class VaultSettings {
|
|||||||
return revealAfterMount;
|
return revealAfterMount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanProperty usesIndividualMountPath() {
|
public BooleanProperty useCustomMountPath() {
|
||||||
return usesIndividualMountPath;
|
return useCustomMountPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty individualMountPath() {
|
public StringProperty customMountPath() {
|
||||||
return individualMountPath;
|
return customMountPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<String> getIndividualMountPath() {
|
public Optional<String> getCustomMountPath() {
|
||||||
if (usesIndividualMountPath.get()) {
|
if (useCustomMountPath.get()) {
|
||||||
return Optional.ofNullable(Strings.emptyToNull(individualMountPath.get()));
|
return Optional.ofNullable(Strings.emptyToNull(customMountPath.get()));
|
||||||
} else {
|
} else {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
@@ -145,11 +151,19 @@ public class VaultSettings {
|
|||||||
public StringProperty mountFlags() {
|
public StringProperty mountFlags() {
|
||||||
return mountFlags;
|
return mountFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntegerProperty filenameLengthLimit() {
|
public IntegerProperty filenameLengthLimit() {
|
||||||
return filenameLengthLimit;
|
return filenameLengthLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<WhenUnlocked> actionAfterUnlock() {
|
||||||
|
return actionAfterUnlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WhenUnlocked getActionAfterUnlock() {
|
||||||
|
return actionAfterUnlock.get();
|
||||||
|
}
|
||||||
|
|
||||||
/* Hashcode/Equals */
|
/* Hashcode/Equals */
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+41
-44
@@ -21,88 +21,85 @@ class VaultSettingsJsonAdapter {
|
|||||||
out.beginObject();
|
out.beginObject();
|
||||||
out.name("id").value(value.getId());
|
out.name("id").value(value.getId());
|
||||||
out.name("path").value(value.path().get().toString());
|
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("winDriveLetter").value(value.winDriveLetter().get());
|
||||||
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
|
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
|
||||||
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
||||||
out.name("usesIndividualMountPath").value(value.usesIndividualMountPath().get());
|
out.name("useCustomMountPath").value(value.useCustomMountPath().get());
|
||||||
out.name("individualMountPath").value(value.individualMountPath().get());
|
out.name("customMountPath").value(value.customMountPath().get());
|
||||||
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
||||||
out.name("mountFlags").value(value.mountFlags().get());
|
out.name("mountFlags").value(value.mountFlags().get());
|
||||||
out.name("filenameLengthLimit").value(value.filenameLengthLimit().get());
|
out.name("filenameLengthLimit").value(value.filenameLengthLimit().get());
|
||||||
|
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
public VaultSettings read(JsonReader in) throws IOException {
|
public VaultSettings read(JsonReader in) throws IOException {
|
||||||
String id = null;
|
String id = null;
|
||||||
String path = null;
|
String path = null;
|
||||||
String mountName = null;
|
String mountName = null; //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||||
String individualMountPath = null;
|
String displayName = null;
|
||||||
|
String customMountPath = null;
|
||||||
String winDriveLetter = null;
|
String winDriveLetter = null;
|
||||||
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
||||||
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
|
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
|
||||||
boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
||||||
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||||
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
||||||
int filenameLengthLimit = VaultSettings.DEFAULT_FILENAME_LENGTH_LIMIT;
|
int filenameLengthLimit = VaultSettings.DEFAULT_FILENAME_LENGTH_LIMIT;
|
||||||
|
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||||
|
|
||||||
in.beginObject();
|
in.beginObject();
|
||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
String name = in.nextName();
|
String name = in.nextName();
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "id":
|
case "id" -> id = in.nextString();
|
||||||
id = in.nextString();
|
case "path" -> path = in.nextString();
|
||||||
break;
|
case "mountName" -> mountName = in.nextString(); //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||||
case "path":
|
case "displayName" -> displayName = in.nextString();
|
||||||
path = in.nextString();
|
case "winDriveLetter" -> winDriveLetter = in.nextString();
|
||||||
break;
|
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
|
||||||
case "mountName":
|
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
|
||||||
mountName = in.nextString();
|
case "usesIndividualMountPath", "useCustomMountPath" -> useCustomMountPath = in.nextBoolean();
|
||||||
break;
|
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
|
||||||
case "winDriveLetter":
|
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
|
||||||
winDriveLetter = in.nextString();
|
case "mountFlags" -> mountFlags = in.nextString();
|
||||||
break;
|
case "filenameLengthLimit" -> filenameLengthLimit = in.nextInt();
|
||||||
case "unlockAfterStartup":
|
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
||||||
unlockAfterStartup = in.nextBoolean();
|
default -> {
|
||||||
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:
|
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in.endObject();
|
in.endObject();
|
||||||
|
|
||||||
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id);
|
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.path().set(Paths.get(path));
|
||||||
vaultSettings.winDriveLetter().set(winDriveLetter);
|
vaultSettings.winDriveLetter().set(winDriveLetter);
|
||||||
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
|
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
|
||||||
vaultSettings.revealAfterMount().set(revealAfterMount);
|
vaultSettings.revealAfterMount().set(revealAfterMount);
|
||||||
vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath);
|
vaultSettings.useCustomMountPath().set(useCustomMountPath);
|
||||||
vaultSettings.individualMountPath().set(individualMountPath);
|
vaultSettings.customMountPath().set(customMountPath);
|
||||||
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
||||||
vaultSettings.mountFlags().set(mountFlags);
|
vaultSettings.mountFlags().set(mountFlags);
|
||||||
vaultSettings.filenameLengthLimit().set(filenameLengthLimit);
|
vaultSettings.filenameLengthLimit().set(filenameLengthLimit);
|
||||||
|
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
||||||
return vaultSettings;
|
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;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public enum VolumeImpl {
|
public enum VolumeImpl {
|
||||||
WEBDAV("WebDAV"),
|
WEBDAV("WebDAV"),
|
||||||
FUSE("FUSE"),
|
FUSE("FUSE"),
|
||||||
@@ -17,18 +15,4 @@ public enum VolumeImpl {
|
|||||||
return displayName;
|
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;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public enum WebDavUrlScheme {
|
public enum WebDavUrlScheme {
|
||||||
DAV("dav", "dav:// (Gnome, Nautilus, ...)"),
|
DAV("dav", "dav:// (Gnome, Nautilus, ...)"),
|
||||||
WEBDAV("webdav", "webdav:// (KDE, Dolphin, ...)");
|
WEBDAV("webdav", "webdav:// (KDE, Dolphin, ...)");
|
||||||
@@ -9,7 +7,8 @@ public enum WebDavUrlScheme {
|
|||||||
private final String prefix;
|
private final String prefix;
|
||||||
private final String displayName;
|
private final String displayName;
|
||||||
|
|
||||||
WebDavUrlScheme(String prefix, String displayName) {this.prefix = prefix;
|
WebDavUrlScheme(String prefix, String displayName) {
|
||||||
|
this.prefix = prefix;
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,18 +19,4 @@ public enum WebDavUrlScheme {
|
|||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
return displayName;
|
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
|
@Documented
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
public @interface DefaultMountFlags {
|
public @interface DefaultMountFlags {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package org.cryptomator.common.vaults;
|
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.VaultSettings;
|
||||||
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.frontend.dokany.Mount;
|
import org.cryptomator.frontend.dokany.Mount;
|
||||||
import org.cryptomator.frontend.dokany.MountFactory;
|
import org.cryptomator.frontend.dokany.MountFactory;
|
||||||
@@ -10,84 +12,47 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.io.IOException;
|
import javax.inject.Named;
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
import java.util.SortedSet;
|
||||||
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 java.util.concurrent.ExecutorService;
|
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 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 VaultSettings vaultSettings;
|
||||||
private final MountFactory mountFactory;
|
private final MountFactory mountFactory;
|
||||||
private final WindowsDriveLetters windowsDriveLetters;
|
|
||||||
private Mount mount;
|
private Mount mount;
|
||||||
private Path mountPoint;
|
|
||||||
|
|
||||||
@Inject
|
@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.vaultSettings = vaultSettings;
|
||||||
this.mountFactory = new MountFactory(executorService);
|
this.mountFactory = new MountFactory(executorService);
|
||||||
this.windowsDriveLetters = windowsDriveLetters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported() {
|
public VolumeImpl getImplementationType() {
|
||||||
return DokanyVolume.isSupportedStatic();
|
return VolumeImpl.DOKANY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException, IOException {
|
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
|
||||||
this.mountPoint = determineMountPoint();
|
this.mountPoint = determineMountPoint();
|
||||||
String mountName = vaultSettings.mountName().get();
|
String mountName = vaultSettings.mountName().get();
|
||||||
try {
|
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) {
|
} 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);
|
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);
|
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
|
@Override
|
||||||
public void reveal() throws VolumeException {
|
public void reveal() throws VolumeException {
|
||||||
boolean success = mount.reveal();
|
boolean success = mount.reveal();
|
||||||
@@ -99,11 +64,17 @@ public class DokanyVolume implements Volume {
|
|||||||
@Override
|
@Override
|
||||||
public void unmount() {
|
public void unmount() {
|
||||||
mount.close();
|
mount.close();
|
||||||
|
cleanupMountPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Path> getMountPoint() {
|
public boolean isSupported() {
|
||||||
return Optional.ofNullable(mountPoint);
|
return DokanyVolume.isSupportedStatic();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MountPointRequirement getMountPointRequirement() {
|
||||||
|
return MountPointRequirement.EMPTY_MOUNT_POINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSupportedStatic() {
|
public static boolean isSupportedStatic() {
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package org.cryptomator.common.vaults;
|
|||||||
|
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.Environment;
|
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.mountpoint.MountPointChooser;
|
||||||
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
|
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
|
||||||
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
|
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
|
||||||
@@ -15,95 +16,36 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.io.IOException;
|
import javax.inject.Named;
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
|
||||||
import java.nio.file.DirectoryStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.NotDirectoryException;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.util.SortedSet;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class FuseVolume implements Volume {
|
public class FuseVolume extends AbstractVolume {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
|
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
|
||||||
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
|
|
||||||
private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase().contains("mac");
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
private Mount mount;
|
||||||
private final Environment environment;
|
|
||||||
|
|
||||||
private Mount fuseMnt;
|
|
||||||
private Path mountPoint;
|
|
||||||
private boolean createdTemporaryMountPoint;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FuseVolume(VaultSettings vaultSettings, Environment environment) {
|
public FuseVolume(@Named("orderedMountPointChoosers") SortedSet<MountPointChooser> choosers) {
|
||||||
this.vaultSettings = vaultSettings;
|
super(choosers);
|
||||||
this.environment = environment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags) throws IOException, FuseNotSupportedException, VolumeException {
|
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
|
||||||
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
|
this.mountPoint = determineMountPoint();
|
||||||
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);
|
|
||||||
}
|
|
||||||
mount(fs.getPath("/"), mountFlags);
|
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 {
|
private void mount(Path root, String mountFlags) throws VolumeException {
|
||||||
try {
|
try {
|
||||||
Mounter mounter = FuseMountFactory.getMounter();
|
Mounter mounter = FuseMountFactory.getMounter();
|
||||||
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
||||||
.withFlags(splitFlags(mountFlags)).withMountPoint(mountPoint) //
|
.withFlags(splitFlags(mountFlags)).withMountPoint(mountPoint) //
|
||||||
.build();
|
.build();
|
||||||
this.fuseMnt = mounter.mount(root, envVars);
|
this.mount = mounter.mount(root, envVars);
|
||||||
} catch (CommandFailedException e) {
|
} catch (CommandFailedException | FuseNotSupportedException e) {
|
||||||
throw new VolumeException("Unable to mount Filesystem", e);
|
throw new VolumeException("Unable to mount Filesystem", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +57,7 @@ public class FuseVolume implements Volume {
|
|||||||
@Override
|
@Override
|
||||||
public void reveal() throws VolumeException {
|
public void reveal() throws VolumeException {
|
||||||
try {
|
try {
|
||||||
fuseMnt.revealInFileManager();
|
mount.revealInFileManager();
|
||||||
} catch (CommandFailedException e) {
|
} catch (CommandFailedException e) {
|
||||||
LOG.debug("Revealing the vault in file manger failed: " + e.getMessage());
|
LOG.debug("Revealing the vault in file manger failed: " + e.getMessage());
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
@@ -130,34 +72,23 @@ public class FuseVolume implements Volume {
|
|||||||
@Override
|
@Override
|
||||||
public synchronized void unmountForced() throws VolumeException {
|
public synchronized void unmountForced() throws VolumeException {
|
||||||
try {
|
try {
|
||||||
fuseMnt.unmountForced();
|
mount.unmountForced();
|
||||||
fuseMnt.close();
|
mount.close();
|
||||||
} catch (CommandFailedException e) {
|
} catch (CommandFailedException e) {
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
cleanupTemporaryMountPoint();
|
cleanupMountPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void unmount() throws VolumeException {
|
public synchronized void unmount() throws VolumeException {
|
||||||
try {
|
try {
|
||||||
fuseMnt.unmount();
|
mount.unmount();
|
||||||
fuseMnt.close();
|
mount.close();
|
||||||
} catch (CommandFailedException e) {
|
} catch (CommandFailedException e) {
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
cleanupTemporaryMountPoint();
|
cleanupMountPoint();
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -166,12 +97,17 @@ public class FuseVolume implements Volume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Path> getMountPoint() {
|
public VolumeImpl getImplementationType() {
|
||||||
return Optional.ofNullable(mountPoint);
|
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() {
|
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
|
@Scope
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@interface PerVault {
|
public @interface PerVault {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,11 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
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.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.LazyInitializer;
|
import org.cryptomator.common.LazyInitializer;
|
||||||
|
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.common.vaults.Volume.VolumeException;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
||||||
@@ -31,13 +28,20 @@ import org.slf4j.LoggerFactory;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Provider;
|
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.io.IOException;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.NotDirectoryException;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@@ -56,7 +60,7 @@ public class Vault {
|
|||||||
private final ObjectProperty<VaultState> state;
|
private final ObjectProperty<VaultState> state;
|
||||||
private final ObjectProperty<Exception> lastKnownException;
|
private final ObjectProperty<Exception> lastKnownException;
|
||||||
private final VaultStats stats;
|
private final VaultStats stats;
|
||||||
private final StringBinding displayableName;
|
private final StringBinding displayName;
|
||||||
private final StringBinding displayablePath;
|
private final StringBinding displayablePath;
|
||||||
private final BooleanBinding locked;
|
private final BooleanBinding locked;
|
||||||
private final BooleanBinding processing;
|
private final BooleanBinding processing;
|
||||||
@@ -66,6 +70,7 @@ public class Vault {
|
|||||||
private final BooleanBinding unknownError;
|
private final BooleanBinding unknownError;
|
||||||
private final StringBinding accessPoint;
|
private final StringBinding accessPoint;
|
||||||
private final BooleanBinding accessPointPresent;
|
private final BooleanBinding accessPointPresent;
|
||||||
|
private final BooleanProperty showingStats;
|
||||||
|
|
||||||
private volatile Volume volume;
|
private volatile Volume volume;
|
||||||
|
|
||||||
@@ -78,7 +83,7 @@ public class Vault {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
this.lastKnownException = lastKnownException;
|
this.lastKnownException = lastKnownException;
|
||||||
this.stats = stats;
|
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.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
||||||
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
||||||
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
||||||
@@ -88,6 +93,7 @@ public class Vault {
|
|||||||
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
||||||
this.accessPoint = Bindings.createStringBinding(this::getAccessPoint, state);
|
this.accessPoint = Bindings.createStringBinding(this::getAccessPoint, state);
|
||||||
this.accessPointPresent = this.accessPoint.isNotEmpty();
|
this.accessPointPresent = this.accessPoint.isNotEmpty();
|
||||||
|
this.showingStats = new SimpleBooleanProperty(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
@@ -120,16 +126,13 @@ public class Vault {
|
|||||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
|
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
|
||||||
if (vaultSettings.usesIndividualMountPath().get() && Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
|
|
||||||
throw new NotDirectoryException("");
|
|
||||||
}
|
|
||||||
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
|
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
|
||||||
volume = volumeProvider.get();
|
volume = volumeProvider.get();
|
||||||
volume.mount(fs, getEffectiveMountFlags());
|
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()) {
|
if (forced && volume.supportsForcedUnmount()) {
|
||||||
volume.unmountForced();
|
volume.unmountForced();
|
||||||
} else {
|
} else {
|
||||||
@@ -145,7 +148,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reveal() throws Volume.VolumeException {
|
public void reveal() throws VolumeException {
|
||||||
volume.reveal();
|
volume.reveal();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,13 +228,12 @@ public class Vault {
|
|||||||
return state.get() == VaultState.ERROR;
|
return state.get() == VaultState.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding displayableNameProperty() {
|
public StringBinding displayNameProperty() {
|
||||||
return displayableName;
|
return displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDisplayableName() {
|
public String getDisplayName() {
|
||||||
Path p = vaultSettings.path().get();
|
return vaultSettings.displayName().get();
|
||||||
return p.getFileName().toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding accessPointProperty() {
|
public StringBinding accessPointProperty() {
|
||||||
@@ -270,6 +272,15 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty showingStatsProperty() {
|
||||||
|
return showingStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isShowingStats() {
|
||||||
|
return accessPointPresent.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
// Getter/Setter
|
// Getter/Setter
|
||||||
// *******************************************************************************/
|
// *******************************************************************************/
|
||||||
@@ -319,6 +330,10 @@ public class Vault {
|
|||||||
return vaultSettings.getId();
|
return vaultSettings.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<Volume> getVolume() {
|
||||||
|
return Optional.ofNullable(this.volume);
|
||||||
|
}
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
// Hashcode / Equals
|
// Hashcode / Equals
|
||||||
// *******************************************************************************/
|
// *******************************************************************************/
|
||||||
|
|||||||
@@ -6,15 +6,15 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import dagger.BindsInstance;
|
import dagger.BindsInstance;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
|
|
||||||
import dagger.Subcomponent;
|
import dagger.Subcomponent;
|
||||||
|
import org.cryptomator.common.mountpoint.MountPointChooserModule;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
@PerVault
|
@PerVault
|
||||||
@Subcomponent(modules = {VaultModule.class})
|
@Subcomponent(modules = {VaultModule.class, MountPointChooserModule.class})
|
||||||
public interface VaultComponent {
|
public interface VaultComponent {
|
||||||
|
|
||||||
Vault vault();
|
Vault vault();
|
||||||
|
|||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ class VaultListChangeListener implements ListChangeListener<Vault> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChanged(Change<? extends Vault> c) {
|
public void onChanged(Change<? extends Vault> c) {
|
||||||
while(c.next()) {
|
while (c.next()) {
|
||||||
if (c.wasAdded()) {
|
if (c.wasAdded()) {
|
||||||
List<VaultSettings> addedSettings = c.getAddedSubList().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
|
List<VaultSettings> addedSettings = c.getAddedSubList().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
|
||||||
vaultSettingsList.addAll(c.getFrom(), addedSettings);
|
vaultSettingsList.addAll(c.getFrom(), addedSettings);
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
@@ -19,12 +17,14 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
@@ -36,10 +36,12 @@ public class VaultListManager {
|
|||||||
|
|
||||||
private final VaultComponent.Builder vaultComponentBuilder;
|
private final VaultComponent.Builder vaultComponentBuilder;
|
||||||
private final ObservableList<Vault> vaultList;
|
private final ObservableList<Vault> vaultList;
|
||||||
|
private final String defaultVaultName;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VaultListManager(VaultComponent.Builder vaultComponentBuilder, Settings settings) {
|
public VaultListManager(VaultComponent.Builder vaultComponentBuilder, ResourceBundle resourceBundle, Settings settings) {
|
||||||
this.vaultComponentBuilder = vaultComponentBuilder;
|
this.vaultComponentBuilder = vaultComponentBuilder;
|
||||||
|
this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName");
|
||||||
this.vaultList = FXCollections.observableArrayList(Vault::observables);
|
this.vaultList = FXCollections.observableArrayList(Vault::observables);
|
||||||
|
|
||||||
addAll(settings.getDirectories());
|
addAll(settings.getDirectories());
|
||||||
@@ -59,14 +61,23 @@ public class VaultListManager {
|
|||||||
if (alreadyExistingVault.isPresent()) {
|
if (alreadyExistingVault.isPresent()) {
|
||||||
return alreadyExistingVault.get();
|
return alreadyExistingVault.get();
|
||||||
} else {
|
} else {
|
||||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
Vault newVault = create(newVaultSettings(normalizedPathToVault));
|
||||||
vaultSettings.path().set(normalizedPathToVault);
|
|
||||||
Vault newVault = create(vaultSettings);
|
|
||||||
vaultList.add(newVault);
|
vaultList.add(newVault);
|
||||||
return 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) {
|
private void addAll(Collection<VaultSettings> vaultSettings) {
|
||||||
Collection<Vault> vaults = vaultSettings.stream().map(this::create).collect(Collectors.toList());
|
Collection<Vault> vaults = vaultSettings.stream().map(this::create).collect(Collectors.toList());
|
||||||
vaultList.addAll(vaults);
|
vaultList.addAll(vaults);
|
||||||
@@ -93,7 +104,26 @@ public class VaultListManager {
|
|||||||
return compBuilder.build().vault();
|
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)) {
|
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
|
||||||
return VaultState.MISSING;
|
return VaultState.MISSING;
|
||||||
} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {
|
} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {
|
||||||
|
|||||||
@@ -7,14 +7,6 @@ package org.cryptomator.common.vaults;
|
|||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
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.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
@@ -25,6 +17,12 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Named;
|
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.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -77,7 +75,7 @@ public class VaultModule {
|
|||||||
@DefaultMountFlags
|
@DefaultMountFlags
|
||||||
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
||||||
ObjectProperty<VolumeImpl> preferredVolumeImpl = settings.preferredVolumeImpl();
|
ObjectProperty<VolumeImpl> preferredVolumeImpl = settings.preferredVolumeImpl();
|
||||||
StringProperty mountName = vaultSettings.mountName();
|
StringBinding mountName = vaultSettings.mountName();
|
||||||
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
|
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
|
||||||
|
|
||||||
return Bindings.createStringBinding(() -> {
|
return Bindings.createStringBinding(() -> {
|
||||||
@@ -86,6 +84,8 @@ public class VaultModule {
|
|||||||
return getMacFuseDefaultMountFlags(mountName, readOnly);
|
return getMacFuseDefaultMountFlags(mountName, readOnly);
|
||||||
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_LINUX) {
|
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_LINUX) {
|
||||||
return getLinuxFuseDefaultMountFlags(readOnly);
|
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) {
|
} else if (v == VolumeImpl.DOKANY && SystemUtils.IS_OS_WINDOWS) {
|
||||||
return getDokanyDefaultMountFlags(readOnly);
|
return getDokanyDefaultMountFlags(readOnly);
|
||||||
} else {
|
} else {
|
||||||
@@ -95,7 +95,7 @@ public class VaultModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
|
// 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;
|
assert SystemUtils.IS_OS_MAC_OSX;
|
||||||
StringBuilder flags = new StringBuilder();
|
StringBuilder flags = new StringBuilder();
|
||||||
if (readOnly.get()) {
|
if (readOnly.get()) {
|
||||||
@@ -144,6 +144,28 @@ public class VaultModule {
|
|||||||
return flags.toString().strip();
|
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
|
// 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) {
|
private String getDokanyDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
|
||||||
assert SystemUtils.IS_OS_WINDOWS;
|
assert SystemUtils.IS_OS_WINDOWS;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.cryptomator.common.vaults;
|
|||||||
|
|
||||||
public enum VaultState {
|
public enum VaultState {
|
||||||
/**
|
/**
|
||||||
* No vault found at the provided path
|
* No vault found at the provided path
|
||||||
*/
|
*/
|
||||||
MISSING,
|
MISSING,
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
package org.cryptomator.common.vaults;
|
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.CryptoFileSystem;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemStats;
|
import org.cryptomator.cryptofs.CryptoFileSystemStats;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
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.Optional;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
@@ -28,6 +30,15 @@ public class VaultStats {
|
|||||||
private final ScheduledService<Optional<CryptoFileSystemStats>> updateService;
|
private final ScheduledService<Optional<CryptoFileSystemStats>> updateService;
|
||||||
private final LongProperty bytesPerSecondRead = new SimpleLongProperty();
|
private final LongProperty bytesPerSecondRead = new SimpleLongProperty();
|
||||||
private final LongProperty bytesPerSecondWritten = 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
|
@Inject
|
||||||
VaultStats(AtomicReference<CryptoFileSystem> fs, ObjectProperty<VaultState> state, ExecutorService executor) {
|
VaultStats(AtomicReference<CryptoFileSystem> fs, ObjectProperty<VaultState> state, ExecutorService executor) {
|
||||||
@@ -53,12 +64,36 @@ public class VaultStats {
|
|||||||
|
|
||||||
private void updateStats(Optional<CryptoFileSystemStats> stats) {
|
private void updateStats(Optional<CryptoFileSystemStats> stats) {
|
||||||
assert Platform.isFxApplicationThread();
|
assert Platform.isFxApplicationThread();
|
||||||
bytesPerSecondRead.set(stats.map(CryptoFileSystemStats::pollBytesRead).orElse(0l));
|
bytesPerSecondRead.set(stats.map(CryptoFileSystemStats::pollBytesRead).orElse(0L));
|
||||||
bytesPerSecondWritten.set(stats.map(CryptoFileSystemStats::pollBytesWritten).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 class UpdateStatsService extends ScheduledService<Optional<CryptoFileSystemStats>> {
|
||||||
|
|
||||||
|
private UpdateStatsService() {
|
||||||
|
setOnFailed(event -> LOG.error("Error in UpdateStateService.", getException()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Task<Optional<CryptoFileSystemStats>> createTask() {
|
protected Task<Optional<CryptoFileSystemStats>> createTask() {
|
||||||
return new Task<>() {
|
return new Task<>() {
|
||||||
@@ -94,4 +129,50 @@ public class VaultStats {
|
|||||||
public long getBytesPerSecondWritten() {
|
public long getBytesPerSecondWritten() {
|
||||||
return bytesPerSecondWritten.get();
|
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;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
|
|
||||||
@@ -20,11 +21,18 @@ public interface Volume {
|
|||||||
*/
|
*/
|
||||||
boolean isSupported();
|
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
|
* @param fs
|
||||||
* @throws IOException
|
* @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;
|
void reveal() throws VolumeException;
|
||||||
|
|
||||||
@@ -32,6 +40,8 @@ public interface Volume {
|
|||||||
|
|
||||||
Optional<Path> getMountPoint();
|
Optional<Path> getMountPoint();
|
||||||
|
|
||||||
|
MountPointRequirement getMountPointRequirement();
|
||||||
|
|
||||||
// optional forced unmounting:
|
// optional forced unmounting:
|
||||||
|
|
||||||
default boolean supportsForcedUnmount() {
|
default boolean supportsForcedUnmount() {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.common.base.CharMatcher;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||||
import org.cryptomator.frontend.webdav.mount.MountParams;
|
import org.cryptomator.frontend.webdav.mount.MountParams;
|
||||||
@@ -44,7 +46,9 @@ public class WebDavVolume implements Volume {
|
|||||||
if (!server.isRunning()) {
|
if (!server.isRunning()) {
|
||||||
server.start();
|
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();
|
servlet.start();
|
||||||
mount();
|
mount();
|
||||||
}
|
}
|
||||||
@@ -98,7 +102,12 @@ public class WebDavVolume implements Volume {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Path> getMountPoint() {
|
public Optional<Path> getMountPoint() {
|
||||||
return Optional.ofNullable(mountPoint);
|
return Optional.ofNullable(mountPoint); //TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MountPointRequirement getMountPointRequirement() {
|
||||||
|
return MountPointRequirement.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getLocalhostAliasOrNull() {
|
private String getLocalhostAliasOrNull() {
|
||||||
@@ -126,6 +135,11 @@ public class WebDavVolume implements Volume {
|
|||||||
return WebDavVolume.isSupportedStatic();
|
return WebDavVolume.isSupportedStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VolumeImpl getImplementationType() {
|
||||||
|
return VolumeImpl.WEBDAV;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsForcedUnmount() {
|
public boolean supportsForcedUnmount() {
|
||||||
return mount != null && mount.forced().isPresent();
|
return mount != null && mount.forced().isPresent();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ import javax.inject.Inject;
|
|||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
@@ -24,7 +26,7 @@ public final class WindowsDriveLetters {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
try (IntStream stream = IntStream.rangeClosed('C', 'Z')) {
|
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() {
|
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());
|
List<Path> result = env.getSettingsPath().collect(Collectors.toList());
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
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")));
|
Paths.get("/home/testuser/.Cryptomator/settings.json")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ class EnvironmentTest {
|
|||||||
|
|
||||||
List<Path> result = env.getIpcPortPath().collect(Collectors.toList());
|
List<Path> result = env.getIpcPortPath().collect(Collectors.toList());
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
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")));
|
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,8 +123,8 @@ class EnvironmentTest {
|
|||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(3));
|
MatcherAssert.assertThat(result, Matchers.hasSize(3));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"),
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"), //
|
||||||
Paths.get("/home/testuser/test2"),
|
Paths.get("/home/testuser/test2"), //
|
||||||
Paths.get("/foo/bar/test")));
|
Paths.get("/foo/bar/test")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.cryptomator.common;
|
package org.cryptomator.common;
|
||||||
|
|
||||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
import org.cryptomator.common.LicenseChecker;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -14,7 +13,7 @@ class LicenseCheckerTest {
|
|||||||
+ "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" //
|
+ "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" //
|
||||||
+ "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" //
|
+ "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" //
|
||||||
+ "Al8G7CqwoJOsW7Kddns=";
|
+ "Al8G7CqwoJOsW7Kddns=";
|
||||||
|
|
||||||
private LicenseChecker licenseChecker;
|
private LicenseChecker licenseChecker;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@@ -25,9 +24,9 @@ class LicenseCheckerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCheckValidLicense() {
|
public void testCheckValidLicense() {
|
||||||
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
||||||
|
|
||||||
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
||||||
|
|
||||||
Assertions.assertTrue(decoded.isPresent());
|
Assertions.assertTrue(decoded.isPresent());
|
||||||
Assertions.assertEquals("cryptobot@example.com", decoded.get().getSubject());
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+10
-2
@@ -3,12 +3,14 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* are made available under the terms of the accompanying LICENSE file.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.keychain;
|
package org.cryptomator.common.keychain;
|
||||||
|
|
||||||
|
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
class MapKeychainAccess implements KeychainAccessStrategy {
|
class MapKeychainAccess implements KeychainAccessProvider {
|
||||||
|
|
||||||
private final Map<String, char[]> map = new HashMap<>();
|
private final Map<String, char[]> map = new HashMap<>();
|
||||||
|
|
||||||
@@ -31,6 +33,12 @@ class MapKeychainAccess implements KeychainAccessStrategy {
|
|||||||
map.remove(key);
|
map.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changePassphrase(String key, CharSequence passphrase) {
|
||||||
|
map.get(key);
|
||||||
|
storePassphrase(key, passphrase);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported() {
|
public boolean isSupported() {
|
||||||
return true;
|
return true;
|
||||||
+4
-4
@@ -37,10 +37,10 @@ public class SettingsJsonAdapterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest(name = "fromJson() should throw IOException for input: {0}")
|
@ParameterizedTest(name = "fromJson() should throw IOException for input: {0}")
|
||||||
@ValueSource(strings = {
|
@ValueSource(strings = { //
|
||||||
"",
|
"", //
|
||||||
"<html>",
|
"<html>", //
|
||||||
"{invalidjson}"
|
"{invalidjson}" //
|
||||||
})
|
})
|
||||||
public void testDeserializeMalformed(String input) {
|
public void testDeserializeMalformed(String input) {
|
||||||
Assertions.assertThrows(IOException.class, () -> {
|
Assertions.assertThrows(IOException.class, () -> {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class SettingsTest {
|
|||||||
Mockito.verify(changeListener, Mockito.times(2)).accept(settings);
|
Mockito.verify(changeListener, Mockito.times(2)).accept(settings);
|
||||||
|
|
||||||
// third change (to property of list item):
|
// third change (to property of list item):
|
||||||
vaultSettings.mountName().set("asd");
|
vaultSettings.displayName().set("asd");
|
||||||
Mockito.verify(changeListener, Mockito.times(3)).accept(settings);
|
Mockito.verify(changeListener, Mockito.times(3)).accept(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-7
@@ -16,7 +16,6 @@ import java.io.IOException;
|
|||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class VaultSettingsJsonAdapterTest {
|
public class VaultSettingsJsonAdapterTest {
|
||||||
|
|
||||||
@@ -24,15 +23,15 @@ public class VaultSettingsJsonAdapterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeserialize() throws IOException {
|
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));
|
JsonReader jsonReader = new JsonReader(new StringReader(json));
|
||||||
|
|
||||||
VaultSettings vaultSettings = adapter.read(jsonReader);
|
VaultSettings vaultSettings = adapter.read(jsonReader);
|
||||||
Assertions.assertEquals("foo", vaultSettings.getId());
|
Assertions.assertEquals("foo", vaultSettings.getId());
|
||||||
Assertions.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
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("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());
|
Assertions.assertEquals("--foo --bar", vaultSettings.mountFlags().get());
|
||||||
|
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ public class VaultSettingsJsonAdapterTest {
|
|||||||
public void testSerialize() throws IOException {
|
public void testSerialize() throws IOException {
|
||||||
VaultSettings vaultSettings = new VaultSettings("test");
|
VaultSettings vaultSettings = new VaultSettings("test");
|
||||||
vaultSettings.path().set(Paths.get("/foo/bar"));
|
vaultSettings.path().set(Paths.get("/foo/bar"));
|
||||||
vaultSettings.mountName().set("mountyMcMountFace");
|
vaultSettings.displayName().set("mountyMcMountFace");
|
||||||
vaultSettings.mountFlags().set("--foo --bar");
|
vaultSettings.mountFlags().set("--foo --bar");
|
||||||
|
|
||||||
StringWriter buf = new StringWriter();
|
StringWriter buf = new StringWriter();
|
||||||
@@ -51,12 +50,12 @@ public class VaultSettingsJsonAdapterTest {
|
|||||||
String result = buf.toString();
|
String result = buf.toString();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"id\":\"test\""));
|
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\""));
|
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"\\\\foo\\\\bar\""));
|
||||||
} else {
|
} else {
|
||||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"/foo/bar\""));
|
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\""));
|
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountFlags\":\"--foo --bar\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,19 +8,19 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
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;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
public class VaultSettingsTest {
|
public class VaultSettingsTest {
|
||||||
|
|
||||||
@Test
|
@ParameterizedTest
|
||||||
public void testNormalize() throws Exception {
|
@CsvSource({"a\u000Fa,a_a", ": \\,_ _", "汉语,汉语", "..,_", "a\ta,a\u0020a", "\t\n\r,_"})
|
||||||
assertEquals("_", VaultSettings.normalizeMountName(" "));
|
public void testNormalize(String test, String expected) {
|
||||||
assertEquals("a", VaultSettings.normalizeMountName("ä"));
|
VaultSettings settings = new VaultSettings("id");
|
||||||
assertEquals("C", VaultSettings.normalizeMountName("Ĉ"));
|
settings.displayName().setValue(test);
|
||||||
assertEquals("_", VaultSettings.normalizeMountName(":"));
|
assertEquals(expected, settings.normalizeDisplayName());
|
||||||
assertEquals("_", VaultSettings.normalizeMountName("汉语"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
package org.cryptomator.common.vaults;
|
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.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
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.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Mockito;
|
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;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public class VaultModuleTest {
|
public class VaultModuleTest {
|
||||||
@@ -28,8 +29,9 @@ public class VaultModuleTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setup(@TempDir Path tmpDir) {
|
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.usesReadOnlyMode()).thenReturn(new SimpleBooleanProperty(true));
|
||||||
|
Mockito.when(vaultSettings.displayName()).thenReturn(new SimpleStringProperty("Vault"));
|
||||||
System.setProperty("user.home", tmpDir.toString());
|
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.3</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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-51
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-65
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-199
@@ -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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-56
@@ -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"?>
|
<?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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.3</version>
|
<version>1.5.9</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>launcher</artifactId>
|
<artifactId>launcher</artifactId>
|
||||||
<name>Cryptomator Launcher</name>
|
<name>Cryptomator Launcher</name>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.concurrent.CountDownLatch;
|
|||||||
|
|
||||||
@Module
|
@Module
|
||||||
class CryptomatorModule {
|
class CryptomatorModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@Named("shutdownLatch")
|
@Named("shutdownLatch")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This class is licensed under the LGPL 3.0 (https://www.gnu.org/licenses/lgpl-3.0.de.html).
|
* This class is licensed under the LGPL 3.0 (https://www.gnu.org/licenses/lgpl-3.0.de.html).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
@@ -25,7 +25,6 @@ import java.util.Collection;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class FileOpenRequestHandler {
|
class FileOpenRequestHandler {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import javax.inject.Singleton;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class IpcProtocolImpl implements IpcProtocol {
|
class IpcProtocolImpl implements IpcProtocol {
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ package org.cryptomator.logging;
|
|||||||
import ch.qos.logback.classic.Level;
|
import ch.qos.logback.classic.Level;
|
||||||
import ch.qos.logback.classic.Logger;
|
import ch.qos.logback.classic.Logger;
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
import ch.qos.logback.classic.LoggerContext;
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|||||||
+30
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+2
-2
@@ -5,11 +5,11 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.logging;
|
package org.cryptomator.logging;
|
||||||
|
|
||||||
|
import ch.qos.logback.core.rolling.TriggeringPolicyBase;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
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.
|
* 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
|
@Singleton
|
||||||
public class LoggerConfiguration {
|
public class LoggerConfiguration {
|
||||||
|
|
||||||
private final LoggerContext context;
|
private final LoggerContext context;
|
||||||
private final Environment environment;
|
private final Environment environment;
|
||||||
private final Appender<ILoggingEvent> stdout;
|
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.ConsoleAppender;
|
||||||
import ch.qos.logback.core.FileAppender;
|
import ch.qos.logback.core.FileAppender;
|
||||||
import ch.qos.logback.core.helpers.NOPAppender;
|
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.FixedWindowRollingPolicy;
|
||||||
import ch.qos.logback.core.rolling.RollingFileAppender;
|
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.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import org.cryptomator.common.Environment;
|
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_MIN = 1;
|
||||||
private static final int LOGFILE_ROLLING_MAX = 9;
|
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_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( //
|
static final Map<String, Level> DEFAULT_LOG_LEVELS = Map.of( //
|
||||||
Logger.ROOT_LOGGER_NAME, Level.INFO, //
|
Logger.ROOT_LOGGER_NAME, Level.INFO, //
|
||||||
"org.cryptomator", Level.INFO //
|
"org.cryptomator", Level.INFO //
|
||||||
@@ -84,7 +85,7 @@ public class LoggerModule {
|
|||||||
appender.setContext(context);
|
appender.setContext(context);
|
||||||
appender.setFile(logDir.resolve(LOGFILE_NAME).toString());
|
appender.setFile(logDir.resolve(LOGFILE_NAME).toString());
|
||||||
appender.setEncoder(encoder);
|
appender.setEncoder(encoder);
|
||||||
LaunchBasedTriggeringPolicy triggeringPolicy = new LaunchBasedTriggeringPolicy();
|
LaunchAndSizeBasedTriggerinPolicy triggeringPolicy = new LaunchAndSizeBasedTriggerinPolicy(FileSize.valueOf(LOG_MAX_SIZE));
|
||||||
triggeringPolicy.setContext(context);
|
triggeringPolicy.setContext(context);
|
||||||
triggeringPolicy.start();
|
triggeringPolicy.start();
|
||||||
appender.setTriggeringPolicy(triggeringPolicy);
|
appender.setTriggeringPolicy(triggeringPolicy);
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ public class IpcFactoryTest {
|
|||||||
Assertions.assertTrue(endpoint2.isConnectedToRemote());
|
Assertions.assertTrue(endpoint2.isConnectedToRemote());
|
||||||
Assertions.assertNotSame(protocolHandler, endpoint2.getRemote());
|
Assertions.assertNotSame(protocolHandler, endpoint2.getRemote());
|
||||||
Mockito.verifyZeroInteractions(protocolHandler);
|
Mockito.verifyZeroInteractions(protocolHandler);
|
||||||
endpoint2.getRemote().handleLaunchArgs(new String[] {"foo"});
|
endpoint2.getRemote().handleLaunchArgs(new String[]{"foo"});
|
||||||
Mockito.verify(protocolHandler).handleLaunchArgs(new String[] {"foo"});
|
Mockito.verify(protocolHandler).handleLaunchArgs(new String[]{"foo"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+112
-45
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.3</version>
|
<version>1.5.9</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>Cryptomator</name>
|
<name>Cryptomator</name>
|
||||||
|
|
||||||
@@ -24,26 +24,29 @@
|
|||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
<!-- cryptomator dependencies -->
|
<!-- cryptomator dependencies -->
|
||||||
<cryptomator.cryptofs.version>1.9.10</cryptomator.cryptofs.version>
|
<cryptomator.cryptofs.version>1.9.13</cryptomator.cryptofs.version>
|
||||||
<cryptomator.jni.version>2.2.2</cryptomator.jni.version>
|
<cryptomator.integrations.version>0.1.6</cryptomator.integrations.version>
|
||||||
<cryptomator.fuse.version>1.2.3</cryptomator.fuse.version>
|
<cryptomator.integrations.win.version>0.1.0-beta1</cryptomator.integrations.win.version>
|
||||||
<cryptomator.dokany.version>1.1.14</cryptomator.dokany.version>
|
<cryptomator.integrations.mac.version>0.1.0-beta3</cryptomator.integrations.mac.version>
|
||||||
<cryptomator.webdav.version>1.0.11</cryptomator.webdav.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 -->
|
<!-- 3rd party dependencies -->
|
||||||
<javafx.version>14</javafx.version>
|
<javafx.version>15</javafx.version>
|
||||||
<commons-lang3.version>3.9</commons-lang3.version>
|
<commons-lang3.version>3.11</commons-lang3.version>
|
||||||
<jwt.version>3.10.2</jwt.version>
|
<jwt.version>3.11.0</jwt.version>
|
||||||
<easybind.version>1.0.3</easybind.version>
|
<easybind.version>2.1.0</easybind.version>
|
||||||
<guava.version>28.2-jre</guava.version>
|
<guava.version>30.0-jre</guava.version>
|
||||||
<dagger.version>2.27</dagger.version>
|
<dagger.version>2.29.1</dagger.version>
|
||||||
<gson.version>2.8.6</gson.version>
|
<gson.version>2.8.6</gson.version>
|
||||||
<slf4j.version>1.7.30</slf4j.version>
|
<slf4j.version>1.7.30</slf4j.version>
|
||||||
<logback.version>1.2.3</logback.version>
|
<logback.version>1.2.3</logback.version>
|
||||||
|
|
||||||
<!-- test dependencies -->
|
<!-- test dependencies -->
|
||||||
<junit.jupiter.version>5.6.1</junit.jupiter.version>
|
<junit.jupiter.version>5.7.0</junit.jupiter.version>
|
||||||
<mockito.version>3.3.3</mockito.version>
|
<mockito.version>3.6.0</mockito.version>
|
||||||
<hamcrest.version>2.2</hamcrest.version>
|
<hamcrest.version>2.2</hamcrest.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
@@ -62,11 +65,6 @@
|
|||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>keychain</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>ui</artifactId>
|
<artifactId>ui</artifactId>
|
||||||
@@ -79,11 +77,6 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Cryptomator Libs -->
|
<!-- Cryptomator Libs -->
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>siv-mode</artifactId>
|
|
||||||
<version>1.4.0</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>cryptofs</artifactId>
|
<artifactId>cryptofs</artifactId>
|
||||||
@@ -106,8 +99,23 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>jni</artifactId>
|
<artifactId>integrations-api</artifactId>
|
||||||
<version>${cryptomator.jni.version}</version>
|
<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>
|
</dependency>
|
||||||
|
|
||||||
<!-- JavaFX -->
|
<!-- JavaFX -->
|
||||||
@@ -160,7 +168,7 @@
|
|||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>${commons-lang3.version}</version>
|
<version>${commons-lang3.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JWT -->
|
<!-- JWT -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.auth0</groupId>
|
<groupId>com.auth0</groupId>
|
||||||
@@ -170,7 +178,7 @@
|
|||||||
|
|
||||||
<!-- EasyBind -->
|
<!-- EasyBind -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.fxmisc.easybind</groupId>
|
<groupId>com.tobiasdiez</groupId>
|
||||||
<artifactId>easybind</artifactId>
|
<artifactId>easybind</artifactId>
|
||||||
<version>${easybind.version}</version>
|
<version>${easybind.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
@@ -227,6 +235,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter</artifactId>
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hamcrest</groupId>
|
<groupId>org.hamcrest</groupId>
|
||||||
@@ -242,7 +251,6 @@
|
|||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>commons</module>
|
<module>commons</module>
|
||||||
<module>keychain</module>
|
|
||||||
<module>ui</module>
|
<module>ui</module>
|
||||||
<module>launcher</module>
|
<module>launcher</module>
|
||||||
</modules>
|
</modules>
|
||||||
@@ -265,26 +273,87 @@
|
|||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</profile>
|
</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>
|
</profiles>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<pluginManagement>
|
<pluginManagement>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<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>
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
<version>3.1.2</version>
|
<version>3.1.2</version>
|
||||||
<executions>
|
</plugin>
|
||||||
<execution>
|
<plugin>
|
||||||
<id>copy-libs</id>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<goals>
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
<goal>copy-dependencies</goal>
|
<version>3.3.0</version>
|
||||||
</goals>
|
</plugin>
|
||||||
<configuration>
|
<plugin>
|
||||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<includeScope>runtime</includeScope>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
</configuration>
|
<version>2.22.2</version>
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
@@ -307,7 +376,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.jacoco</groupId>
|
<groupId>org.jacoco</groupId>
|
||||||
<artifactId>jacoco-maven-plugin</artifactId>
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
<version>0.8.5</version>
|
<version>0.8.6</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>prepare-agent</id>
|
<id>prepare-agent</id>
|
||||||
@@ -334,7 +403,6 @@
|
|||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.1</version>
|
|
||||||
<configuration>
|
<configuration>
|
||||||
<release>14</release>
|
<release>14</release>
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths>
|
||||||
@@ -349,7 +417,6 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>2.22.2</version>
|
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|||||||
+7
-15
@@ -1,28 +1,20 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.3</version>
|
<version>1.5.9</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>ui</artifactId>
|
<artifactId>ui</artifactId>
|
||||||
<name>Cryptomator GUI</name>
|
<name>Cryptomator GUI</name>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>keychain</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>jni</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- JavaFx -->
|
<!-- JavaFx -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
@@ -35,7 +27,7 @@
|
|||||||
|
|
||||||
<!-- EasyBind -->
|
<!-- EasyBind -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.fxmisc.easybind</groupId>
|
<groupId>com.tobiasdiez</groupId>
|
||||||
<artifactId>easybind</artifactId>
|
<artifactId>easybind</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -67,14 +59,14 @@
|
|||||||
<artifactId>zxcvbn</artifactId>
|
<artifactId>zxcvbn</artifactId>
|
||||||
<version>1.3.0</version>
|
<version>1.3.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Logging -->
|
<!-- Logging -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-simple</artifactId>
|
<artifactId>slf4j-simple</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Testing -->
|
<!-- Testing -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.jimfs</groupId>
|
<groupId>com.google.jimfs</groupId>
|
||||||
@@ -88,7 +80,7 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<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:
|
Cryptomator uses ${dependencyMap?size} third-party dependencies under the following licenses:
|
||||||
<#list licenseMap as e>
|
<#list licenseMap as e>
|
||||||
<#assign license = e.getKey()/>
|
<#assign license = e.getKey()/>
|
||||||
<#assign projects = e.getValue()/>
|
<#assign projects = e.getValue()/>
|
||||||
<#if projects?size > 0>
|
<#if projects?size > 0>
|
||||||
${license}:
|
${license}:
|
||||||
<#list projects as project>
|
<#list projects as project>
|
||||||
- ${artifactFormat(project)}
|
- ${artifactFormat(project)}
|
||||||
</#list>
|
</#list>
|
||||||
</#if>
|
</#if>
|
||||||
</#list>
|
</#list>
|
||||||
|
|
||||||
Cryptomator uses other third-party assets under the following licenses:
|
Cryptomator uses other third-party assets under the following licenses:
|
||||||
SIL OFL 1.1 License:
|
SIL OFL 1.1 License:
|
||||||
- Font Awesome 5.12.0 (https://fontawesome.com/)
|
- 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