mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-21 20:21:27 +00:00
Compare commits
278 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
207bfee6e5 | ||
|
|
d08f3d03d0 | ||
|
|
a88bd81347 | ||
|
|
8e2d2b899e | ||
|
|
3b4870a98a | ||
|
|
ef5eabdb79 | ||
|
|
f52b2f323a | ||
|
|
7e60e5606c | ||
|
|
8e2fa082cc | ||
|
|
d8ef402607 | ||
|
|
748f1be0c5 | ||
|
|
608d54a8f2 | ||
|
|
50b167e28f | ||
|
|
b7d06783dd | ||
|
|
5ef3d23970 | ||
|
|
200a195f3b | ||
|
|
db0aceefdf | ||
|
|
492e986608 | ||
|
|
98cab7e4d8 | ||
|
|
384c9de7aa | ||
|
|
528005a623 | ||
|
|
c78d4c2d0e | ||
|
|
c8e9201692 | ||
|
|
536da2621a | ||
|
|
6167eeecb4 | ||
|
|
9cc873a344 | ||
|
|
b539590d7a | ||
|
|
5209bef1a9 | ||
|
|
ee5505362c | ||
|
|
82f388d420 | ||
|
|
70e733f341 | ||
|
|
3af5d5f267 | ||
|
|
28bf9e2ab1 | ||
|
|
2f3a576de9 | ||
|
|
bbe1ef3dbc | ||
|
|
078a127182 | ||
|
|
240bf122dd | ||
|
|
f2f8f9b28c | ||
|
|
af03e0d73d | ||
|
|
7844078203 | ||
|
|
da62a22faf | ||
|
|
c36a1a4aef | ||
|
|
f760347d9d | ||
|
|
8fc647eb2b | ||
|
|
69b1bf5a26 | ||
|
|
6951edac96 | ||
|
|
76c84b34e9 | ||
|
|
1362720011 | ||
|
|
3f15352ebf | ||
|
|
fdedee49d3 | ||
|
|
b506493c13 | ||
|
|
bc09f28120 | ||
|
|
eba0935900 | ||
|
|
050a6e6a57 | ||
|
|
dbcb407a7c | ||
|
|
b1acdf9e61 | ||
|
|
9c6a0b3e0c | ||
|
|
f6df442948 | ||
|
|
29038a679f | ||
|
|
2e042423d0 | ||
|
|
e08c2b7dc3 | ||
|
|
d5d0589dec | ||
|
|
a760edcab9 | ||
|
|
a759455473 | ||
|
|
50d2f16f71 | ||
|
|
8afa09007b | ||
|
|
fccd02a7e8 | ||
|
|
7e46957bcb | ||
|
|
194f373c08 | ||
|
|
5f11e1aa8b | ||
|
|
0ac1b406b7 | ||
|
|
ade2db7cc0 | ||
|
|
eb911aeb6c | ||
|
|
a58259d4f0 | ||
|
|
963a731202 | ||
|
|
5ea0fc4b6f | ||
|
|
4becdf7e1e | ||
|
|
8ab8798381 | ||
|
|
c3f8fa1662 | ||
|
|
59d019f34a | ||
|
|
ee99e9994e | ||
|
|
019b7ac643 | ||
|
|
7392a99da5 | ||
|
|
e909269ae0 | ||
|
|
45ad496f41 | ||
|
|
a272bf2614 | ||
|
|
119e0724d5 | ||
|
|
76a9cb9a06 | ||
|
|
acb8d4cd0c | ||
|
|
ffe8887114 | ||
|
|
d8c8c2380a | ||
|
|
fe5ce79802 | ||
|
|
bf7a8686a6 | ||
|
|
143070d02d | ||
|
|
06827a7466 | ||
|
|
5add862ce8 | ||
|
|
2b8d7c6c3b | ||
|
|
f5da13d3b4 | ||
|
|
03dfd3e887 | ||
|
|
8241559362 | ||
|
|
c289040624 | ||
|
|
767acbd922 | ||
|
|
ccd4cedb08 | ||
|
|
0ddd6d767d | ||
|
|
6a5a1e5bae | ||
|
|
b3d76bb394 | ||
|
|
1924a7dec9 | ||
|
|
b65da30899 | ||
|
|
7de8b3da02 | ||
|
|
48ac8da1a7 | ||
|
|
86ae35c7eb | ||
|
|
8421a8fc7b | ||
|
|
b579e03bc8 | ||
|
|
9217b11e61 | ||
|
|
e16bd7373c | ||
|
|
ef53561bf0 | ||
|
|
3165c4ba86 | ||
|
|
f1bf157cac | ||
|
|
9c75dd48dd | ||
|
|
74d4b4ea47 | ||
|
|
6f66f4cbf1 | ||
|
|
874c5506a7 | ||
|
|
aed35c17c8 | ||
|
|
873e438759 | ||
|
|
5b45893c7b | ||
|
|
5515258af1 | ||
|
|
dd5d52d25a | ||
|
|
24236f3844 | ||
|
|
458866f7d6 | ||
|
|
525b0a7982 | ||
|
|
d53af61b58 | ||
|
|
b0ab46b7b6 | ||
|
|
9107d296c3 | ||
|
|
6be95963a1 | ||
|
|
09c9361e94 | ||
|
|
5e7cea216d | ||
|
|
6d91992102 | ||
|
|
5a23ee0be6 | ||
|
|
31e186dd15 | ||
|
|
c7beb4a93c | ||
|
|
6aff94a8f8 | ||
|
|
273f6907c9 | ||
|
|
95df963913 | ||
|
|
13c5e2470b | ||
|
|
2341440ed9 | ||
|
|
558f4be945 | ||
|
|
d63735df1a | ||
|
|
8be9cbbab7 | ||
|
|
191d7561f0 | ||
|
|
49a6fe9571 | ||
|
|
8b85fa5928 | ||
|
|
2695979ae2 | ||
|
|
f2212169be | ||
|
|
5898a4d584 | ||
|
|
42b5b2663e | ||
|
|
a0a867474b | ||
|
|
c98f7f8574 | ||
|
|
fae2e19b96 | ||
|
|
45548a0023 | ||
|
|
8edd5326fa | ||
|
|
1d178674ce | ||
|
|
7593d5cd40 | ||
|
|
9e8f88f000 | ||
|
|
e1eafbc838 | ||
|
|
f2107954b3 | ||
|
|
db3dabe3c5 | ||
|
|
76f69dec34 | ||
|
|
1b130ab58e | ||
|
|
a12e2ae6cd | ||
|
|
7441f67b03 | ||
|
|
d02d521856 | ||
|
|
e6dd14d0e5 | ||
|
|
e257d8e497 | ||
|
|
2d6d3084b0 | ||
|
|
4fffc63869 | ||
|
|
ce457ae20e | ||
|
|
5b9efcc16f | ||
|
|
28001710b3 | ||
|
|
32e81796ae | ||
|
|
68445befeb | ||
|
|
da31a9d2a0 | ||
|
|
9b3167c886 | ||
|
|
7c1a0b5fdf | ||
|
|
a666c80ef5 | ||
|
|
2610ef4645 | ||
|
|
357f659c8d | ||
|
|
19225c058d | ||
|
|
f1ee991d07 | ||
|
|
562daf990f | ||
|
|
19cd0d70e7 | ||
|
|
d38b488ee0 | ||
|
|
84d6f11be2 | ||
|
|
b3cf7eee16 | ||
|
|
9e5e14e462 | ||
|
|
b165f41c6b | ||
|
|
41358e6715 | ||
|
|
c8387c7e3c | ||
|
|
8ace293701 | ||
|
|
f5a08f05c1 | ||
|
|
9707d951d6 | ||
|
|
242b1e92a3 | ||
|
|
3bc7df9e83 | ||
|
|
f774829fb1 | ||
|
|
bfd8918e6a | ||
|
|
3989316269 | ||
|
|
d0d83c6833 | ||
|
|
c957f93ce6 | ||
|
|
b069a16f0c | ||
|
|
bf637f8361 | ||
|
|
a2f6a85334 | ||
|
|
8a359704ca | ||
|
|
e3b9601721 | ||
|
|
d170e87c1b | ||
|
|
39d1d9c561 | ||
|
|
2914af5f7b | ||
|
|
fa10a92fa4 | ||
|
|
1e7478a89f | ||
|
|
8ec2b10a4d | ||
|
|
2001e768d4 | ||
|
|
79fd17250b | ||
|
|
b8ee19b395 | ||
|
|
08cfcffa72 | ||
|
|
9e82739c16 | ||
|
|
6534242275 | ||
|
|
cc40d05e44 | ||
|
|
5fcbe7eff1 | ||
|
|
4be842aff5 | ||
|
|
e808db51a5 | ||
|
|
8e1bb121bb | ||
|
|
355c1934f9 | ||
|
|
b47ced1173 | ||
|
|
a66e08ee9b | ||
|
|
ad27094d63 | ||
|
|
0be97b309b | ||
|
|
b8de68bf3b | ||
|
|
f9dba09003 | ||
|
|
a428e62891 | ||
|
|
424ea8429c | ||
|
|
a52adc1871 | ||
|
|
08d5335b74 | ||
|
|
8741c64b86 | ||
|
|
b64f7cc7a8 | ||
|
|
f84760746f | ||
|
|
296848b41e | ||
|
|
51e6ee5be5 | ||
|
|
9d5609b129 | ||
|
|
e18d62d57f | ||
|
|
9ff710ddf5 | ||
|
|
02f3f5ad53 | ||
|
|
40caba847b | ||
|
|
467e242df4 | ||
|
|
a7c42c3d59 | ||
|
|
ecdd3bc5ec | ||
|
|
46cab2ef9a | ||
|
|
a7b036d441 | ||
|
|
17bbd9161f | ||
|
|
a850e8b816 | ||
|
|
95b8240a21 | ||
|
|
69d379d03b | ||
|
|
1b9951e90e | ||
|
|
e62e9a1dff | ||
|
|
6d1989bbfe | ||
|
|
c41973a8d6 | ||
|
|
f84bb4710f | ||
|
|
d1a9233557 | ||
|
|
1a73f84d83 | ||
|
|
36cf3c2283 | ||
|
|
091fab90ee | ||
|
|
d81bf2e301 | ||
|
|
cd3cf3da0e | ||
|
|
3e961dabdd | ||
|
|
dde7d263b9 | ||
|
|
ff373b9a3f | ||
|
|
0a7d102981 | ||
|
|
10867e87c9 | ||
|
|
7900f0ea8c | ||
|
|
f0d364a6c5 | ||
|
|
5ed50c50c7 |
15
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
15
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
@@ -4,20 +4,9 @@
|
|||||||
|
|
||||||
- Ensure you're running the latest version of Cryptomator.
|
- Ensure you're running the latest version of Cryptomator.
|
||||||
- Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS and Android app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/cryptomator-ios/issues) and [Cryptomator for Android issues list](https://github.com/cryptomator/cryptomator-android/issues) respectively.
|
- Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS and Android app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/cryptomator-ios/issues) and [Cryptomator for Android issues list](https://github.com/cryptomator/cryptomator-android/issues) respectively.
|
||||||
- Ensure the bug was not [already reported](https://github.com/cryptomator/cryptomator/issues). You can also check out our [knowledge base](https://cryptomator.freshdesk.com/support/solutions) and our [Wiki](https://github.com/cryptomator/cryptomator/wiki).
|
- Ensure the bug was not [already reported](https://github.com/cryptomator/cryptomator/issues). You can also check out our [FAQ](https://community.cryptomator.org/c/kb/faq).
|
||||||
- If you're unable to find an open issue addressing the problem, [submit a new one](https://github.com/cryptomator/cryptomator/issues/new).
|
- If you're unable to find an open issue addressing the problem, [submit a new one](https://github.com/cryptomator/cryptomator/issues/new).
|
||||||
|
|
||||||
## Do you have questions?
|
|
||||||
|
|
||||||
- Ask questions by [submitting a new issue](https://github.com/cryptomator/cryptomator/issues/new).
|
|
||||||
- [Contact us](https://cryptomator.org/contact/) directly by writing an email. Wir sprechen auch Deutsch!
|
|
||||||
- Have a chat with us on [Gitter](https://gitter.im/cryptomator/cryptomator).
|
|
||||||
|
|
||||||
## Do you miss a feature?
|
|
||||||
|
|
||||||
- Ensure the feature was not [already requested](https://github.com/cryptomator/cryptomator/issues).
|
|
||||||
- You're welcome to suggest a feature by [submitting a new issue](https://github.com/cryptomator/cryptomator/issues/new).
|
|
||||||
|
|
||||||
## Did you write a patch that fixes a bug?
|
## Did you write a patch that fixes a bug?
|
||||||
|
|
||||||
- Open a new pull request with the patch.
|
- Open a new pull request with the patch.
|
||||||
@@ -29,7 +18,7 @@
|
|||||||
|
|
||||||
## Code of Conduct
|
## Code of Conduct
|
||||||
|
|
||||||
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/master/CODE_OF_CONDUCT.md).
|
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md).
|
||||||
|
|
||||||
## Above all, thank you for your contributions
|
## Above all, thank you for your contributions
|
||||||
|
|
||||||
31
.github/ISSUE_TEMPLATE.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
## Issue Checklist
|
||||||
|
|
||||||
|
Before creating a new issue make sure that you
|
||||||
|
- searched existing (and closed) issues: https://github.com/cryptomator/cryptomator/issues
|
||||||
|
- searched the knowledge base: https://community.cryptomator.org/c/kb
|
||||||
|
- have read the support guide: https://github.com/cryptomator/cryptomator/blob/develop/SUPPORT.md
|
||||||
|
- have read the contribution guide: https://github.com/cryptomator/cryptomator/blob/develop/.github/CONTRIBUTING.md
|
||||||
|
- have read the code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
|
||||||
|
|
||||||
|
## Basic Info
|
||||||
|
|
||||||
|
I'm using Windows / macOS / Linux / … in version: …
|
||||||
|
|
||||||
|
I'm running Cryptomator in version: …
|
||||||
|
(You can check the version in the Cryptomator settings.)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
…
|
||||||
|
(Please describe in detail what you did, what you expected, and what really happened.)
|
||||||
|
|
||||||
|
## Attachments (optional)
|
||||||
|
|
||||||
|
If you want to add the log file or screenshots, please add them as attachments. If your log file seems empty and doesn't show any errors, you may enable the debug mode first. Here is how to do that: https://community.cryptomator.org/t/how-do-i-enable-debug-mode/36
|
||||||
|
|
||||||
|
Then reproduce the problem to ensure all important information is contained in there. You may use test data or redact sensitive information from the log file.
|
||||||
|
|
||||||
|
You can find the log file
|
||||||
|
- on Windows: %appdata%/Cryptomator/cryptomator.log
|
||||||
|
- on macOS: ~/Library/Logs/Cryptomator/cryptomator.log
|
||||||
|
- on Linux: ~/.Cryptomator/cryptomator.log
|
||||||
15
.github/ISSUE_TEMPLATE/1.4.0-beta-testing.md
vendored
Normal file
15
.github/ISSUE_TEMPLATE/1.4.0-beta-testing.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
## 1.4.0 Beta Issue Checklist
|
||||||
|
- Existing 1.4.0 Beta Issues: https://github.com/cryptomator/cryptomator/milestone/27
|
||||||
|
- Contribution Guide: https://github.com/cryptomator/cryptomator/blob/develop/.github/CONTRIBUTING.md
|
||||||
|
- Code of Conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
|
||||||
|
|
||||||
|
## Software Used During the Test
|
||||||
|
- Cryptomator 1.4.0 Beta 1
|
||||||
|
- Ubuntu 16.04 / macOS 10.11.6 / etc
|
||||||
|
- Linux Kernel x.y.z
|
||||||
|
- Gnome x.y.z
|
||||||
|
- OpenOffice x.y.z
|
||||||
|
- ...
|
||||||
|
|
||||||
|
## Description
|
||||||
|
What doesn't work? What did you do? How can the bug be reproduced?
|
||||||
13
.github/no-response.yml
vendored
Normal file
13
.github/no-response.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Configuration for probot-no-response - https://github.com/probot/no-response
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue is closed for lack of response
|
||||||
|
daysUntilClose: 14
|
||||||
|
# Label requiring a response
|
||||||
|
responseRequiredLabel: state:awaiting-response
|
||||||
|
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
|
||||||
|
closeComment: >
|
||||||
|
This issue has been automatically closed because there has been no response
|
||||||
|
to our request for more information from the original author. With only the
|
||||||
|
information that is currently in the issue, we don't have enough information
|
||||||
|
to take action. Please reach out if you have or find the answers we need so
|
||||||
|
that we can investigate further.
|
||||||
19
.github/stale.yml
vendored
Normal file
19
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 60
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 7
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- type:security-issue # never close automatically
|
||||||
|
- state:awaiting-response # handled by different bot
|
||||||
|
# Set to true to ignore issues in a milestone (defaults to false)
|
||||||
|
exemptMilestones: true
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: state:stale
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
||||||
16
.gitignore
vendored
16
.gitignore
vendored
@@ -9,11 +9,13 @@
|
|||||||
.settings
|
.settings
|
||||||
.project
|
.project
|
||||||
.classpath
|
.classpath
|
||||||
target/
|
|
||||||
test-output/
|
|
||||||
|
|
||||||
# IntelliJ Settings Files #
|
# Maven #
|
||||||
.idea/
|
target/
|
||||||
out/
|
|
||||||
.idea_modules/
|
# IntelliJ Settings Files (https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems) #
|
||||||
*.iws
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/**/libraries/
|
||||||
|
*.iml
|
||||||
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Cryptomator
|
||||||
51
.idea/codeStyles/Project.xml
generated
Normal file
51
.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<option name="OTHER_INDENT_OPTIONS">
|
||||||
|
<value>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="LINE_SEPARATOR" value=" " />
|
||||||
|
<option name="RIGHT_MARGIN" value="220" />
|
||||||
|
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
||||||
|
<JavaCodeStyleSettings>
|
||||||
|
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="30" />
|
||||||
|
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="10" />
|
||||||
|
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||||
|
<value />
|
||||||
|
</option>
|
||||||
|
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
|
||||||
|
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
|
||||||
|
</JavaCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="Groovy">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="HTML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JAVA">
|
||||||
|
<option name="KEEP_LINE_BREAKS" value="false" />
|
||||||
|
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
|
||||||
|
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JSON">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
||||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
33
.idea/compiler.xml
generated
Normal file
33
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?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.20/dagger-compiler-2.20.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.20/dagger-2.20.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.20/dagger-producers-2.20.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/25.0-jre/guava-25.0-jre.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.3/checker-compat-qual-2.5.3.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.1.3/error_prone_annotations-2.1.3.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/org/codehaus/mojo/animal-sniffer-annotations/1.14/animal-sniffer-annotations-1.14.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.20/dagger-spi-2.20.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.11.1/javapoet-1.11.1.jar" />
|
||||||
|
<entry name="$MAVEN_REPOSITORY$/javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar" />
|
||||||
|
</processorPath>
|
||||||
|
<module name="commons" />
|
||||||
|
<module name="keychain" />
|
||||||
|
<module name="launcher" />
|
||||||
|
<module name="ui" />
|
||||||
|
</profile>
|
||||||
|
</annotationProcessing>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
.idea/encodings.xml
generated
Normal file
10
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/main" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/main/commons" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/main/keychain" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/main/launcher" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/main/ui" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
10
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
14
.idea/misc.xml
generated
Normal file
14
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/main/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_10" project-jdk-name="10" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
77
.travis.yml
77
.travis.yml
@@ -1,50 +1,73 @@
|
|||||||
language: java
|
language: java
|
||||||
sudo: required
|
sudo: false
|
||||||
dist: trusty
|
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk9
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- $HOME/.m2
|
- $HOME/.m2
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- secure: "IfYURwZaDWuBDvyn47n0k1Zod/IQw1FF+CS5nnV08Q+NfC3vGGJMwV8m59XnbfwnWGxwvCaAbk4qP6s6+ijgZNKkvgfFMo3rfTok5zt43bIqgaFOANYV+OC/1c59gYD6ZUxhW5iNgMgU3qdsRtJuwSmfkVv/jKyLGfAbS4kN8BA=" # COVERITY_SCAN_TOKEN
|
- secure: "HftEaabMmWn5GwKFKksUkOcelc3Mn7xazwAEy+4d4gL1+F8VhID/6DCK7nas+afUymWnxTano8Rv4Ci5MWryNkNkTH+FUPWmF3xWezc3hajSyS7RB92IZ8VPetl4Fo8UI1WwM5apDEaugalPxkIf8a7N+lpG5X/Gpumwzo3Be3w=" # BINTRAY_API_KEY
|
||||||
- secure: "lV9OwUbHMrMpLUH1CY+Z4puLDdFXytudyPlG1eGRsesdpuG6KM3uQVz6uAtf6lrU8DRbMM/T7ML+PmvQ4UoPPYLdLxESLLBat2qUPOIVBOhTSlCc7I0DmGy04CSvkeMy8dPaQC0ukgNiR7zwoNzfcpGRN/U9S8tziDruuHoZSrg=" # BINTRAY_API_KEY
|
|
||||||
- secure: "oWFgRTVP6lyTa7qVxlvkpm20MtVc3BtmsNXQJS6bfg2A0o/iCQMNx7OD59BaafCLGRKvCcJVESiC8FlSylVMS7CDSyYu0gg70NUiIuHp4NBM5inFWYCy/PdQsCTzr5uvNG+rMFQpMFRaCV0FrfM3tLondcVkhsHL68l93Xoexx4=" # CODACY_PROJECT_TOKEN
|
- secure: "oWFgRTVP6lyTa7qVxlvkpm20MtVc3BtmsNXQJS6bfg2A0o/iCQMNx7OD59BaafCLGRKvCcJVESiC8FlSylVMS7CDSyYu0gg70NUiIuHp4NBM5inFWYCy/PdQsCTzr5uvNG+rMFQpMFRaCV0FrfM3tLondcVkhsHL68l93Xoexx4=" # CODACY_PROJECT_TOKEN
|
||||||
|
- secure: "zJxgytA2Ks5Xzv+7kUaUq+EBFNQw9Qec63lcMJVuXVWczjL16nKW1EzzV515ag+OWL46z3lEPForDhufw0VtFnNmaX68jkO0mp01eLrHApc1llN2Y/U8GBXfNNazN4+Kom4H+z/AO+wJr8EsKMMUczCdQ3APgd9uVI0hzXw/Z3M=" # GITHUB_API_KEY
|
||||||
addons:
|
addons:
|
||||||
coverity_scan:
|
apt:
|
||||||
project:
|
packages:
|
||||||
name: "cryptomator/cryptomator"
|
- haveged
|
||||||
notification_email: sebastian.stenzel@cryptomator.org
|
|
||||||
build_command: "mvn -fmain/pom.xml clean test -DskipTests"
|
|
||||||
branch_pattern: release.*
|
|
||||||
install:
|
install:
|
||||||
# "clean" needed until https://bugs.openjdk.java.net/browse/JDK-8067747 is resolved.
|
- curl -o $HOME/.m2/settings.xml https://gist.githubusercontent.com/cryptobot/cf5fbd909c4782aaeeeb7c7f4a1a43da/raw/e60ee486e34ee0c79f89f947abe2c83b4290c6bb/settings.xml
|
||||||
- mvn -fmain/pom.xml clean package -DskipTests dependency:go-offline -Pcoverage
|
- mvn -fmain/pom.xml clean install -DskipTests org.codehaus.mojo:versions-maven-plugin:help dependency:go-offline -Pcoverage,release # "clean install" needed until we can exclude artifacts currently in the reactor, see https://maven.apache.org/plugins/maven-dependency-plugin/go-offline-mojo.html#excludeReactor and https://issues.apache.org/jira/browse/MDEP-568
|
||||||
- mvn -fmain/pom.xml clean package -DskipTests dependency:go-offline -Prelease
|
|
||||||
script:
|
script:
|
||||||
- mvn --update-snapshots -fmain/pom.xml clean test jacoco:report verify -Pcoverage
|
- mvn --update-snapshots -fmain/pom.xml clean test verify -Pcoverage
|
||||||
|
after_success:
|
||||||
|
- curl -o ~/codacy-coverage-reporter.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/4.0.2/codacy-coverage-reporter-4.0.2-assembly.jar
|
||||||
|
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial
|
||||||
|
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/keychain/target/site/jacoco/jacoco.xml --partial
|
||||||
|
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
|
||||||
|
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
|
||||||
|
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
|
||||||
before_deploy:
|
before_deploy:
|
||||||
- mvn -fmain/pom.xml -Prelease clean package -DskipTests
|
- |
|
||||||
|
if [[ -n "$TRAVIS_TAG" ]]; then
|
||||||
|
mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=$TRAVIS_TAG
|
||||||
|
elif [[ $TRAVIS_BRANCH == "develop" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]]; then
|
||||||
|
mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=SNAPSHOT-$(echo $TRAVIS_COMMIT | head -c7)
|
||||||
|
fi
|
||||||
|
- mvn -fmain/pom.xml clean package -Prelease -DskipTests
|
||||||
deploy:
|
deploy:
|
||||||
- provider: releases
|
- provider: script # SNAPSHOTS
|
||||||
|
skip_cleanup: true
|
||||||
|
script: >-
|
||||||
|
curl -T main/ant-kit/target/antkit.zip
|
||||||
|
-u cryptobot:${BINTRAY_API_KEY}
|
||||||
|
-H "X-Bintray-Package:ant-kit"
|
||||||
|
-H "X-Bintray-Version:continuous"
|
||||||
|
-H "X-Bintray-Override:1"
|
||||||
|
-H "X-Bintray-Publish:1"
|
||||||
|
https://api.bintray.com/content/cryptomator/cryptomator/antkit-continuous.zip
|
||||||
|
on:
|
||||||
|
repo: cryptomator/cryptomator
|
||||||
|
branch: develop
|
||||||
|
condition: $TRAVIS_TAG = ''
|
||||||
|
- provider: releases # RELEASE
|
||||||
prerelease: false
|
prerelease: false
|
||||||
api_key:
|
api_key: $GITHUB_API_KEY
|
||||||
secure: "ZjE1j93v3qbPIe2YbmhS319aCbMdLQw0HuymmluTurxXsZtn9D4t2+eTr99vBVxGRuB5lzzGezPR5zjk5W7iHF7xhwrawXrFzr2rPJWzWFt0aM+Ry2njU1ROTGGXGTbv4anWeBlgMxLEInTAy/9ytOGNJlec83yc0THpOY2wxnk="
|
|
||||||
file:
|
file:
|
||||||
- "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar"
|
- "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar"
|
||||||
- "main/ant-kit/target/antkit.tar.gz"
|
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
on:
|
on:
|
||||||
repo: cryptomator/cryptomator
|
repo: cryptomator/cryptomator
|
||||||
tags: true
|
tags: true
|
||||||
- provider: script
|
- provider: script
|
||||||
script: "curl -X POST -u cryptobot:${BINTRAY_API_KEY} -H 'Content-Type: application/json' -d '{\"name\": \"${TRAVIS_TAG}\", \"vcs_tag\": \"${TRAVIS_TAG}\"}' https://api.bintray.com/packages/cryptomator/cryptomator/cryptomator-win/versions"
|
skip_cleanup: true
|
||||||
on:
|
script: >-
|
||||||
repo: cryptomator/cryptomator
|
curl -T main/ant-kit/target/antkit.zip
|
||||||
tags: true
|
-u cryptobot:${BINTRAY_API_KEY}
|
||||||
- provider: script
|
-H "X-Bintray-Package:ant-kit"
|
||||||
script: "curl -X POST -u cryptobot:${BINTRAY_API_KEY} -H 'Content-Type: application/json' -d '{\"name\": \"${TRAVIS_TAG}\", \"vcs_tag\": \"${TRAVIS_TAG}\"}' https://api.bintray.com/packages/cryptomator/cryptomator/cryptomator-osx/versions"
|
-H "X-Bintray-Version:${TRAVIS_TAG}"
|
||||||
|
-H "X-Bintray-Override:1"
|
||||||
|
-H "X-Bintray-Publish:1"
|
||||||
|
https://api.bintray.com/content/cryptomator/cryptomator/antkit-${TRAVIS_TAG}.zip
|
||||||
on:
|
on:
|
||||||
repo: cryptomator/cryptomator
|
repo: cryptomator/cryptomator
|
||||||
tags: true
|
tags: true
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
To tick a checkbox replace [ ] with [x]. Make sure to replace placeholders (…) accordingly.
|
|
||||||
|
|
||||||
## Issue Checklist
|
|
||||||
|
|
||||||
Before creating a new issue make sure that you
|
|
||||||
- [ ] searched [existing (and closed) issues](https://github.com/cryptomator/cryptomator/issues).
|
|
||||||
- [ ] searched the [knowledge base](https://cryptomator.freshdesk.com/support/solutions).
|
|
||||||
- [ ] have read the [contribution guide](https://github.com/cryptomator/cryptomator/blob/master/CONTRIBUTING.md).
|
|
||||||
- [ ] have read the [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/master/CODE_OF_CONDUCT.md).
|
|
||||||
|
|
||||||
## Basic Info
|
|
||||||
|
|
||||||
This is a
|
|
||||||
- [ ] bug report.
|
|
||||||
- [ ] feature request.
|
|
||||||
- [ ] question or something else.
|
|
||||||
|
|
||||||
I'm using
|
|
||||||
- [ ] Windows in version: …
|
|
||||||
- [ ] macOS in version: …
|
|
||||||
- [ ] Linux in version: …
|
|
||||||
|
|
||||||
I'm running Cryptomator in version: …
|
|
||||||
(You can check the version in the Cryptomator settings.)
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
…
|
|
||||||
(Please describe in detail what you did, what you expected, and what really happened.)
|
|
||||||
|
|
||||||
## Attachments (optional)
|
|
||||||
|
|
||||||
If you want to add the log file or screenshots, please add them as attachments. If your log file seems empty and doesn't show any errors, you may enable the [debug mode](https://cryptomator.freshdesk.com/support/solutions/articles/16000046480) first and reproduce the problem to ensure all important information is contained in there. You may use test data or redact sensitive information from the log file.
|
|
||||||
|
|
||||||
You can find the log file
|
|
||||||
- on Windows: %appdata%/Cryptomator/cryptomator.log
|
|
||||||
- on macOS: ~/Library/Logs/Cryptomator/cryptomator.log
|
|
||||||
- on Linux: ~/.Cryptomator/cryptomator.log
|
|
||||||
17
README.md
17
README.md
@@ -1,11 +1,12 @@
|
|||||||

|

|
||||||
|
|
||||||
[](https://travis-ci.org/cryptomator/cryptomator)
|
[](https://travis-ci.org/cryptomator/cryptomator)
|
||||||
[](https://scan.coverity.com/projects/cryptomator-cryptomator)
|
[](https://snyk.io/test/github/cryptomator/cryptomator?targetFile=main%2Fpom.xml)
|
||||||
[](https://www.codacy.com/app/cryptomator/cryptomator?utm_source=github.com&utm_medium=referral&utm_content=cryptomator/cryptomator&utm_campaign=Badge_Grade)
|
[](https://www.codacy.com/app/cryptomator/cryptomator?utm_source=github.com&utm_medium=referral&utm_content=cryptomator/cryptomator&utm_campaign=Badge_Grade)
|
||||||
[](http://twitter.com/Cryptomator)
|
[](http://twitter.com/Cryptomator)
|
||||||
[](https://poeditor.com/join/project/bHwbvJmx0E)
|
[](https://poeditor.com/join/project/bHwbvJmx0E)
|
||||||
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
||||||
|
[](https://community.cryptomator.org)
|
||||||
|
|
||||||
Multi-platform transparent client-side encryption of your files in the cloud.
|
Multi-platform transparent client-side encryption of your files in the cloud.
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Works with Dropbox, Google Drive, OneDrive, Nextcloud and any other cloud storage service which synchronizes with a local directory
|
- Works with Dropbox, Google Drive, OneDrive, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
|
||||||
- Open Source means: No backdoors, control is better than trust
|
- Open Source means: No backdoors, control is better than trust
|
||||||
- Client-side: No accounts, no data shared with any online service
|
- Client-side: No accounts, no data shared with any online service
|
||||||
- Totally transparent: Just work on the virtual drive as if it were a USB flash drive
|
- Totally transparent: Just work on the virtual drive as if it were a USB flash drive
|
||||||
@@ -21,6 +22,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
|
|||||||
- File names get encrypted
|
- File names get encrypted
|
||||||
- Folder structure gets obfuscated
|
- Folder structure gets obfuscated
|
||||||
- Use as many vaults in your Dropbox as you want, each having individual passwords
|
- Use as many vaults in your Dropbox as you want, each having individual passwords
|
||||||
|
- One thousand commits for the security of your data!! :tada:
|
||||||
|
|
||||||
### Privacy
|
### Privacy
|
||||||
|
|
||||||
@@ -44,8 +46,7 @@ For more information on the security details visit [cryptomator.org](https://cry
|
|||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
* Java 8 (min. 8u51, we recommend to use the current version)
|
* Java 10 (min. 10.0.1, we recommend to use the current version)
|
||||||
* [JCE unlimited strength policy files](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html) (needed for 256-bit keys)
|
|
||||||
* Maven 3
|
* Maven 3
|
||||||
* Optional: OS-dependent build tools for native packaging (see [Windows](https://github.com/cryptomator/cryptomator-win), [OS X](https://github.com/cryptomator/cryptomator-osx), [Linux](https://github.com/cryptomator/builder-containers))
|
* Optional: OS-dependent build tools for native packaging (see [Windows](https://github.com/cryptomator/cryptomator-win), [OS X](https://github.com/cryptomator/cryptomator-osx), [Linux](https://github.com/cryptomator/builder-containers))
|
||||||
|
|
||||||
@@ -58,14 +59,6 @@ mvn clean install -Prelease
|
|||||||
|
|
||||||
An executable jar file will be created inside `main/uber-jar/target`.
|
An executable jar file will be created inside `main/uber-jar/target`.
|
||||||
|
|
||||||
## Contributing to Cryptomator
|
|
||||||
|
|
||||||
Please read our [contribution guide](https://github.com/cryptomator/cryptomator/blob/master/CONTRIBUTING.md), if you would like to report a bug, ask a question or help us with coding.
|
|
||||||
|
|
||||||
## Code of Conduct
|
|
||||||
|
|
||||||
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/master/CODE_OF_CONDUCT.md).
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is dual-licensed under the GPLv3 for FOSS projects as well as a commercial license for independent software vendors and resellers. If you want to modify this application under different conditions, feel free to contact our support team.
|
This project is dual-licensed under the GPLv3 for FOSS projects as well as a commercial license for independent software vendors and resellers. If you want to modify this application under different conditions, feel free to contact our support team.
|
||||||
|
|||||||
10
SUPPORT.md
Normal file
10
SUPPORT.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Support for Cryptomator
|
||||||
|
|
||||||
|
For development-related topics, GitHub is the right place.
|
||||||
|
|
||||||
|
For _everything else_, please visit our official [Cryptomator Community](https://community.cryptomator.org) (we are there, too :wink:). Amongst others, you will find:
|
||||||
|
|
||||||
|
- Installation manuals
|
||||||
|
- Usage guides
|
||||||
|
- Help with problems
|
||||||
|
- Tips & tricks
|
||||||
1
main/ant-kit/.gitignore
vendored
1
main/ant-kit/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
/target/
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.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>
|
||||||
<formats>
|
<formats>
|
||||||
<format>tar.gz</format>
|
<format>zip</format>
|
||||||
</formats>
|
</formats>
|
||||||
<fileSets>
|
<fileSets>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
<directory>target</directory>
|
<directory>target</directory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>build.xml</include>
|
<include>build.xml</include>
|
||||||
|
<include>logback.xml</include>
|
||||||
</includes>
|
</includes>
|
||||||
<filtered>false</filtered>
|
<filtered>false</filtered>
|
||||||
<outputDirectory>.</outputDirectory>
|
<outputDirectory>.</outputDirectory>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.3.0</version>
|
<version>1.4.3</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>ant-kit</artifactId>
|
<artifactId>ant-kit</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
@@ -56,15 +56,15 @@
|
|||||||
<resource>
|
<resource>
|
||||||
<directory>src/main/resources</directory>
|
<directory>src/main/resources</directory>
|
||||||
<filtering>true</filtering>
|
<filtering>true</filtering>
|
||||||
<excludes>
|
<includes>
|
||||||
<exclude>fixed-binaries/**</exclude>
|
<include>build.xml</include>
|
||||||
</excludes>
|
</includes>
|
||||||
</resource>
|
</resource>
|
||||||
<resource>
|
<resource>
|
||||||
<directory>src/main/resources</directory>
|
<directory>src/main/resources</directory>
|
||||||
<filtering>false</filtering>
|
<filtering>false</filtering>
|
||||||
<includes>
|
<includes>
|
||||||
<include>fixed-binaries/**</include>
|
<include>logback.xml</include>
|
||||||
</includes>
|
</includes>
|
||||||
</resource>
|
</resource>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -73,10 +73,10 @@
|
|||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<!-- create antkit.tar.gz: -->
|
<!-- create antkit.zip: -->
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.1.0</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>make-assembly</id>
|
<id>make-assembly</id>
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project name="Cryptomator" default="create-jar" basedir="." xmlns:fx="javafx:com.sun.javafx.tools.ant">
|
<project name="Cryptomator" default="create-jar" basedir="." xmlns:fx="javafx:com.sun.javafx.tools.ant">
|
||||||
<taskdef uri="javafx:com.sun.javafx.tools.ant" resource="com/sun/javafx/tools/ant/antlib.xml" classpath="\${java.class.path}:\${java.home}/../lib/ant-javafx.jar:." />
|
<taskdef uri="javafx:com.sun.javafx.tools.ant" resource="com/sun/javafx/tools/ant/antlib.xml" classpath="\${java.class.path}:\${java.home}/lib/ant-javafx.jar:." />
|
||||||
|
|
||||||
<!-- Define application to build -->
|
<!-- Define application to build -->
|
||||||
<fx:application id="Cryptomator" name="Cryptomator" version="${project.version}" mainClass="org.cryptomator.launcher.Cryptomator" />
|
<fx:application id="Cryptomator" name="Cryptomator" version="${project.version}" mainClass="org.cryptomator.launcher.Cryptomator" />
|
||||||
|
|
||||||
|
<!-- Print build environment properties -->
|
||||||
|
<target name="check-env">
|
||||||
|
<echoproperties/>
|
||||||
|
</target>
|
||||||
|
|
||||||
<!-- Create main application jar -->
|
<!-- Create main application jar -->
|
||||||
<target name="create-jar">
|
<target name="create-jar" depends="check-env">
|
||||||
<fx:jar destfile="antbuild/Cryptomator-${project.version}.jar">
|
<fx:jar destfile="antbuild/Cryptomator-${project.version}.jar">
|
||||||
<fx:application refid="Cryptomator" />
|
<fx:application refid="Cryptomator" />
|
||||||
<fx:fileset dir="libs" includes="launcher-${project.version}.jar" />
|
<fx:fileset dir="libs" includes="launcher-${project.version}.jar" />
|
||||||
@@ -20,54 +25,27 @@
|
|||||||
</fx:manifest>
|
</fx:manifest>
|
||||||
</fx:jar>
|
</fx:jar>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<!-- Create Debian package -->
|
<!-- Create Image -->
|
||||||
<target name="deb" depends="create-jar">
|
<target name="image" depends="create-jar">
|
||||||
<fx:deploy nativeBundles="deb" outdir="antbuild" outfile="Cryptomator-${project.version}" verbose="true">
|
<fx:deploy nativeBundles="image" outdir="antbuild" verbose="true">
|
||||||
<fx:application refid="Cryptomator" />
|
<fx:application refid="Cryptomator" />
|
||||||
<fx:info title="Cryptomator" vendor="cryptomator.org" copyright="cryptomator.org" license="MIT" category="Utility">
|
<fx:info title="Cryptomator" vendor="cryptomator.org" copyright="cryptomator.org" license="GPL" category="Utility"/>
|
||||||
<fx:association mimetype="application/x-vnd.cryptomator-vault-metadata" extension="cryptomator" description="Cryptomator Vault Metadata" />
|
<fx:platform j2se="10">
|
||||||
</fx:info>
|
<fx:property name="logback.configurationFile" value="\${antbuild.logback.configurationFile}" />
|
||||||
<fx:platform j2se="8.0">
|
<fx:property name="cryptomator.settingsPath" value="\${antbuild.cryptomator.settingsPath}" />
|
||||||
<fx:property name="logback.configurationFile" value="logback.xml" />
|
<fx:property name="cryptomator.ipcPortPath" value="\${antbuild.cryptomator.ipcPortPath}" />
|
||||||
<fx:property name="cryptomator.settingsPath" value="~/.Cryptomator/settings.json" />
|
<fx:property name="cryptomator.keychainPath" value="\${antbuild.cryptomator.keychainPath}"/>
|
||||||
<fx:property name="cryptomator.ipcPortPath" value="~/.Cryptomator/ipcPort.bin" />
|
|
||||||
<fx:jvmarg value="-Xss2m"/>
|
<fx:jvmarg value="-Xss2m"/>
|
||||||
<fx:jvmarg value="-Xmx512m"/>
|
<fx:jvmarg value="-Xmx512m"/>
|
||||||
</fx:platform>
|
</fx:platform>
|
||||||
<fx:resources>
|
<fx:resources>
|
||||||
<fx:fileset dir="." type="data" includes="logback.xml" />
|
|
||||||
<fx:fileset dir="antbuild" type="jar" includes="Cryptomator-${project.version}.jar" />
|
<fx:fileset dir="antbuild" type="jar" includes="Cryptomator-${project.version}.jar" />
|
||||||
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar"/>
|
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar"/>
|
||||||
<fx:fileset dir="fixed-binaries" type="data" includes="linux-launcher-*" arch=""/>
|
|
||||||
</fx:resources>
|
|
||||||
<fx:permissions elevated="false" />
|
|
||||||
<fx:preferences install="true" />
|
|
||||||
</fx:deploy>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<!-- Create Red Hat package -->
|
|
||||||
<target name="rpm" depends="create-jar">
|
|
||||||
<fx:deploy nativeBundles="rpm" outdir="antbuild" outfile="Cryptomator-${project.version}" verbose="true">
|
|
||||||
<fx:application refid="Cryptomator" />
|
|
||||||
<fx:info title="Cryptomator" vendor="cryptomator.org" copyright="cryptomator.org" license="MIT" category="Utility">
|
|
||||||
<fx:association mimetype="application/x-vnd.cryptomator-vault-metadata" extension="cryptomator" description="Cryptomator Vault Metadata" />
|
|
||||||
</fx:info>
|
|
||||||
<fx:platform j2se="8.0">
|
|
||||||
<fx:property name="logback.configurationFile" value="logback.xml" />
|
|
||||||
<fx:property name="cryptomator.settingsPath" value="~/.Cryptomator/settings.json" />
|
|
||||||
<fx:property name="cryptomator.ipcPortPath" value="~/.Cryptomator/ipcPort.bin" />
|
|
||||||
<fx:jvmarg value="-Xss2m"/>
|
|
||||||
<fx:jvmarg value="-Xmx512m"/>
|
|
||||||
</fx:platform>
|
|
||||||
<fx:resources>
|
|
||||||
<fx:fileset dir="." type="data" includes="logback.xml" />
|
|
||||||
<fx:fileset dir="antbuild" type="jar" includes="Cryptomator-${project.version}.jar" />
|
|
||||||
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar"/>
|
|
||||||
<fx:fileset dir="fixed-binaries" type="data" includes="linux-launcher-*" arch=""/>
|
|
||||||
</fx:resources>
|
</fx:resources>
|
||||||
<fx:permissions elevated="false" />
|
<fx:permissions elevated="false" />
|
||||||
<fx:preferences install="true" />
|
<fx:preferences install="true" />
|
||||||
|
<fx:bundleArgument arg="dropinResourcesRoot" value="\${antbuild.dropinResourcesRoot}"/>
|
||||||
</fx:deploy>
|
</fx:deploy>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,44 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!DOCTYPE xml>
|
|
||||||
<configuration scan="true" debug="true">
|
|
||||||
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
|
||||||
<file>${user.home}/.Cryptomator/cryptomator.log</file>
|
|
||||||
<append>false</append>
|
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
|
|
||||||
<fileNamePattern>${user.home}/.Cryptomator/cryptomator%i.log</fileNamePattern>
|
|
||||||
<minIndex>0</minIndex>
|
|
||||||
<maxIndex>9</maxIndex>
|
|
||||||
</rollingPolicy>
|
|
||||||
<triggeringPolicy class="org.cryptomator.logging.LaunchBasedTriggeringPolicy" />
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<appender name="UPGRADE_FILE" class="ch.qos.logback.core.FileAppender">
|
|
||||||
<file>${user.home}/.Cryptomator/upgrade.log</file>
|
|
||||||
<append>true</append>
|
|
||||||
<encoder>
|
|
||||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<logger name="org.cryptomator" level="INFO" />
|
|
||||||
<logger name="org.eclipse.jetty.server.HttpChannel" level="INFO" />
|
|
||||||
<logger name="org.cryptomator.ui.model" level="INFO">
|
|
||||||
<appender-ref ref="UPGRADE_FILE" />
|
|
||||||
</logger>
|
|
||||||
|
|
||||||
<root level="INFO">
|
|
||||||
<appender-ref ref="STDOUT" />
|
|
||||||
<appender-ref ref="FILE" />
|
|
||||||
</root>
|
|
||||||
|
|
||||||
</configuration>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
Package: APPLICATION_PACKAGE
|
|
||||||
Version: APPLICATION_VERSION
|
|
||||||
Section: contrib/utils
|
|
||||||
Maintainer: Sebastian Stenzel <sebastian.stenzel@gmail.com>
|
|
||||||
Homepage: https://cryptomator.org
|
|
||||||
Vcs-Git: https://github.com/totalvoidness/cryptomator.git
|
|
||||||
Vcs-Browser: https://github.com/totalvoidness/cryptomator
|
|
||||||
Priority: optional
|
|
||||||
Architecture: APPLICATION_ARCH
|
|
||||||
Provides: APPLICATION_PACKAGE
|
|
||||||
Installed-Size: APPLICATION_INSTALLED_SIZE
|
|
||||||
Depends: gvfs-bin, gvfs-backends, gvfs-fuse
|
|
||||||
Description: Multi-platform client-side encryption of your cloud files.
|
|
||||||
Cryptomator provides free client-side AES encryption for your cloud files.
|
|
||||||
Create encrypted vaults, which get mounted as virtual volumes. Whatever
|
|
||||||
you save on one of these volumes will end up encrypted inside your vault.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
||||||
Upstream-Name: cryptomator
|
|
||||||
Source: <https://github.com/totalvoidness/cryptomator>
|
|
||||||
|
|
||||||
Copyright: 2015 Sebastian Stenzel <sebastian.stenzel@gmail.com> and contributors.
|
|
||||||
License: MIT
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of this software and associated documentation files (the "Software"),
|
|
||||||
to deal in the Software without restriction, including without limitation
|
|
||||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following conditions:
|
|
||||||
.
|
|
||||||
The above copyright notice and this permission notice shall be included
|
|
||||||
in all copies or substantial portions of the Software.
|
|
||||||
.
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# postinst script for APPLICATION_NAME
|
|
||||||
#
|
|
||||||
# see: dh_installdeb(1)
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# summary of how this script can be called:
|
|
||||||
# * <postinst> `configure' <most-recently-configured-version>
|
|
||||||
# * <old-postinst> `abort-upgrade' <new version>
|
|
||||||
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
|
|
||||||
# <new-version>
|
|
||||||
# * <postinst> `abort-remove'
|
|
||||||
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
|
|
||||||
# <failed-install-package> <version> `removing'
|
|
||||||
# <conflicting-package> <version>
|
|
||||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
|
||||||
# the debian-policy package
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
configure)
|
|
||||||
echo Adding shortcut to the menu
|
|
||||||
SECONDARY_LAUNCHERS_INSTALL
|
|
||||||
APP_CDS_CACHE
|
|
||||||
mkdir -pm 644 /usr/share/desktop-directories
|
|
||||||
xdg-desktop-menu install --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
|
|
||||||
FILE_ASSOCIATION_INSTALL
|
|
||||||
|
|
||||||
rm /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
|
||||||
if [ $(uname -m) = "x86_64" ]; then
|
|
||||||
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x64 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
|
||||||
else
|
|
||||||
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x86 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
|
|
||||||
abort-upgrade|abort-remove|abort-deconfigure)
|
|
||||||
;;
|
|
||||||
|
|
||||||
*)
|
|
||||||
echo "postinst called with unknown argument \`$1'" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# dh_installdeb will replace this with shell code automatically
|
|
||||||
# generated by other debhelper scripts.
|
|
||||||
|
|
||||||
#DEBHELPER#
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
Summary: APPLICATION_SUMMARY
|
|
||||||
Name: APPLICATION_PACKAGE
|
|
||||||
Version: APPLICATION_VERSION
|
|
||||||
Release: 1
|
|
||||||
License: APPLICATION_LICENSE_TYPE
|
|
||||||
Vendor: APPLICATION_VENDOR
|
|
||||||
Prefix: /opt
|
|
||||||
Provides: APPLICATION_PACKAGE
|
|
||||||
Requires: ld-linux.so.2 libX11.so.6 libXext.so.6 libXi.so.6 libXrender.so.1 libXtst.so.6 libasound.so.2 libc.so.6 libdl.so.2 libgcc_s.so.1 libm.so.6 libpthread.so.0 libthread_db.so.1
|
|
||||||
Autoprov: 0
|
|
||||||
Autoreq: 0
|
|
||||||
|
|
||||||
#avoid ARCH subfolder
|
|
||||||
%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
|
|
||||||
|
|
||||||
#comment line below to enable effective jar compression
|
|
||||||
#it could easily get your package size from 40 to 15Mb but
|
|
||||||
#build time will substantially increase and it may require unpack200/system java to install
|
|
||||||
%define __jar_repack %{nil}
|
|
||||||
|
|
||||||
%description
|
|
||||||
APPLICATION_DESCRIPTION
|
|
||||||
|
|
||||||
%prep
|
|
||||||
|
|
||||||
%build
|
|
||||||
|
|
||||||
%install
|
|
||||||
rm -rf %{buildroot}
|
|
||||||
mkdir -p %{buildroot}/opt
|
|
||||||
cp -r %{_sourcedir}/APPLICATION_FS_NAME %{buildroot}/opt
|
|
||||||
|
|
||||||
%files
|
|
||||||
APPLICATION_LICENSE_FILE
|
|
||||||
/opt/APPLICATION_FS_NAME
|
|
||||||
|
|
||||||
%post
|
|
||||||
SECONDARY_LAUNCHERS_INSTALL
|
|
||||||
APP_CDS_CACHE
|
|
||||||
xdg-desktop-menu install --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
|
|
||||||
FILE_ASSOCIATION_INSTALL
|
|
||||||
rm /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
|
||||||
if [ $(uname -m) = "x86_64" ]; then
|
|
||||||
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x64 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
|
||||||
else
|
|
||||||
mv /opt/APPLICATION_FS_NAME/app/linux-launcher-x86 /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME
|
|
||||||
fi
|
|
||||||
|
|
||||||
%preun
|
|
||||||
SECONDARY_LAUNCHERS_REMOVE
|
|
||||||
xdg-desktop-menu uninstall --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
|
|
||||||
FILE_ASSOCIATION_REMOVE
|
|
||||||
|
|
||||||
%clean
|
|
||||||
@@ -1,11 +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"
|
<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">
|
||||||
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.3.0</version>
|
<version>1.4.3</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
<name>Cryptomator Commons</name>
|
<name>Cryptomator Commons</name>
|
||||||
@@ -29,13 +28,13 @@
|
|||||||
<groupId>org.fxmisc.easybind</groupId>
|
<groupId>org.fxmisc.easybind</groupId>
|
||||||
<artifactId>easybind</artifactId>
|
<artifactId>easybind</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- DI -->
|
<!-- DI -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.dagger</groupId>
|
<groupId>com.google.dagger</groupId>
|
||||||
<artifactId>dagger</artifactId>
|
<artifactId>dagger</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Logging -->
|
<!-- Logging -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
|
|||||||
@@ -2,41 +2,41 @@
|
|||||||
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
||||||
*
|
*
|
||||||
* Contributors:
|
* Contributors:
|
||||||
* Sebastian Stenzel - initial API and implementation
|
* Sebastian Stenzel - initial API and implementation
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import javafx.beans.property.*;
|
||||||
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
|
||||||
import javafx.beans.property.IntegerProperty;
|
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class Settings {
|
public class Settings {
|
||||||
|
|
||||||
public static final int MIN_PORT = 1024;
|
public static final int MIN_PORT = 1024;
|
||||||
public static final int MAX_PORT = 65535;
|
public static final int MAX_PORT = 65535;
|
||||||
public static final boolean DEFAULT_CHECK_FOR_UDPATES = true;
|
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
|
||||||
|
public static final boolean DEFAULT_CHECK_FOR_UDPATES = false;
|
||||||
public static final int DEFAULT_PORT = 42427;
|
public static final int DEFAULT_PORT = 42427;
|
||||||
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||||
public static final String DEFAULT_GVFS_SCHEME = "dav";
|
public static final String DEFAULT_GVFS_SCHEME = "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;
|
||||||
|
|
||||||
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
|
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
|
||||||
|
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
||||||
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
|
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
|
||||||
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
|
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
|
||||||
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
|
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
|
||||||
private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME);
|
private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME);
|
||||||
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 Consumer<Settings> saveCmd;
|
private Consumer<Settings> saveCmd;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,11 +44,13 @@ public class Settings {
|
|||||||
*/
|
*/
|
||||||
Settings() {
|
Settings() {
|
||||||
directories.addListener((ListChangeListener.Change<? extends VaultSettings> change) -> this.save());
|
directories.addListener((ListChangeListener.Change<? extends VaultSettings> change) -> this.save());
|
||||||
|
askedForUpdateCheck.addListener(this::somethingChanged);
|
||||||
checkForUpdates.addListener(this::somethingChanged);
|
checkForUpdates.addListener(this::somethingChanged);
|
||||||
port.addListener(this::somethingChanged);
|
port.addListener(this::somethingChanged);
|
||||||
numTrayNotifications.addListener(this::somethingChanged);
|
numTrayNotifications.addListener(this::somethingChanged);
|
||||||
preferredGvfsScheme.addListener(this::somethingChanged);
|
preferredGvfsScheme.addListener(this::somethingChanged);
|
||||||
debugMode.addListener(this::somethingChanged);
|
debugMode.addListener(this::somethingChanged);
|
||||||
|
preferredVolumeImpl.addListener(this::somethingChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||||
@@ -71,6 +73,10 @@ public class Settings {
|
|||||||
return directories;
|
return directories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty askedForUpdateCheck() {
|
||||||
|
return askedForUpdateCheck;
|
||||||
|
}
|
||||||
|
|
||||||
public BooleanProperty checkForUpdates() {
|
public BooleanProperty checkForUpdates() {
|
||||||
return checkForUpdates;
|
return checkForUpdates;
|
||||||
}
|
}
|
||||||
@@ -91,4 +97,8 @@ public class Settings {
|
|||||||
return debugMode;
|
return debugMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<VolumeImpl> preferredVolumeImpl() {
|
||||||
|
return preferredVolumeImpl;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,16 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.google.gson.TypeAdapter;
|
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 org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||||
|
|
||||||
@@ -28,11 +27,13 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
out.beginObject();
|
out.beginObject();
|
||||||
out.name("directories");
|
out.name("directories");
|
||||||
writeVaultSettingsArray(out, value.getDirectories());
|
writeVaultSettingsArray(out, value.getDirectories());
|
||||||
|
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
|
||||||
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
|
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
|
||||||
out.name("port").value(value.port().get());
|
out.name("port").value(value.port().get());
|
||||||
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
||||||
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get());
|
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get());
|
||||||
out.name("debugMode").value(value.debugMode().get());
|
out.name("debugMode").value(value.debugMode().get());
|
||||||
|
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,27 +53,33 @@ 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));
|
||||||
break;
|
break;
|
||||||
case "checkForUpdatesEnabled":
|
case "askedForUpdateCheck":
|
||||||
settings.checkForUpdates().set(in.nextBoolean());
|
settings.askedForUpdateCheck().set(in.nextBoolean());
|
||||||
break;
|
break;
|
||||||
case "port":
|
case "checkForUpdatesEnabled":
|
||||||
settings.port().set(in.nextInt());
|
settings.checkForUpdates().set(in.nextBoolean());
|
||||||
break;
|
break;
|
||||||
case "numTrayNotifications":
|
case "port":
|
||||||
settings.numTrayNotifications().set(in.nextInt());
|
settings.port().set(in.nextInt());
|
||||||
break;
|
break;
|
||||||
case "preferredGvfsScheme":
|
case "numTrayNotifications":
|
||||||
settings.preferredGvfsScheme().set(in.nextString());
|
settings.numTrayNotifications().set(in.nextInt());
|
||||||
break;
|
break;
|
||||||
case "debugMode":
|
case "preferredGvfsScheme":
|
||||||
settings.debugMode().set(in.nextBoolean());
|
settings.preferredGvfsScheme().set(in.nextString());
|
||||||
break;
|
break;
|
||||||
default:
|
case "debugMode":
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
settings.debugMode().set(in.nextBoolean());
|
||||||
in.skipValue();
|
break;
|
||||||
|
case "preferredVolumeImpl":
|
||||||
|
settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||||
|
in.skipValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in.endObject();
|
in.endObject();
|
||||||
@@ -80,6 +87,14 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private VolumeImpl parsePreferredVolumeImplName(String nioAdapterName) {
|
||||||
|
try {
|
||||||
|
return VolumeImpl.valueOf(nioAdapterName);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return Settings.DEFAULT_PREFERRED_VOLUME_IMPL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<VaultSettings> readVaultSettingsArray(JsonReader in) throws IOException {
|
private List<VaultSettings> readVaultSettingsArray(JsonReader in) throws IOException {
|
||||||
List<VaultSettings> result = new ArrayList<>();
|
List<VaultSettings> result = new ArrayList<>();
|
||||||
in.beginArray();
|
in.beginArray();
|
||||||
|
|||||||
@@ -5,16 +5,7 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import com.google.common.base.Strings;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
@@ -22,20 +13,37 @@ import javafx.beans.property.SimpleBooleanProperty;
|
|||||||
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.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The settings specific to a single vault.
|
||||||
|
* TODO: Change the name of individualMountPath and its derivatives to customMountPath
|
||||||
|
*/
|
||||||
public class VaultSettings {
|
public class VaultSettings {
|
||||||
|
|
||||||
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
|
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
|
||||||
public static final boolean DEFAULT_MOUNT_AFTER_UNLOCK = true;
|
|
||||||
public static final boolean DEFAULT_REAVEAL_AFTER_MOUNT = true;
|
public static final boolean DEFAULT_REAVEAL_AFTER_MOUNT = true;
|
||||||
|
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
|
||||||
|
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
|
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
|
||||||
private final StringProperty mountName = new SimpleStringProperty();
|
private final StringProperty mountName = 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 mountAfterUnlock = new SimpleBooleanProperty(DEFAULT_MOUNT_AFTER_UNLOCK);
|
|
||||||
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 StringProperty individualMountPath = new SimpleStringProperty();
|
||||||
|
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
||||||
|
|
||||||
public VaultSettings(String id) {
|
public VaultSettings(String id) {
|
||||||
this.id = Objects.requireNonNull(id);
|
this.id = Objects.requireNonNull(id);
|
||||||
@@ -44,7 +52,7 @@ public class VaultSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Observable[] observables() {
|
Observable[] observables() {
|
||||||
return new Observable[] {path, mountName, winDriveLetter, unlockAfterStartup, mountAfterUnlock, revealAfterMount};
|
return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, usesIndividualMountPath, individualMountPath, usesReadOnlyMode};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deriveMountNameFromPath(Path path) {
|
private void deriveMountNameFromPath(Path path) {
|
||||||
@@ -115,14 +123,30 @@ public class VaultSettings {
|
|||||||
return unlockAfterStartup;
|
return unlockAfterStartup;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanProperty mountAfterUnlock() {
|
|
||||||
return mountAfterUnlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanProperty revealAfterMount() {
|
public BooleanProperty revealAfterMount() {
|
||||||
return revealAfterMount;
|
return revealAfterMount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty usesIndividualMountPath() {
|
||||||
|
return usesIndividualMountPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty individualMountPath() {
|
||||||
|
return individualMountPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> getIndividualMountPath() {
|
||||||
|
if (usesIndividualMountPath.get()) {
|
||||||
|
return Optional.ofNullable(Strings.emptyToNull(individualMountPath.get()));
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty usesReadOnlyMode() {
|
||||||
|
return usesReadOnlyMode;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hashcode/Equals */
|
/* Hashcode/Equals */
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5,14 +5,13 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import java.io.IOException;
|
import com.google.gson.stream.JsonReader;
|
||||||
import java.nio.file.Paths;
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.google.gson.stream.JsonReader;
|
import java.io.IOException;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
class VaultSettingsJsonAdapter {
|
class VaultSettingsJsonAdapter {
|
||||||
|
|
||||||
@@ -25,8 +24,10 @@ class VaultSettingsJsonAdapter {
|
|||||||
out.name("mountName").value(value.mountName().get());
|
out.name("mountName").value(value.mountName().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("mountAfterUnlock").value(value.mountAfterUnlock().get());
|
|
||||||
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
||||||
|
out.name("usesIndividualMountPath").value(value.usesIndividualMountPath().get());
|
||||||
|
out.name("individualMountPath").value(value.individualMountPath().get()); //TODO: should this always be written? ( because it could contain metadata, which the user may not want to save!)
|
||||||
|
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,39 +35,47 @@ class VaultSettingsJsonAdapter {
|
|||||||
String id = null;
|
String id = null;
|
||||||
String path = null;
|
String path = null;
|
||||||
String mountName = null;
|
String mountName = null;
|
||||||
|
String individualMountPath = null;
|
||||||
String winDriveLetter = null;
|
String winDriveLetter = null;
|
||||||
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
||||||
boolean mountAfterUnlock = VaultSettings.DEFAULT_MOUNT_AFTER_UNLOCK;
|
|
||||||
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
|
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
|
||||||
|
boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
||||||
|
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||||
|
|
||||||
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();
|
||||||
break;
|
break;
|
||||||
case "path":
|
case "path":
|
||||||
path = in.nextString();
|
path = in.nextString();
|
||||||
break;
|
break;
|
||||||
case "mountName":
|
case "mountName":
|
||||||
mountName = in.nextString();
|
mountName = in.nextString();
|
||||||
break;
|
break;
|
||||||
case "winDriveLetter":
|
case "winDriveLetter":
|
||||||
winDriveLetter = in.nextString();
|
winDriveLetter = in.nextString();
|
||||||
break;
|
break;
|
||||||
case "unlockAfterStartup":
|
case "unlockAfterStartup":
|
||||||
unlockAfterStartup = in.nextBoolean();
|
unlockAfterStartup = in.nextBoolean();
|
||||||
break;
|
break;
|
||||||
case "mountAfterUnlock":
|
case "revealAfterMount":
|
||||||
mountAfterUnlock = in.nextBoolean();
|
revealAfterMount = in.nextBoolean();
|
||||||
break;
|
break;
|
||||||
case "revealAfterMount":
|
case "usesIndividualMountPath":
|
||||||
revealAfterMount = in.nextBoolean();
|
usesIndividualMountPath = in.nextBoolean();
|
||||||
break;
|
break;
|
||||||
default:
|
case "individualMountPath":
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
individualMountPath = in.nextString();
|
||||||
in.skipValue();
|
break;
|
||||||
|
case "usesReadOnlyMode":
|
||||||
|
usesReadOnlyMode = in.nextBoolean();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||||
|
in.skipValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in.endObject();
|
in.endObject();
|
||||||
@@ -76,8 +85,10 @@ class VaultSettingsJsonAdapter {
|
|||||||
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.mountAfterUnlock().set(mountAfterUnlock);
|
|
||||||
vaultSettings.revealAfterMount().set(revealAfterMount);
|
vaultSettings.revealAfterMount().set(revealAfterMount);
|
||||||
|
vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath);
|
||||||
|
vaultSettings.individualMountPath().set(individualMountPath);
|
||||||
|
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
||||||
return vaultSettings;
|
return vaultSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public enum VolumeImpl {
|
||||||
|
WEBDAV("WebDAV"),
|
||||||
|
FUSE("FUSE"),
|
||||||
|
DOKANY("Dokany");
|
||||||
|
|
||||||
|
private String displayName;
|
||||||
|
|
||||||
|
VolumeImpl(String displayName) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a VolumeImpl by display name.
|
||||||
|
*
|
||||||
|
* @param displayName Display name of the VolumeImpl
|
||||||
|
* @return VolumeImpl with the given <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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -21,17 +21,17 @@ public class SettingsJsonAdapterTest {
|
|||||||
String json = "{\"directories\": [" + vault1Json + "," + vault2Json + "]," //
|
String json = "{\"directories\": [" + vault1Json + "," + vault2Json + "]," //
|
||||||
+ "\"checkForUpdatesEnabled\": true,"//
|
+ "\"checkForUpdatesEnabled\": true,"//
|
||||||
+ "\"port\": 8080,"//
|
+ "\"port\": 8080,"//
|
||||||
+ "\"useIpv6\": true,"//
|
+ "\"numTrayNotifications\": 42,"//
|
||||||
+ "\"numTrayNotifications\": 42}";
|
+ "\"preferredVolumeImpl\": \"FUSE\"}";
|
||||||
|
|
||||||
Settings settings = adapter.fromJson(json);
|
Settings settings = adapter.fromJson(json);
|
||||||
|
|
||||||
Assert.assertTrue(settings.checkForUpdates().get());
|
Assert.assertTrue(settings.checkForUpdates().get());
|
||||||
Assert.assertEquals(2, settings.getDirectories().size());
|
Assert.assertEquals(2, settings.getDirectories().size());
|
||||||
Assert.assertEquals(8080, settings.port().get());
|
Assert.assertEquals(8080, settings.port().get());
|
||||||
// Assert.assertTrue(settings.useIpv6().get()); temporarily ignored
|
|
||||||
Assert.assertEquals(42, settings.numTrayNotifications().get());
|
Assert.assertEquals(42, settings.numTrayNotifications().get());
|
||||||
Assert.assertEquals("dav", settings.preferredGvfsScheme().get());
|
Assert.assertEquals("dav", settings.preferredGvfsScheme().get());
|
||||||
|
Assert.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ 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}";
|
String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\"}";
|
||||||
JsonReader jsonReader = new JsonReader(new StringReader(json));
|
JsonReader jsonReader = new JsonReader(new StringReader(json));
|
||||||
|
|
||||||
VaultSettings vaultSettings = adapter.read(jsonReader);
|
VaultSettings vaultSettings = adapter.read(jsonReader);
|
||||||
@@ -28,6 +28,7 @@ public class VaultSettingsJsonAdapterTest {
|
|||||||
Assert.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
Assert.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
||||||
Assert.assertEquals("test", vaultSettings.mountName().get());
|
Assert.assertEquals("test", vaultSettings.mountName().get());
|
||||||
Assert.assertEquals("X", vaultSettings.winDriveLetter().get());
|
Assert.assertEquals("X", vaultSettings.winDriveLetter().get());
|
||||||
|
Assert.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
1
main/jacoco-report/.gitignore
vendored
1
main/jacoco-report/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
/target/
|
|
||||||
@@ -1,93 +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.3.0</version>
|
|
||||||
</parent>
|
|
||||||
<artifactId>jacoco-report</artifactId>
|
|
||||||
<name>Cryptomator Code Coverage Report</name>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<!-- all modules containing unit tests: -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>commons</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>keychain</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>ui</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>launcher</artifactId>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<!-- conflict with codacy-coverage-reporter -->
|
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
|
||||||
<artifactId>*</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- binary dependency used during build -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.codacy</groupId>
|
|
||||||
<artifactId>codacy-coverage-reporter</artifactId>
|
|
||||||
<version>1.0.13</version>
|
|
||||||
<classifier>assembly</classifier>
|
|
||||||
<exclusions>
|
|
||||||
<exclusion>
|
|
||||||
<groupId>*</groupId>
|
|
||||||
<artifactId>*</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
</exclusions>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.jacoco</groupId>
|
|
||||||
<artifactId>jacoco-maven-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>report-aggregate</id>
|
|
||||||
<phase>verify</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>report-aggregate</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
|
||||||
<artifactId>exec-maven-plugin</artifactId>
|
|
||||||
<version>1.5.0</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>verify</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>java</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<mainClass>com.codacy.CodacyCoverageReporter</mainClass>
|
|
||||||
<arguments>
|
|
||||||
<argument>-l</argument>
|
|
||||||
<argument>Java</argument>
|
|
||||||
<argument>-r</argument>
|
|
||||||
<argument>${project.build.directory}/site/jacoco-aggregate/jacoco.xml</argument>
|
|
||||||
</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
<project xmlns="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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.3.0</version>
|
<version>1.4.3</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>keychain</artifactId>
|
<artifactId>keychain</artifactId>
|
||||||
<name>System Keychain Access</name>
|
<name>System Keychain Access</name>
|
||||||
|
|||||||
@@ -8,17 +8,27 @@ package org.cryptomator.keychain;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.cryptomator.jni.JniModule;
|
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.ElementsIntoSet;
|
import dagger.multibindings.ElementsIntoSet;
|
||||||
|
import org.cryptomator.jni.JniFunctions;
|
||||||
|
import org.cryptomator.jni.MacFunctions;
|
||||||
|
import org.cryptomator.jni.WinFunctions;
|
||||||
|
|
||||||
@Module(includes = {JniModule.class})
|
@Module
|
||||||
public class KeychainModule {
|
public class KeychainModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
Optional<MacFunctions> provideOptionalMacFunctions() {
|
||||||
|
return JniFunctions.macFunctions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
Optional<WinFunctions> provideOptionalWinFunctions() {
|
||||||
|
return JniFunctions.winFunctions();
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@ElementsIntoSet
|
@ElementsIntoSet
|
||||||
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain) {
|
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain) {
|
||||||
|
|||||||
@@ -15,35 +15,35 @@ import org.cryptomator.jni.MacKeychainAccess;
|
|||||||
|
|
||||||
class MacSystemKeychainAccess implements KeychainAccessStrategy {
|
class MacSystemKeychainAccess implements KeychainAccessStrategy {
|
||||||
|
|
||||||
private final MacKeychainAccess keychain;
|
private final Optional<MacFunctions> macFunctions;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MacSystemKeychainAccess(Optional<MacFunctions> macFunctions) {
|
public MacSystemKeychainAccess(Optional<MacFunctions> macFunctions) {
|
||||||
if (macFunctions.isPresent()) {
|
this.macFunctions = macFunctions;
|
||||||
this.keychain = macFunctions.get().keychainAccess();
|
}
|
||||||
} else {
|
|
||||||
this.keychain = null;
|
private MacKeychainAccess keychain() {
|
||||||
}
|
return macFunctions.orElseThrow(IllegalStateException::new).keychainAccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void storePassphrase(String key, CharSequence passphrase) {
|
public void storePassphrase(String key, CharSequence passphrase) {
|
||||||
keychain.storePassword(key, passphrase);
|
keychain().storePassword(key, passphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public char[] loadPassphrase(String key) {
|
public char[] loadPassphrase(String key) {
|
||||||
return keychain.loadPassword(key);
|
return keychain().loadPassword(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported() {
|
public boolean isSupported() {
|
||||||
return SystemUtils.IS_OS_MAC_OSX && keychain != null;
|
return SystemUtils.IS_OS_MAC_OSX && macFunctions.isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deletePassphrase(String key) {
|
public void deletePassphrase(String key) {
|
||||||
keychain.deletePassword(key);
|
keychain().deletePassword(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.keychain;
|
package org.cryptomator.keychain;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
@@ -31,12 +29,6 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.jni.WinDataProtection;
|
|
||||||
import org.cryptomator.jni.WinFunctions;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
@@ -49,6 +41,13 @@ import com.google.gson.JsonSerializationContext;
|
|||||||
import com.google.gson.JsonSerializer;
|
import com.google.gson.JsonSerializer;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.jni.WinDataProtection;
|
||||||
|
import org.cryptomator.jni.WinFunctions;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||||
|
|
||||||
@@ -57,19 +56,15 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
|||||||
.registerTypeHierarchyAdapter(byte[].class, new ByteArrayJsonAdapter()) //
|
.registerTypeHierarchyAdapter(byte[].class, new ByteArrayJsonAdapter()) //
|
||||||
.disableHtmlEscaping().create();
|
.disableHtmlEscaping().create();
|
||||||
|
|
||||||
private final WinDataProtection dataProtection;
|
private final Optional<WinFunctions> winFunctions;
|
||||||
private final Path keychainPath;
|
private final Path keychainPath;
|
||||||
private Map<String, KeychainEntry> keychainEntries;
|
private Map<String, KeychainEntry> keychainEntries;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions) {
|
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions) {
|
||||||
if (winFunctions.isPresent()) {
|
this.winFunctions = winFunctions;
|
||||||
this.dataProtection = winFunctions.get().dataProtection();
|
|
||||||
} else {
|
|
||||||
this.dataProtection = null;
|
|
||||||
}
|
|
||||||
String keychainPathProperty = System.getProperty("cryptomator.keychainPath");
|
String keychainPathProperty = System.getProperty("cryptomator.keychainPath");
|
||||||
if (dataProtection != null && keychainPathProperty == null) {
|
if (keychainPathProperty == null) {
|
||||||
LOG.warn("Windows DataProtection module loaded, but no cryptomator.keychainPath property found.");
|
LOG.warn("Windows DataProtection module loaded, but no cryptomator.keychainPath property found.");
|
||||||
}
|
}
|
||||||
if (keychainPathProperty != null) {
|
if (keychainPathProperty != null) {
|
||||||
@@ -82,6 +77,10 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WinDataProtection dataProtection() {
|
||||||
|
return winFunctions.orElseThrow(IllegalStateException::new).dataProtection();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void storePassphrase(String key, CharSequence passphrase) {
|
public void storePassphrase(String key, CharSequence passphrase) {
|
||||||
loadKeychainEntriesIfNeeded();
|
loadKeychainEntriesIfNeeded();
|
||||||
@@ -90,7 +89,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
|||||||
buf.get(cleartext);
|
buf.get(cleartext);
|
||||||
KeychainEntry entry = new KeychainEntry();
|
KeychainEntry entry = new KeychainEntry();
|
||||||
entry.salt = generateSalt();
|
entry.salt = generateSalt();
|
||||||
entry.ciphertext = dataProtection.protect(cleartext, entry.salt);
|
entry.ciphertext = dataProtection().protect(cleartext, entry.salt);
|
||||||
Arrays.fill(buf.array(), (byte) 0x00);
|
Arrays.fill(buf.array(), (byte) 0x00);
|
||||||
Arrays.fill(cleartext, (byte) 0x00);
|
Arrays.fill(cleartext, (byte) 0x00);
|
||||||
keychainEntries.put(key, entry);
|
keychainEntries.put(key, entry);
|
||||||
@@ -104,7 +103,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
|||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
byte[] cleartext = dataProtection.unprotect(entry.ciphertext, entry.salt);
|
byte[] cleartext = dataProtection().unprotect(entry.ciphertext, entry.salt);
|
||||||
if (cleartext == null) {
|
if (cleartext == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -125,7 +124,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported() {
|
public boolean isSupported() {
|
||||||
return SystemUtils.IS_OS_WINDOWS && dataProtection != null && keychainPath != null;
|
return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && keychainPath != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] generateSalt() {
|
private byte[] generateSalt() {
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ public class KeychainModuleTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetKeychain() {
|
public void testGetKeychain() {
|
||||||
Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().jniModule(new TestJniModule()).keychainModule(new TestKeychainModule()).build().keychainAccess();
|
Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess();
|
||||||
Assert.assertTrue(keychainAccess.isPresent());
|
Assert.assertTrue(keychainAccess.isPresent());
|
||||||
Assert.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
|
Assert.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
|
||||||
|
keychainAccess.get().storePassphrase("test", "asd");
|
||||||
|
Assert.assertArrayEquals("asd".toCharArray(), keychainAccess.get().loadPassphrase("test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.keychain;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import org.cryptomator.jni.JniModule;
|
|
||||||
import org.cryptomator.jni.MacFunctions;
|
|
||||||
import org.cryptomator.jni.WinFunctions;
|
|
||||||
|
|
||||||
import dagger.Lazy;
|
|
||||||
|
|
||||||
public class TestJniModule extends JniModule {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<WinFunctions> winFunctions(Lazy<WinFunctions> winFunction) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<MacFunctions> macFunctions(Lazy<MacFunctions> winFunction) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.3.0</version>
|
<version>1.4.3</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>launcher</artifactId>
|
<artifactId>launcher</artifactId>
|
||||||
<name>Cryptomator Launcher</name>
|
<name>Cryptomator Launcher</name>
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public class Cryptomator {
|
|||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.error("Failed to initiate inter-process communication.", e);
|
LOG.error("Failed to initiate inter-process communication.", e);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
LOG.error("Error during startup", e);
|
||||||
}
|
}
|
||||||
System.exit(0); // end remaining non-daemon threads.
|
System.exit(0); // end remaining non-daemon threads.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
|
import java.awt.Desktop;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.FileSystem;
|
import java.nio.file.FileSystem;
|
||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
@@ -13,7 +14,6 @@ import java.nio.file.InvalidPathException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
import org.cryptomator.ui.util.EawtApplicationWrapper;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -24,11 +24,13 @@ class FileOpenRequestHandler {
|
|||||||
|
|
||||||
public FileOpenRequestHandler(BlockingQueue<Path> fileOpenRequests) {
|
public FileOpenRequestHandler(BlockingQueue<Path> fileOpenRequests) {
|
||||||
this.fileOpenRequests = fileOpenRequests;
|
this.fileOpenRequests = fileOpenRequests;
|
||||||
EawtApplicationWrapper.getApplication().ifPresent(app -> {
|
try {
|
||||||
app.setOpenFileHandler(files -> {
|
Desktop.getDesktop().setOpenFileHandler(e -> {
|
||||||
files.stream().map(File::toPath).forEach(fileOpenRequests::add);
|
e.getFiles().stream().map(File::toPath).forEach(fileOpenRequests::add);
|
||||||
});
|
});
|
||||||
});
|
} catch (UnsupportedOperationException e) {
|
||||||
|
LOG.info("Unable to setOpenFileHandler, probably not supported on this OS.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleLaunchArgs(String[] args) {
|
public void handleLaunchArgs(String[] args) {
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import java.io.Closeable;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.ReadableByteChannel;
|
||||||
import java.nio.channels.WritableByteChannel;
|
import java.nio.channels.WritableByteChannel;
|
||||||
@@ -17,6 +20,7 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.rmi.ConnectException;
|
import java.rmi.ConnectException;
|
||||||
|
import java.rmi.ConnectIOException;
|
||||||
import java.rmi.NotBoundException;
|
import java.rmi.NotBoundException;
|
||||||
import java.rmi.Remote;
|
import java.rmi.Remote;
|
||||||
import java.rmi.RemoteException;
|
import java.rmi.RemoteException;
|
||||||
@@ -55,22 +59,19 @@ abstract class InterProcessCommunicator implements InterProcessCommunicationProt
|
|||||||
// visible for testing
|
// visible for testing
|
||||||
static InterProcessCommunicator start(Path portFilePath, InterProcessCommunicationProtocol endpoint) throws IOException {
|
static InterProcessCommunicator start(Path portFilePath, InterProcessCommunicationProtocol endpoint) throws IOException {
|
||||||
System.setProperty("java.rmi.server.hostname", "localhost");
|
System.setProperty("java.rmi.server.hostname", "localhost");
|
||||||
// try to connect to existing server:
|
|
||||||
int port = readPort(portFilePath);
|
|
||||||
LOG.debug("Connecting to running process on TCP port {}...", port);
|
|
||||||
try {
|
try {
|
||||||
ClientCommunicator client = new ClientCommunicator(port);
|
// try to connect to existing server:
|
||||||
|
ClientCommunicator client = new ClientCommunicator(portFilePath);
|
||||||
LOG.trace("Connected to running process.");
|
LOG.trace("Connected to running process.");
|
||||||
return client;
|
return client;
|
||||||
} catch (ConnectException | NotBoundException e) {
|
} catch (ConnectException | ConnectIOException | NotBoundException e) {
|
||||||
LOG.debug("Did not find running process.");
|
LOG.debug("Could not connect to running process.");
|
||||||
// continue
|
// continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// spawn a new server:
|
// spawn a new server:
|
||||||
LOG.trace("Spawning new server...");
|
LOG.trace("Spawning new server...");
|
||||||
ServerCommunicator server = new ServerCommunicator(endpoint);
|
ServerCommunicator server = new ServerCommunicator(endpoint, portFilePath);
|
||||||
writePort(portFilePath, server.getPort());
|
|
||||||
LOG.debug("Server listening on port {}.", server.getPort());
|
LOG.debug("Server listening on port {}.", server.getPort());
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
@@ -79,7 +80,7 @@ abstract class InterProcessCommunicator implements InterProcessCommunicationProt
|
|||||||
final String settingsPathProperty = System.getProperty("cryptomator.ipcPortPath");
|
final String settingsPathProperty = System.getProperty("cryptomator.ipcPortPath");
|
||||||
if (settingsPathProperty == null) {
|
if (settingsPathProperty == null) {
|
||||||
LOG.warn("System property cryptomator.ipcPortPath not set.");
|
LOG.warn("System property cryptomator.ipcPortPath not set.");
|
||||||
return Paths.get("ipcPort.tmp");
|
return Paths.get(".ipcPort.tmp");
|
||||||
} else {
|
} else {
|
||||||
return Paths.get(replaceHomeDir(settingsPathProperty));
|
return Paths.get(replaceHomeDir(settingsPathProperty));
|
||||||
}
|
}
|
||||||
@@ -97,12 +98,30 @@ abstract class InterProcessCommunicator implements InterProcessCommunicationProt
|
|||||||
|
|
||||||
private final IpcProtocolRemote remote;
|
private final IpcProtocolRemote remote;
|
||||||
|
|
||||||
private ClientCommunicator(int port) throws ConnectException, NotBoundException, RemoteException {
|
private ClientCommunicator(Path portFilePath) throws ConnectException, NotBoundException, RemoteException {
|
||||||
if (port == 0) {
|
if (Files.notExists(portFilePath)) {
|
||||||
throw new ConnectException("Can not connect to port 0.");
|
throw new ConnectException("No IPC port file.");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int port = ClientCommunicator.readPort(portFilePath);
|
||||||
|
LOG.debug("Connecting to port {}...", port);
|
||||||
|
Registry registry = LocateRegistry.getRegistry("localhost", port, new ClientSocketFactory());
|
||||||
|
this.remote = (IpcProtocolRemote) registry.lookup(RMI_NAME);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ConnectException("Error reading IPC port file.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int readPort(Path portFilePath) throws IOException {
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||||
|
try (ReadableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.READ)) {
|
||||||
|
if (ch.read(buf) == Integer.BYTES) {
|
||||||
|
buf.flip();
|
||||||
|
return buf.getInt();
|
||||||
|
} else {
|
||||||
|
throw new IOException("Invalid IPC port file.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Registry registry = LocateRegistry.getRegistry("localhost", port);
|
|
||||||
this.remote = (IpcProtocolRemote) registry.lookup(RMI_NAME);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -131,8 +150,9 @@ abstract class InterProcessCommunicator implements InterProcessCommunicationProt
|
|||||||
private final ServerSocket socket;
|
private final ServerSocket socket;
|
||||||
private final Registry registry;
|
private final Registry registry;
|
||||||
private final IpcProtocolRemoteImpl remote;
|
private final IpcProtocolRemoteImpl remote;
|
||||||
|
private final Path portFilePath;
|
||||||
|
|
||||||
private ServerCommunicator(InterProcessCommunicationProtocol delegate) throws IOException {
|
private ServerCommunicator(InterProcessCommunicationProtocol delegate, Path portFilePath) throws IOException {
|
||||||
this.socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost"));
|
this.socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost"));
|
||||||
RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory();
|
RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory();
|
||||||
SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket);
|
SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket);
|
||||||
@@ -140,6 +160,20 @@ abstract class InterProcessCommunicator implements InterProcessCommunicationProt
|
|||||||
this.remote = new IpcProtocolRemoteImpl(delegate);
|
this.remote = new IpcProtocolRemoteImpl(delegate);
|
||||||
UnicastRemoteObject.exportObject(remote, 0);
|
UnicastRemoteObject.exportObject(remote, 0);
|
||||||
registry.rebind(RMI_NAME, remote);
|
registry.rebind(RMI_NAME, remote);
|
||||||
|
this.portFilePath = portFilePath;
|
||||||
|
ServerCommunicator.writePort(portFilePath, socket.getLocalPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writePort(Path portFilePath, int port) throws IOException {
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||||
|
buf.putInt(port);
|
||||||
|
buf.flip();
|
||||||
|
MoreFiles.createParentDirectories(portFilePath);
|
||||||
|
try (WritableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||||
|
if (ch.write(buf) != Integer.BYTES) {
|
||||||
|
throw new IOException("Did not write expected number of bytes.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -162,6 +196,7 @@ abstract class InterProcessCommunicator implements InterProcessCommunicationProt
|
|||||||
registry.unbind(RMI_NAME);
|
registry.unbind(RMI_NAME);
|
||||||
UnicastRemoteObject.unexportObject(remote, true);
|
UnicastRemoteObject.unexportObject(remote, true);
|
||||||
socket.close();
|
socket.close();
|
||||||
|
Files.deleteIfExists(portFilePath);
|
||||||
LOG.debug("Server shut down.");
|
LOG.debug("Server shut down.");
|
||||||
} catch (NotBoundException | IOException e) {
|
} catch (NotBoundException | IOException e) {
|
||||||
LOG.warn("Failed to close IPC Server.", e);
|
LOG.warn("Failed to close IPC Server.", e);
|
||||||
@@ -210,31 +245,30 @@ abstract class InterProcessCommunicator implements InterProcessCommunicationProt
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int readPort(Path path) throws IOException {
|
/**
|
||||||
if (Files.notExists(path)) {
|
* Creates client sockets with short timeouts.
|
||||||
return 0;
|
*/
|
||||||
}
|
private static class ClientSocketFactory implements RMIClientSocketFactory {
|
||||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
|
||||||
try (ReadableByteChannel ch = Files.newByteChannel(path, StandardOpenOption.READ)) {
|
@Override
|
||||||
if (ch.read(buf) == Integer.BYTES) {
|
public Socket createSocket(String host, int port) throws IOException {
|
||||||
buf.flip();
|
return new SocketWithFixedTimeout(host, port, 1000);
|
||||||
return buf.getInt();
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void writePort(Path path, int port) throws IOException {
|
private static class SocketWithFixedTimeout extends Socket {
|
||||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
|
||||||
buf.putInt(port);
|
public SocketWithFixedTimeout(String host, int port, int timeoutInMs) throws UnknownHostException, IOException {
|
||||||
buf.flip();
|
super(host, port);
|
||||||
MoreFiles.createParentDirectories(path);
|
super.setSoTimeout(timeoutInMs);
|
||||||
try (WritableByteChannel ch = Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
|
||||||
if (ch.write(buf) != Integer.BYTES) {
|
|
||||||
throw new IOException("Did not write expected number of bytes.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setSoTimeout(int timeout) throws SocketException {
|
||||||
|
// do nothing, timeout is fixed
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,25 +5,23 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.stage.Stage;
|
||||||
import org.cryptomator.ui.controllers.MainController;
|
import org.cryptomator.ui.controllers.MainController;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javafx.application.Application;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
|
|
||||||
public class MainApplication extends Application {
|
public class MainApplication extends Application {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(MainApplication.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MainApplication.class);
|
||||||
private Stage primaryStage;
|
private Stage primaryStage;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) throws Exception {
|
public void start(Stage primaryStage) {
|
||||||
LOG.info("JavaFX application started.");
|
LOG.info("JavaFX application started.");
|
||||||
this.primaryStage = primaryStage;
|
this.primaryStage = primaryStage;
|
||||||
setupFXMLClassLoader();
|
primaryStage.setMinWidth(652.0);
|
||||||
|
primaryStage.setMinHeight(440.0);
|
||||||
|
|
||||||
LauncherModule launcherModule = new LauncherModule(this, primaryStage);
|
LauncherModule launcherModule = new LauncherModule(this, primaryStage);
|
||||||
LauncherComponent launcherComponent = DaggerLauncherComponent.builder() //
|
LauncherComponent launcherComponent = DaggerLauncherComponent.builder() //
|
||||||
@@ -34,30 +32,14 @@ public class MainApplication extends Application {
|
|||||||
|
|
||||||
MainController mainCtrl = launcherComponent.fxmlLoader().load("/fxml/main.fxml");
|
MainController mainCtrl = launcherComponent.fxmlLoader().load("/fxml/main.fxml");
|
||||||
mainCtrl.initStage(primaryStage);
|
mainCtrl.initStage(primaryStage);
|
||||||
|
|
||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() throws Exception {
|
public void stop() {
|
||||||
assert primaryStage != null;
|
assert primaryStage != null;
|
||||||
primaryStage.hide();
|
primaryStage.hide();
|
||||||
LOG.info("JavaFX application stopped.");
|
LOG.info("JavaFX application stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// fix discussed in https://github.com/cryptomator/cryptomator/pull/29
|
|
||||||
private void setupFXMLClassLoader() {
|
|
||||||
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
|
|
||||||
FXMLLoader.setDefaultClassLoader(contextClassLoader);
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
/*
|
|
||||||
* This fixes a bug on OSX where the magic file open handler leads to no context class loader being set in the AppKit (event)
|
|
||||||
* thread if the application is not started opening a file.
|
|
||||||
*/
|
|
||||||
if (Thread.currentThread().getContextClassLoader() == null) {
|
|
||||||
Thread.currentThread().setContextClassLoader(contextClassLoader);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ public class FileOpenRequestHandlerTest {
|
|||||||
Mockito.when(fs.provider()).thenReturn(provider);
|
Mockito.when(fs.provider()).thenReturn(provider);
|
||||||
Mockito.when(fs.getPath(Mockito.anyString())).thenReturn(p1, p2);
|
Mockito.when(fs.getPath(Mockito.anyString())).thenReturn(p1, p2);
|
||||||
Mockito.when(provider.readAttributes(Mockito.any(), Mockito.eq(BasicFileAttributes.class))).thenReturn(attrs);
|
Mockito.when(provider.readAttributes(Mockito.any(), Mockito.eq(BasicFileAttributes.class))).thenReturn(attrs);
|
||||||
Mockito.when(attrs.isRegularFile()).thenReturn(true);
|
|
||||||
|
|
||||||
BlockingQueue<Path> queue = new ArrayBlockingQueue<>(10);
|
BlockingQueue<Path> queue = new ArrayBlockingQueue<>(10);
|
||||||
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
|
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
|
||||||
|
|||||||
74
main/pom.xml
74
main/pom.xml
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.3.0</version>
|
<version>1.4.3</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>Cryptomator</name>
|
<name>Cryptomator</name>
|
||||||
|
|
||||||
@@ -24,27 +24,28 @@
|
|||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
<!-- dependency versions -->
|
<!-- dependency versions -->
|
||||||
<cryptomator.cryptolib.version>1.1.5</cryptomator.cryptolib.version>
|
<cryptomator.cryptolib.version>1.2.1</cryptomator.cryptolib.version>
|
||||||
<cryptomator.cryptofs.version>1.4.0</cryptomator.cryptofs.version>
|
<cryptomator.cryptofs.version>1.7.0</cryptomator.cryptofs.version>
|
||||||
<cryptomator.webdav.version>0.6.2</cryptomator.webdav.version>
|
<cryptomator.jni.version>2.0.0</cryptomator.jni.version>
|
||||||
<cryptomator.jni.version>1.0.2</cryptomator.jni.version>
|
<cryptomator.fuse.version>1.1.0</cryptomator.fuse.version>
|
||||||
|
<cryptomator.dokany.version>1.1.3</cryptomator.dokany.version>
|
||||||
<commons-io.version>2.5</commons-io.version>
|
<cryptomator.webdav.version>1.0.6</cryptomator.webdav.version>
|
||||||
<commons-lang3.version>3.6</commons-lang3.version>
|
|
||||||
<httpclient.version>4.5.3</httpclient.version>
|
<commons-io.version>2.6</commons-io.version>
|
||||||
|
<commons-lang3.version>3.8.1</commons-lang3.version>
|
||||||
|
|
||||||
<easybind.version>1.0.3</easybind.version>
|
<easybind.version>1.0.3</easybind.version>
|
||||||
|
|
||||||
<guava.version>22.0</guava.version>
|
<guava.version>27.0-jre</guava.version>
|
||||||
<dagger.version>2.11</dagger.version>
|
<dagger.version>2.20</dagger.version>
|
||||||
<gson.version>2.8.1</gson.version>
|
<gson.version>2.8.5</gson.version>
|
||||||
|
|
||||||
<slf4j.version>1.7.25</slf4j.version>
|
<slf4j.version>1.7.25</slf4j.version>
|
||||||
<logback.version>1.2.2</logback.version>
|
<logback.version>1.2.3</logback.version>
|
||||||
|
|
||||||
<junit.version>4.12</junit.version>
|
<junit.version>4.12</junit.version>
|
||||||
<junit.hierarchicalrunner.version>4.12.1</junit.hierarchicalrunner.version>
|
<junit.hierarchicalrunner.version>4.12.1</junit.hierarchicalrunner.version>
|
||||||
<mockito.version>2.7.21</mockito.version>
|
<mockito.version>2.23.0</mockito.version>
|
||||||
<hamcrest.version>1.3</hamcrest.version> <!-- keep in sync with version required by JUnit -->
|
<hamcrest.version>1.3</hamcrest.version> <!-- keep in sync with version required by JUnit -->
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
@@ -59,6 +60,10 @@
|
|||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
</snapshots>
|
</snapshots>
|
||||||
</repository>
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>jcenter</id>
|
||||||
|
<url>http://jcenter.bintray.com</url>
|
||||||
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -96,6 +101,16 @@
|
|||||||
<artifactId>cryptofs</artifactId>
|
<artifactId>cryptofs</artifactId>
|
||||||
<version>${cryptomator.cryptofs.version}</version>
|
<version>${cryptomator.cryptofs.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.cryptomator</groupId>
|
||||||
|
<artifactId>fuse-nio-adapter</artifactId>
|
||||||
|
<version>${cryptomator.fuse.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.cryptomator</groupId>
|
||||||
|
<artifactId>dokany-nio-adapter</artifactId>
|
||||||
|
<version>${cryptomator.dokany.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>webdav-nio-adapter</artifactId>
|
<artifactId>webdav-nio-adapter</artifactId>
|
||||||
@@ -140,11 +155,6 @@
|
|||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>${commons-lang3.version}</version>
|
<version>${commons-lang3.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpclient</artifactId>
|
|
||||||
<version>${httpclient.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- EasyBind -->
|
<!-- EasyBind -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -245,9 +255,6 @@
|
|||||||
</profile>
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
<id>coverage</id>
|
<id>coverage</id>
|
||||||
<modules>
|
|
||||||
<module>jacoco-report</module>
|
|
||||||
</modules>
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
@@ -264,7 +271,7 @@
|
|||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-dependency-plugin</artifactId>
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
<version>3.0.1</version>
|
<version>3.1.0</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>copy-libs</id>
|
<id>copy-libs</id>
|
||||||
@@ -281,7 +288,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.jacoco</groupId>
|
<groupId>org.jacoco</groupId>
|
||||||
<artifactId>jacoco-maven-plugin</artifactId>
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
<version>0.7.9</version>
|
<version>0.8.2</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>prepare-agent</id>
|
<id>prepare-agent</id>
|
||||||
@@ -289,6 +296,12 @@
|
|||||||
<goal>prepare-agent</goal>
|
<goal>prepare-agent</goal>
|
||||||
</goals>
|
</goals>
|
||||||
</execution>
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>report</id>
|
||||||
|
<goals>
|
||||||
|
<goal>report</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
<configuration>
|
<configuration>
|
||||||
<excludes>
|
<excludes>
|
||||||
@@ -302,10 +315,9 @@
|
|||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.6.1</version>
|
<version>3.8.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>1.8</source>
|
<release>9</release>
|
||||||
<target>1.8</target>
|
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths>
|
||||||
<path>
|
<path>
|
||||||
<groupId>com.google.dagger</groupId>
|
<groupId>com.google.dagger</groupId>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.3.0</version>
|
<version>1.4.3</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>uber-jar</artifactId>
|
<artifactId>uber-jar</artifactId>
|
||||||
<name>Single über jar with all dependencies</name>
|
<name>Single über jar with all dependencies</name>
|
||||||
|
|||||||
@@ -4,33 +4,42 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.3.0</version>
|
<version>1.4.3</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>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>cryptofs</artifactId>
|
<artifactId>cryptofs</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>webdav-nio-adapter</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>jni</artifactId>
|
<artifactId>jni</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>keychain</artifactId>
|
<artifactId>fuse-nio-adapter</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.cryptomator</groupId>
|
||||||
|
<artifactId>dokany-nio-adapter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.cryptomator</groupId>
|
||||||
|
<artifactId>webdav-nio-adapter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- CryptoLib -->
|
<!-- CryptoLib -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
@@ -62,10 +71,6 @@
|
|||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpclient</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- DI -->
|
<!-- DI -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -86,5 +91,13 @@
|
|||||||
<artifactId>slf4j-simple</artifactId>
|
<artifactId>slf4j-simple</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Testing -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.jimfs</groupId>
|
||||||
|
<artifactId>jimfs</artifactId>
|
||||||
|
<version>1.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ public class ExitUtil {
|
|||||||
private final Localization localization;
|
private final Localization localization;
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final Optional<MacFunctions> macFunctions;
|
private final Optional<MacFunctions> macFunctions;
|
||||||
|
private TrayIcon trayIcon;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ExitUtil(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, Optional<MacFunctions> macFunctions) {
|
public ExitUtil(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, Optional<MacFunctions> macFunctions) {
|
||||||
@@ -82,7 +83,7 @@ public class ExitUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initTrayIconExitHandler(Runnable exitCommand) {
|
private void initTrayIconExitHandler(Runnable exitCommand) {
|
||||||
final TrayIcon trayIcon = createTrayIcon(exitCommand);
|
trayIcon = createTrayIcon(exitCommand);
|
||||||
try {
|
try {
|
||||||
// double clicking tray icon should open Cryptomator
|
// double clicking tray icon should open Cryptomator
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
@@ -118,14 +119,7 @@ public class ExitUtil {
|
|||||||
exitItem.addActionListener(e -> exitCommand.run());
|
exitItem.addActionListener(e -> exitCommand.run());
|
||||||
popup.add(exitItem);
|
popup.add(exitItem);
|
||||||
|
|
||||||
final Image image;
|
final Image image = getAppropriateTrayIconImage(true);
|
||||||
if (SystemUtils.IS_OS_MAC_OSX && isMacMenuBarDarkMode()) {
|
|
||||||
image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/tray_icon_mac_white.png"));
|
|
||||||
} else if (SystemUtils.IS_OS_MAC_OSX) {
|
|
||||||
image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/tray_icon_mac_black.png"));
|
|
||||||
} else {
|
|
||||||
image = Toolkit.getDefaultToolkit().getImage(getClass().getResource("/tray_icon.png"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new TrayIcon(image, localization.getString("app.name"), popup);
|
return new TrayIcon(image, localization.getString("app.name"), popup);
|
||||||
}
|
}
|
||||||
@@ -202,4 +196,23 @@ public class ExitUtil {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateTrayIcon(boolean areAllVaultsLocked) {
|
||||||
|
if (trayIcon != null) {
|
||||||
|
Image image = getAppropriateTrayIconImage(areAllVaultsLocked);
|
||||||
|
trayIcon.setImage(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image getAppropriateTrayIconImage(boolean areAllVaultsLocked) {
|
||||||
|
String resourceName;
|
||||||
|
if (SystemUtils.IS_OS_MAC_OSX && isMacMenuBarDarkMode()) {
|
||||||
|
resourceName = areAllVaultsLocked ? "/tray_icon_mac_white.png" : "/tray_icon_unlocked_mac_white.png";
|
||||||
|
} else if (SystemUtils.IS_OS_MAC_OSX) {
|
||||||
|
resourceName = areAllVaultsLocked ? "/tray_icon_mac_black.png" : "/tray_icon_unlocked_mac_black.png";
|
||||||
|
} else {
|
||||||
|
resourceName = areAllVaultsLocked ? "/tray_icon.png" : "/tray_icon_unlocked.png";
|
||||||
|
}
|
||||||
|
return Toolkit.getDefaultToolkit().getImage(getClass().getResource(resourceName));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,39 +11,61 @@ package org.cryptomator.ui;
|
|||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import javafx.beans.binding.Binding;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.CommonsModule;
|
import org.cryptomator.common.CommonsModule;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.SettingsProvider;
|
import org.cryptomator.common.settings.SettingsProvider;
|
||||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||||
import org.cryptomator.jni.JniModule;
|
|
||||||
import org.cryptomator.keychain.KeychainModule;
|
import org.cryptomator.keychain.KeychainModule;
|
||||||
import org.cryptomator.ui.controllers.ViewControllerModule;
|
import org.cryptomator.ui.controllers.ViewControllerModule;
|
||||||
import org.cryptomator.ui.model.VaultComponent;
|
import org.cryptomator.ui.model.VaultComponent;
|
||||||
import org.fxmisc.easybind.EasyBind;
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
|
||||||
import dagger.Module;
|
@Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class})
|
||||||
import dagger.Provides;
|
|
||||||
import javafx.beans.binding.Binding;
|
|
||||||
|
|
||||||
@Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class, JniModule.class}, subcomponents = {VaultComponent.class})
|
|
||||||
public class UiModule {
|
public class UiModule {
|
||||||
|
|
||||||
|
private static final int NUM_SCHEDULER_THREADS = 4;
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
Settings provideSettings(SettingsProvider settingsProvider) {
|
Settings provideSettings(SettingsProvider settingsProvider) {
|
||||||
return settingsProvider.get();
|
return settingsProvider.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
||||||
|
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
|
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
|
||||||
|
Thread t = new Thread(r);
|
||||||
|
t.setName("Scheduler Thread " + threadNumber.getAndIncrement());
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
shutdownTaskScheduler.accept(executorService::shutdown);
|
||||||
|
return executorService;
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
||||||
ExecutorService executorService = Executors.newCachedThreadPool();
|
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
|
ExecutorService executorService = Executors.newCachedThreadPool(r -> {
|
||||||
|
Thread t = new Thread(r);
|
||||||
|
t.setName("Background Thread " + threadNumber.getAndIncrement());
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
shutdownTaskScheduler.accept(executorService::shutdown);
|
shutdownTaskScheduler.accept(executorService::shutdown);
|
||||||
return executorService;
|
return executorService;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import java.util.Objects;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
|
import javafx.beans.Observable;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||||
import org.cryptomator.ui.controls.SecPasswordField;
|
import org.cryptomator.ui.controls.SecPasswordField;
|
||||||
@@ -42,7 +42,6 @@ import javafx.scene.layout.GridPane;
|
|||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class ChangePasswordController implements ViewController {
|
public class ChangePasswordController implements ViewController {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
|
||||||
@@ -50,7 +49,7 @@ public class ChangePasswordController implements ViewController {
|
|||||||
private final Application app;
|
private final Application app;
|
||||||
private final PasswordStrengthUtil strengthRater;
|
private final PasswordStrengthUtil strengthRater;
|
||||||
private final Localization localization;
|
private final Localization localization;
|
||||||
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
|
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(-1); // 0-4
|
||||||
private Optional<ChangePasswordListener> listener = Optional.empty();
|
private Optional<ChangePasswordListener> listener = Optional.empty();
|
||||||
private Vault vault;
|
private Vault vault;
|
||||||
|
|
||||||
@@ -102,11 +101,9 @@ public class ChangePasswordController implements ViewController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
BooleanBinding oldPasswordIsEmpty = oldPasswordField.textProperty().isEmpty();
|
oldPasswordField.textProperty().addListener(this::passwordsChanged);
|
||||||
BooleanBinding newPasswordIsEmpty = newPasswordField.textProperty().isEmpty();
|
newPasswordField.textProperty().addListener(this::passwordsChanged);
|
||||||
BooleanBinding passwordsDiffer = newPasswordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
|
retypePasswordField.textProperty().addListener(this::passwordsChanged);
|
||||||
changePasswordButton.disableProperty().bind(oldPasswordIsEmpty.or(newPasswordIsEmpty.or(passwordsDiffer)));
|
|
||||||
passwordStrength.bind(EasyBind.map(newPasswordField.textProperty(), strengthRater::computeRate));
|
|
||||||
|
|
||||||
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
|
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
|
||||||
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
|
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
|
||||||
@@ -116,16 +113,26 @@ public class ChangePasswordController implements ViewController {
|
|||||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void passwordsChanged(Observable observable) {
|
||||||
|
boolean oldPasswordEmpty = oldPasswordField.getCharacters().length() == 0;
|
||||||
|
boolean newPasswordEmpty = newPasswordField.getCharacters().length() == 0;
|
||||||
|
boolean passwordsEqual = newPasswordField.getCharacters().equals(retypePasswordField.getCharacters());
|
||||||
|
changePasswordButton.setDisable(oldPasswordEmpty || newPasswordEmpty || !passwordsEqual);
|
||||||
|
passwordStrength.set(strengthRater.computeRate(newPasswordField.getCharacters().toString()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Parent getRoot() {
|
public Parent getRoot() {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void focus() {
|
||||||
|
oldPasswordField.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
void setVault(Vault vault) {
|
void setVault(Vault vault) {
|
||||||
this.vault = Objects.requireNonNull(vault);
|
this.vault = Objects.requireNonNull(vault);
|
||||||
oldPasswordField.swipe();
|
|
||||||
newPasswordField.swipe();
|
|
||||||
retypePasswordField.swipe();
|
|
||||||
// trigger "default" change to refresh key bindings:
|
// trigger "default" change to refresh key bindings:
|
||||||
changePasswordButton.setDefaultButton(false);
|
changePasswordButton.setDefaultButton(false);
|
||||||
changePasswordButton.setDefaultButton(true);
|
changePasswordButton.setDefaultButton(true);
|
||||||
|
|||||||
@@ -10,14 +10,15 @@
|
|||||||
package org.cryptomator.ui.controllers;
|
package org.cryptomator.ui.controllers;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
|
||||||
import java.nio.file.FileAlreadyExistsException;
|
import java.nio.file.FileAlreadyExistsException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
|
import javafx.beans.Observable;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.value.ObservableIntegerValue;
|
||||||
import org.cryptomator.ui.controls.SecPasswordField;
|
import org.cryptomator.ui.controls.SecPasswordField;
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
import org.cryptomator.ui.model.Vault;
|
import org.cryptomator.ui.model.Vault;
|
||||||
@@ -28,8 +29,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.property.IntegerProperty;
|
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
@@ -38,14 +39,13 @@ import javafx.scene.control.Label;
|
|||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class InitializeController implements ViewController {
|
public class InitializeController implements ViewController {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
|
||||||
|
|
||||||
private final Localization localization;
|
private final Localization localization;
|
||||||
private final PasswordStrengthUtil strengthRater;
|
private final PasswordStrengthUtil strengthRater;
|
||||||
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
|
private IntegerProperty passwordStrength = new SimpleIntegerProperty(-1); // strengths: 0-4
|
||||||
private Optional<InitializationListener> listener = Optional.empty();
|
private Optional<InitializationListener> listener = Optional.empty();
|
||||||
private Vault vault;
|
private Vault vault;
|
||||||
|
|
||||||
@@ -90,10 +90,8 @@ public class InitializeController implements ViewController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
BooleanBinding passwordIsEmpty = passwordField.textProperty().isEmpty();
|
passwordField.textProperty().addListener(this::passwordsChanged);
|
||||||
BooleanBinding passwordsDiffer = passwordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
|
retypePasswordField.textProperty().addListener(this::passwordsChanged);
|
||||||
okButton.disableProperty().bind(passwordIsEmpty.or(passwordsDiffer));
|
|
||||||
passwordStrength.bind(EasyBind.map(passwordField.textProperty(), strengthRater::computeRate));
|
|
||||||
|
|
||||||
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
|
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
|
||||||
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
|
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
|
||||||
@@ -103,15 +101,25 @@ public class InitializeController implements ViewController {
|
|||||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void passwordsChanged(Observable observable) {
|
||||||
|
boolean passwordsEmpty = passwordField.getCharacters().length() == 0;
|
||||||
|
boolean passwordsEqual = passwordField.getCharacters().equals(retypePasswordField.getCharacters());
|
||||||
|
okButton.setDisable(passwordsEmpty || !passwordsEqual);
|
||||||
|
passwordStrength.set(strengthRater.computeRate(passwordField.getCharacters().toString()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Parent getRoot() {
|
public Parent getRoot() {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void focus() {
|
||||||
|
passwordField.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
void setVault(Vault vault) {
|
void setVault(Vault vault) {
|
||||||
this.vault = Objects.requireNonNull(vault);
|
this.vault = Objects.requireNonNull(vault);
|
||||||
passwordField.swipe();
|
|
||||||
retypePasswordField.swipe();
|
|
||||||
// trigger "default" change to refresh key bindings:
|
// trigger "default" change to refresh key bindings:
|
||||||
okButton.setDefaultButton(false);
|
okButton.setDefaultButton(false);
|
||||||
okButton.setDefaultButton(true);
|
okButton.setDefaultButton(true);
|
||||||
@@ -129,8 +137,6 @@ public class InitializeController implements ViewController {
|
|||||||
listener.ifPresent(this::invokeListenerLater);
|
listener.ifPresent(this::invokeListenerLater);
|
||||||
} catch (FileAlreadyExistsException ex) {
|
} catch (FileAlreadyExistsException ex) {
|
||||||
messageLabel.setText(localization.getString("initialize.messageLabel.alreadyInitialized"));
|
messageLabel.setText(localization.getString("initialize.messageLabel.alreadyInitialized"));
|
||||||
} catch (DirectoryNotEmptyException ex) {
|
|
||||||
messageLabel.setText(localization.getString("initialize.messageLabel.notEmpty"));
|
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
LOG.error("I/O Exception", ex);
|
LOG.error("I/O Exception", ex);
|
||||||
messageLabel.setText(localization.getString("initialize.messageLabel.initializationFailed"));
|
messageLabel.setText(localization.getString("initialize.messageLabel.initializationFailed"));
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
||||||
*
|
*
|
||||||
* Contributors:
|
* Contributors:
|
||||||
* Sebastian Stenzel - initial API and implementation
|
* Sebastian Stenzel - initial API and implementation
|
||||||
* Jean-Noël Charon - confirmation dialog on vault removal
|
* Jean-Noël Charon - confirmation dialog on vault removal
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.cryptomator.ui.controllers;
|
package org.cryptomator.ui.controllers;
|
||||||
|
|
||||||
|
import java.awt.Desktop;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -20,30 +21,12 @@ import java.util.Map;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.ui.ExitUtil;
|
|
||||||
import org.cryptomator.ui.controls.DirectoryListCell;
|
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
|
||||||
import org.cryptomator.ui.model.AutoUnlocker;
|
|
||||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
|
||||||
import org.cryptomator.ui.model.UpgradeStrategy;
|
|
||||||
import org.cryptomator.ui.model.Vault;
|
|
||||||
import org.cryptomator.ui.model.VaultFactory;
|
|
||||||
import org.cryptomator.ui.model.VaultList;
|
|
||||||
import org.cryptomator.ui.util.DialogBuilderUtil;
|
|
||||||
import org.cryptomator.ui.util.EawtApplicationWrapper;
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
import org.fxmisc.easybind.Subscription;
|
|
||||||
import org.fxmisc.easybind.monadic.MonadicBinding;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Binding;
|
import javafx.beans.binding.Binding;
|
||||||
@@ -63,17 +46,40 @@ import javafx.scene.Scene;
|
|||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
|
import javafx.scene.control.Cell;
|
||||||
import javafx.scene.control.ContextMenu;
|
import javafx.scene.control.ContextMenu;
|
||||||
import javafx.scene.control.ListCell;
|
import javafx.scene.control.ListCell;
|
||||||
import javafx.scene.control.ListView;
|
import javafx.scene.control.ListView;
|
||||||
import javafx.scene.control.MenuItem;
|
import javafx.scene.control.MenuItem;
|
||||||
import javafx.scene.control.ToggleButton;
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.ui.ExitUtil;
|
||||||
|
import org.cryptomator.ui.controls.DirectoryListCell;
|
||||||
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
|
import org.cryptomator.ui.model.AutoUnlocker;
|
||||||
|
import org.cryptomator.ui.model.UpgradeStrategies;
|
||||||
|
import org.cryptomator.ui.model.UpgradeStrategy;
|
||||||
|
import org.cryptomator.ui.model.Vault;
|
||||||
|
import org.cryptomator.ui.model.VaultFactory;
|
||||||
|
import org.cryptomator.ui.model.VaultList;
|
||||||
|
import org.cryptomator.ui.util.DialogBuilderUtil;
|
||||||
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
import org.fxmisc.easybind.Subscription;
|
||||||
|
import org.fxmisc.easybind.monadic.MonadicBinding;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static org.cryptomator.ui.util.DialogBuilderUtil.buildErrorDialog;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class MainController implements ViewController {
|
public class MainController implements ViewController {
|
||||||
@@ -104,7 +110,7 @@ public class MainController implements ViewController {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("fileOpenRequests") BlockingQueue<Path> fileOpenRequests, ExitUtil exitUtil, Localization localization,
|
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("fileOpenRequests") BlockingQueue<Path> fileOpenRequests, ExitUtil exitUtil, Localization localization,
|
||||||
VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker) {
|
VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker) {
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.executorService = executorService;
|
this.executorService = executorService;
|
||||||
this.fileOpenRequests = fileOpenRequests;
|
this.fileOpenRequests = fileOpenRequests;
|
||||||
@@ -119,14 +125,17 @@ public class MainController implements ViewController {
|
|||||||
this.upgradeStrategyForSelectedVault = EasyBind.monadic(selectedVault).map(upgradeStrategies::getUpgradeStrategy);
|
this.upgradeStrategyForSelectedVault = EasyBind.monadic(selectedVault).map(upgradeStrategies::getUpgradeStrategy);
|
||||||
this.areAllVaultsLocked = Bindings.isEmpty(FXCollections.observableList(vaults, Vault::observables).filtered(Vault.NOT_LOCKED));
|
this.areAllVaultsLocked = Bindings.isEmpty(FXCollections.observableList(vaults, Vault::observables).filtered(Vault.NOT_LOCKED));
|
||||||
|
|
||||||
|
EasyBind.subscribe(areAllVaultsLocked, exitUtil::updateTrayIcon);
|
||||||
EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit);
|
EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit);
|
||||||
autoUnlocker.unlockAllSilently();
|
autoUnlocker.unlockAllSilently();
|
||||||
|
|
||||||
EawtApplicationWrapper.getApplication().ifPresent(app -> {
|
try {
|
||||||
app.setPreferencesHandler(() -> {
|
Desktop.getDesktop().setPreferencesHandler(e -> {
|
||||||
Platform.runLater(this::toggleShowSettings);
|
Platform.runLater(this::toggleShowSettings);
|
||||||
});
|
});
|
||||||
});
|
} catch (UnsupportedOperationException e) {
|
||||||
|
LOG.info("Unable to setPreferencesHandler, probably not supported on this OS.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -162,7 +171,10 @@ public class MainController implements ViewController {
|
|||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
vaultList.setItems(vaults);
|
vaultList.setItems(vaults);
|
||||||
|
vaultList.getSelectionModel().clearSelection();
|
||||||
|
vaultList.setOnKeyReleased(this::didPressKeyOnList);
|
||||||
vaultList.setCellFactory(this::createDirecoryListCell);
|
vaultList.setCellFactory(this::createDirecoryListCell);
|
||||||
|
root.setOnKeyReleased(this::didPressKeyOnRoot);
|
||||||
activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
|
activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
|
||||||
selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
|
selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
|
||||||
removeVaultButton.disableProperty().bind(canEditSelectedVault.not());
|
removeVaultButton.disableProperty().bind(canEditSelectedVault.not());
|
||||||
@@ -183,6 +195,7 @@ public class MainController implements ViewController {
|
|||||||
public void initStage(Stage stage) {
|
public void initStage(Stage stage) {
|
||||||
stage.setScene(new Scene(getRoot()));
|
stage.setScene(new Scene(getRoot()));
|
||||||
stage.sizeToScene();
|
stage.sizeToScene();
|
||||||
|
stage.setTitle(localization.getString("app.name")); // set once before bind to avoid display bugs with Linux window managers
|
||||||
stage.titleProperty().bind(windowTitle());
|
stage.titleProperty().bind(windowTitle());
|
||||||
stage.setResizable(false);
|
stage.setResizable(false);
|
||||||
loadFont("/css/ionicons.ttf");
|
loadFont("/css/ionicons.ttf");
|
||||||
@@ -192,9 +205,10 @@ public class MainController implements ViewController {
|
|||||||
subs = subs.and(EasyBind.includeWhen(mainWindow.getScene().getRoot().getStyleClass(), INACTIVE_WINDOW_STYLE_CLASS, mainWindow.focusedProperty().not()));
|
subs = subs.and(EasyBind.includeWhen(mainWindow.getScene().getRoot().getStyleClass(), INACTIVE_WINDOW_STYLE_CLASS, mainWindow.focusedProperty().not()));
|
||||||
Application.setUserAgentStylesheet(getClass().getResource("/css/mac_theme.css").toString());
|
Application.setUserAgentStylesheet(getClass().getResource("/css/mac_theme.css").toString());
|
||||||
} else if (SystemUtils.IS_OS_LINUX) {
|
} else if (SystemUtils.IS_OS_LINUX) {
|
||||||
|
stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon_512.png")));
|
||||||
Application.setUserAgentStylesheet(getClass().getResource("/css/linux_theme.css").toString());
|
Application.setUserAgentStylesheet(getClass().getResource("/css/linux_theme.css").toString());
|
||||||
} else if (SystemUtils.IS_OS_WINDOWS) {
|
} else if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon.png")));
|
stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon_32.png")));
|
||||||
Application.setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString());
|
Application.setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString());
|
||||||
}
|
}
|
||||||
exitUtil.initExitHandler(this::gracefulShutdown);
|
exitUtil.initExitHandler(this::gracefulShutdown);
|
||||||
@@ -203,7 +217,26 @@ public class MainController implements ViewController {
|
|||||||
|
|
||||||
private void gracefulShutdown() {
|
private void gracefulShutdown() {
|
||||||
vaults.filtered(Vault.NOT_LOCKED).forEach(Vault::prepareForShutdown);
|
vaults.filtered(Vault.NOT_LOCKED).forEach(Vault::prepareForShutdown);
|
||||||
Platform.runLater(Platform::exit);
|
if (!vaults.filtered(Vault.NOT_LOCKED).isEmpty()) {
|
||||||
|
ButtonType tryAgainButtonType = new ButtonType(localization.getString("main.gracefulShutdown.button.tryAgain"));
|
||||||
|
ButtonType forceShutdownButtonType = new ButtonType(localization.getString("main.gracefulShutdown.button.forceShutdown"));
|
||||||
|
Alert gracefulShutdownDialog = DialogBuilderUtil.buildGracefulShutdownDialog(
|
||||||
|
localization.getString("main.gracefulShutdown.dialog.title"), localization.getString("main.gracefulShutdown.dialog.header"), localization.getString("main.gracefulShutdown.dialog.content"),
|
||||||
|
forceShutdownButtonType, ButtonType.CANCEL, forceShutdownButtonType, tryAgainButtonType);
|
||||||
|
|
||||||
|
Optional<ButtonType> choice = gracefulShutdownDialog.showAndWait();
|
||||||
|
choice.ifPresent(btnType -> {
|
||||||
|
if (tryAgainButtonType.equals(btnType)) {
|
||||||
|
gracefulShutdown();
|
||||||
|
} else if (forceShutdownButtonType.equals(btnType)) {
|
||||||
|
Platform.runLater(Platform::exit);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Platform.runLater(Platform::exit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadFont(String resourcePath) {
|
private void loadFont(String resourcePath) {
|
||||||
@@ -236,6 +269,7 @@ public class MainController implements ViewController {
|
|||||||
private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
|
private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
|
||||||
final DirectoryListCell cell = new DirectoryListCell();
|
final DirectoryListCell cell = new DirectoryListCell();
|
||||||
cell.setVaultContextMenu(vaultListCellContextMenu);
|
cell.setVaultContextMenu(vaultListCellContextMenu);
|
||||||
|
cell.setOnMouseClicked(this::didClickOnListCell);
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +295,18 @@ public class MainController implements ViewController {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final Path vaultDir = file.toPath();
|
final Path vaultDir = file.toPath();
|
||||||
if (!Files.exists(vaultDir)) {
|
if (Files.exists(vaultDir)) {
|
||||||
|
try (Stream<Path> stream = Files.list(vaultDir)) {
|
||||||
|
if (stream.filter(this::isNotHidden).findAny().isPresent()) {
|
||||||
|
buildErrorDialog( //
|
||||||
|
localization.getString("main.createVault.nonEmptyDir.title"), //
|
||||||
|
localization.getString("main.createVault.nonEmptyDir.header"), //
|
||||||
|
localization.getString("main.createVault.nonEmptyDir.content"), //
|
||||||
|
ButtonType.OK).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Files.createDirectory(vaultDir);
|
Files.createDirectory(vaultDir);
|
||||||
}
|
}
|
||||||
addVault(vaultDir, true);
|
addVault(vaultDir, true);
|
||||||
@@ -270,6 +315,10 @@ public class MainController implements ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isNotHidden(Path file) {
|
||||||
|
return !file.getFileName().toString().startsWith(".");
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void didClickAddExistingVaults(ActionEvent event) {
|
private void didClickAddExistingVaults(ActionEvent event) {
|
||||||
final FileChooser fileChooser = new FileChooser();
|
final FileChooser fileChooser = new FileChooser();
|
||||||
@@ -284,14 +333,14 @@ public class MainController implements ViewController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* adds the given directory or selects it if it is already in the list of directories.
|
* adds the given directory or selects it if it is already in the list of directories.
|
||||||
*
|
*
|
||||||
* @param path to a vault directory or masterkey file
|
* @param path to a vault directory or masterkey file
|
||||||
*/
|
*/
|
||||||
public void addVault(final Path path, boolean select) {
|
public void addVault(final Path path, boolean select) {
|
||||||
final Path vaultPath;
|
final Path vaultPath;
|
||||||
if (path != null && Files.isDirectory(path)) {
|
if (path != null && Files.isDirectory(path)) {
|
||||||
vaultPath = path;
|
vaultPath = path;
|
||||||
} else if (path != null && Files.isRegularFile(path)) {
|
} else if (path != null && Files.isReadable(path)) {
|
||||||
vaultPath = path.getParent();
|
vaultPath = path.getParent();
|
||||||
} else {
|
} else {
|
||||||
LOG.warn("Ignoring attempt to add vault with invalid path: {}", path);
|
LOG.warn("Ignoring attempt to add vault with invalid path: {}", path);
|
||||||
@@ -309,6 +358,7 @@ public class MainController implements ViewController {
|
|||||||
}
|
}
|
||||||
if (select) {
|
if (select) {
|
||||||
vaultList.getSelectionModel().select(vault);
|
vaultList.getSelectionModel().select(vault);
|
||||||
|
activeController.get().focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,6 +375,8 @@ public class MainController implements ViewController {
|
|||||||
vaults.remove(selectedVault.get());
|
vaults.remove(selectedVault.get());
|
||||||
if (vaults.isEmpty()) {
|
if (vaults.isEmpty()) {
|
||||||
activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
|
activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
|
||||||
|
} else {
|
||||||
|
activeController.get().focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,18 +415,54 @@ public class MainController implements ViewController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (newValue.getState() != Vault.State.LOCKED) {
|
if (newValue.getState() != Vault.State.LOCKED) {
|
||||||
this.showUnlockedView(newValue);
|
this.showUnlockedView(newValue, false);
|
||||||
} else if (!newValue.doesVaultDirectoryExist()) {
|
} else if (!newValue.doesVaultDirectoryExist()) {
|
||||||
this.showNotFoundView();
|
this.showNotFoundView();
|
||||||
} else if (newValue.isValidVaultDirectory() && upgradeStrategyForSelectedVault.isPresent()) {
|
} else if (newValue.isValidVaultDirectory() && upgradeStrategyForSelectedVault.isPresent()) {
|
||||||
this.showUpgradeView();
|
this.showUpgradeView();
|
||||||
} else if (newValue.isValidVaultDirectory()) {
|
} else if (newValue.isValidVaultDirectory()) {
|
||||||
this.showUnlockView();
|
this.showUnlockView(UnlockController.State.UNLOCKING);
|
||||||
} else {
|
} else {
|
||||||
this.showInitializeView();
|
this.showInitializeView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void didPressKeyOnList(KeyEvent e) {
|
||||||
|
if (e.getCode() == KeyCode.ENTER || e.getCode() == KeyCode.SPACE) {
|
||||||
|
activeController.get().focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void didPressKeyOnRoot(KeyEvent event) {
|
||||||
|
boolean triggered;
|
||||||
|
if (SystemUtils.IS_OS_MAC) {
|
||||||
|
triggered = event.isMetaDown();
|
||||||
|
} else {
|
||||||
|
triggered = event.isControlDown() && !event.isAltDown();
|
||||||
|
}
|
||||||
|
if (triggered && event.getCode().isDigitKey()) {
|
||||||
|
int digit = Integer.valueOf(event.getText());
|
||||||
|
switch (digit) {
|
||||||
|
case 0: {
|
||||||
|
vaultList.getSelectionModel().clearSelection();
|
||||||
|
showWelcomeView();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
vaultList.getSelectionModel().select(digit - 1);
|
||||||
|
activeController.get().focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void didClickOnListCell(MouseEvent e) {
|
||||||
|
if (MouseEvent.MOUSE_CLICKED.equals(e.getEventType()) && e.getSource() instanceof Cell && ((Cell<?>) e.getSource()).isSelected()) {
|
||||||
|
activeController.get().focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ****************************************
|
// ****************************************
|
||||||
// Public Bindings
|
// Public Bindings
|
||||||
// ****************************************
|
// ****************************************
|
||||||
@@ -407,7 +495,8 @@ public class MainController implements ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void didInitialize() {
|
public void didInitialize() {
|
||||||
showUnlockView();
|
showUnlockView(UnlockController.State.INITIALIZED);
|
||||||
|
activeController.get().focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showUpgradeView() {
|
private void showUpgradeView() {
|
||||||
@@ -418,34 +507,39 @@ public class MainController implements ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void didUpgrade() {
|
public void didUpgrade() {
|
||||||
showUnlockView();
|
showUnlockView(UnlockController.State.UPGRADED);
|
||||||
|
activeController.get().focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showUnlockView() {
|
private void showUnlockView(UnlockController.State state) {
|
||||||
final UnlockController ctrl = viewControllerLoader.load("/fxml/unlock.fxml");
|
final UnlockController ctrl = viewControllerLoader.load("/fxml/unlock.fxml");
|
||||||
ctrl.setVault(selectedVault.get());
|
ctrl.setVault(selectedVault.get(), state);
|
||||||
ctrl.setListener(this::didUnlock);
|
ctrl.setListener(this::didUnlock);
|
||||||
activeController.set(ctrl);
|
activeController.set(ctrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void didUnlock(Vault vault) {
|
public void didUnlock(Vault vault) {
|
||||||
if (vault.equals(selectedVault.getValue())) {
|
if (vault.equals(selectedVault.getValue())) {
|
||||||
this.showUnlockedView(vault);
|
this.showUnlockedView(vault, vault.getVaultSettings().revealAfterMount().getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showUnlockedView(Vault vault) {
|
private void showUnlockedView(Vault vault, boolean reveal) {
|
||||||
final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> {
|
final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> viewControllerLoader.load("/fxml/unlocked.fxml"));
|
||||||
return viewControllerLoader.load("/fxml/unlocked.fxml");
|
|
||||||
});
|
|
||||||
ctrl.setVault(vault);
|
ctrl.setVault(vault);
|
||||||
ctrl.setListener(this::didLock);
|
ctrl.setListener(this::didLock);
|
||||||
|
if (reveal) {
|
||||||
|
ctrl.revealVault(vault);
|
||||||
|
}
|
||||||
activeController.set(ctrl);
|
activeController.set(ctrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void didLock(UnlockedController ctrl) {
|
public void didLock(UnlockedController ctrl) {
|
||||||
unlockedVaults.remove(ctrl.getVault());
|
unlockedVaults.remove(ctrl.getVault());
|
||||||
showUnlockView();
|
if (ctrl.getVault().getId() == selectedVault.get().getId()) {
|
||||||
|
showUnlockView(UnlockController.State.UNLOCKING);
|
||||||
|
}
|
||||||
|
activeController.get().focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showChangePasswordView() {
|
private void showChangePasswordView() {
|
||||||
@@ -453,10 +547,12 @@ public class MainController implements ViewController {
|
|||||||
ctrl.setVault(selectedVault.get());
|
ctrl.setVault(selectedVault.get());
|
||||||
ctrl.setListener(this::didChangePassword);
|
ctrl.setListener(this::didChangePassword);
|
||||||
activeController.set(ctrl);
|
activeController.set(ctrl);
|
||||||
|
Platform.runLater(ctrl::focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void didChangePassword() {
|
public void didChangePassword() {
|
||||||
showUnlockView();
|
showUnlockView(UnlockController.State.PASSWORD_CHANGED);
|
||||||
|
activeController.get().focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,28 +2,19 @@
|
|||||||
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
||||||
*
|
*
|
||||||
* Contributors:
|
* Contributors:
|
||||||
* Sebastian Stenzel - initial API and implementation
|
* Sebastian Stenzel - initial API and implementation
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.cryptomator.ui.controllers;
|
package org.cryptomator.ui.controllers;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
|
||||||
|
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
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.Bindings;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Group;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
@@ -32,6 +23,17 @@ import javafx.scene.control.Label;
|
|||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
|
import org.cryptomator.ui.model.Volume;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class SettingsController implements ViewController {
|
public class SettingsController implements ViewController {
|
||||||
@@ -47,11 +49,17 @@ public class SettingsController implements ViewController {
|
|||||||
this.localization = localization;
|
this.localization = localization;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.applicationVersion = applicationVersion;
|
this.applicationVersion = applicationVersion;
|
||||||
|
this.webdavSettings = new Group();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private CheckBox checkForUpdatesCheckbox;
|
private CheckBox checkForUpdatesCheckbox;
|
||||||
|
|
||||||
|
private Group webdavSettings;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label portFieldLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextField portField;
|
private TextField portField;
|
||||||
|
|
||||||
@@ -67,6 +75,9 @@ public class SettingsController implements ViewController {
|
|||||||
@FXML
|
@FXML
|
||||||
private ChoiceBox<String> prefGvfsScheme;
|
private ChoiceBox<String> prefGvfsScheme;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ChoiceBox<VolumeImpl> volume;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private CheckBox debugModeCheckbox;
|
private CheckBox debugModeCheckbox;
|
||||||
|
|
||||||
@@ -75,22 +86,41 @@ public class SettingsController implements ViewController {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion.orElse("SNAPSHOT")));
|
||||||
checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally());
|
checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally());
|
||||||
checkForUpdatesCheckbox.setSelected(settings.checkForUpdates().get() && !areUpdatesManagedExternally());
|
checkForUpdatesCheckbox.setSelected(settings.checkForUpdates().get() && !areUpdatesManagedExternally());
|
||||||
|
|
||||||
|
//NIOADAPTER
|
||||||
|
volume.getItems().addAll(Volume.getCurrentSupportedAdapters());
|
||||||
|
volume.setValue(settings.preferredVolumeImpl().get());
|
||||||
|
volume.setConverter(new NioAdapterImplStringConverter());
|
||||||
|
volume.valueProperty().addListener(this::setVisibilityGvfsElements);
|
||||||
|
|
||||||
|
//WEBDAV
|
||||||
|
webdavSettings.visibleProperty().bind(volume.valueProperty().isEqualTo(VolumeImpl.WEBDAV));
|
||||||
|
webdavSettings.managedProperty().bind(webdavSettings.visibleProperty());
|
||||||
|
prefGvfsScheme.managedProperty().bind(webdavSettings.visibleProperty());
|
||||||
|
prefGvfsSchemeLabel.managedProperty().bind(webdavSettings.visibleProperty());
|
||||||
|
portFieldLabel.managedProperty().bind(webdavSettings.visibleProperty());
|
||||||
|
portFieldLabel.visibleProperty().bind(webdavSettings.visibleProperty());
|
||||||
|
changePortButton.managedProperty().bind(webdavSettings.visibleProperty());
|
||||||
|
portField.managedProperty().bind(webdavSettings.visibleProperty());
|
||||||
|
portField.visibleProperty().bind(webdavSettings.visibleProperty());
|
||||||
portField.setText(String.valueOf(settings.port().intValue()));
|
portField.setText(String.valueOf(settings.port().intValue()));
|
||||||
portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents);
|
portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents);
|
||||||
changePortButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(portField.textProperty()));
|
changePortButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(portField.textProperty()));
|
||||||
changePortButton.disableProperty().bind(Bindings.createBooleanBinding(this::isPortValid, portField.textProperty()).not());
|
changePortButton.disableProperty().bind(Bindings.createBooleanBinding(this::isPortValid, portField.textProperty()).not());
|
||||||
versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion.orElse("SNAPSHOT")));
|
|
||||||
prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX);
|
|
||||||
prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX);
|
|
||||||
prefGvfsScheme.getItems().add("dav");
|
prefGvfsScheme.getItems().add("dav");
|
||||||
prefGvfsScheme.getItems().add("webdav");
|
prefGvfsScheme.getItems().add("webdav");
|
||||||
prefGvfsScheme.setValue(settings.preferredGvfsScheme().get());
|
prefGvfsScheme.setValue(settings.preferredGvfsScheme().get());
|
||||||
|
prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX);
|
||||||
|
prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX);
|
||||||
|
|
||||||
debugModeCheckbox.setSelected(settings.debugMode().get());
|
debugModeCheckbox.setSelected(settings.debugMode().get());
|
||||||
|
|
||||||
settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty());
|
settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty());
|
||||||
settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty());
|
settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty());
|
||||||
|
settings.preferredVolumeImpl().bind(volume.valueProperty());
|
||||||
settings.debugMode().bind(debugModeCheckbox.selectedProperty());
|
settings.debugMode().bind(debugModeCheckbox.selectedProperty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,8 +159,26 @@ public class SettingsController implements ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setVisibilityGvfsElements(Observable obs, Object oldValue, Object newValue) {
|
||||||
|
prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX && ((VolumeImpl) newValue).getDisplayName().equals("WebDAV"));
|
||||||
|
prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX && ((VolumeImpl) newValue).getDisplayName().equals("WebDAV"));
|
||||||
|
}
|
||||||
|
|
||||||
private boolean areUpdatesManagedExternally() {
|
private boolean areUpdatesManagedExternally() {
|
||||||
return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
|
return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class NioAdapterImplStringConverter extends StringConverter<VolumeImpl> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(VolumeImpl object) {
|
||||||
|
return object.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VolumeImpl fromString(String string) {
|
||||||
|
return VolumeImpl.forDisplayName(string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,41 +2,14 @@
|
|||||||
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
||||||
*
|
*
|
||||||
* Contributors:
|
* Contributors:
|
||||||
* Sebastian Stenzel - initial API and implementation
|
* Sebastian Stenzel - initial API and implementation
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.cryptomator.ui.controllers;
|
package org.cryptomator.ui.controllers;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.CharUtils;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
|
||||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
|
||||||
import org.cryptomator.frontend.webdav.ServerLifecycleException;
|
|
||||||
import org.cryptomator.keychain.KeychainAccess;
|
|
||||||
import org.cryptomator.ui.controls.SecPasswordField;
|
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
|
||||||
import org.cryptomator.ui.model.Vault;
|
|
||||||
import org.cryptomator.ui.model.WindowsDriveLetters;
|
|
||||||
import org.cryptomator.ui.util.AsyncTaskService;
|
|
||||||
import org.cryptomator.ui.util.DialogBuilderUtil;
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
import org.fxmisc.easybind.Subscription;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.google.common.base.CharMatcher;
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
@@ -54,8 +27,42 @@ import javafx.scene.control.ProgressIndicator;
|
|||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.stage.DirectoryChooser;
|
||||||
|
import javafx.stage.Stage;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
import org.apache.commons.lang3.CharUtils;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
|
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||||
|
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||||
|
import org.cryptomator.keychain.KeychainAccess;
|
||||||
|
import org.cryptomator.ui.controls.SecPasswordField;
|
||||||
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
|
import org.cryptomator.ui.model.Vault;
|
||||||
|
import org.cryptomator.ui.model.WindowsDriveLetters;
|
||||||
|
import org.cryptomator.ui.util.DialogBuilderUtil;
|
||||||
|
import org.cryptomator.ui.util.Tasks;
|
||||||
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
import org.fxmisc.easybind.Subscription;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
|
import java.nio.file.NotDirectoryException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
public class UnlockController implements ViewController {
|
public class UnlockController implements ViewController {
|
||||||
|
|
||||||
@@ -67,22 +74,26 @@ public class UnlockController implements ViewController {
|
|||||||
.precomputed();
|
.precomputed();
|
||||||
|
|
||||||
private final Application app;
|
private final Application app;
|
||||||
|
private final Stage mainWindow;
|
||||||
private final Localization localization;
|
private final Localization localization;
|
||||||
private final AsyncTaskService asyncTaskService;
|
|
||||||
private final WindowsDriveLetters driveLetters;
|
private final WindowsDriveLetters driveLetters;
|
||||||
private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange;
|
private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange;
|
||||||
private final Optional<KeychainAccess> keychainAccess;
|
private final Optional<KeychainAccess> keychainAccess;
|
||||||
|
private final Settings settings;
|
||||||
|
private final ExecutorService executor;
|
||||||
private Vault vault;
|
private Vault vault;
|
||||||
private Optional<UnlockListener> listener = Optional.empty();
|
private Optional<UnlockListener> listener = Optional.empty();
|
||||||
private Subscription vaultSubs = Subscription.EMPTY;
|
private Subscription vaultSubs = Subscription.EMPTY;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UnlockController(Application app, Localization localization, AsyncTaskService asyncTaskService, WindowsDriveLetters driveLetters, Optional<KeychainAccess> keychainAccess) {
|
public UnlockController(Application app, @Named("mainWindow") Stage mainWindow, Localization localization, WindowsDriveLetters driveLetters, Optional<KeychainAccess> keychainAccess, Settings settings, ExecutorService executor) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
this.localization = localization;
|
this.localization = localization;
|
||||||
this.asyncTaskService = asyncTaskService;
|
|
||||||
this.driveLetters = driveLetters;
|
this.driveLetters = driveLetters;
|
||||||
this.keychainAccess = keychainAccess;
|
this.keychainAccess = keychainAccess;
|
||||||
|
this.settings = settings;
|
||||||
|
this.executor = executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -95,10 +106,10 @@ public class UnlockController implements ViewController {
|
|||||||
private Button unlockButton;
|
private Button unlockButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private CheckBox savePassword;
|
private Label successMessage;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private CheckBox mountAfterUnlock;
|
private CheckBox savePassword;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextField mountName;
|
private TextField mountName;
|
||||||
@@ -112,6 +123,15 @@ public class UnlockController implements ViewController {
|
|||||||
@FXML
|
@FXML
|
||||||
private ChoiceBox<Character> winDriveLetter;
|
private ChoiceBox<Character> winDriveLetter;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox useCustomMountPoint;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private HBox customMountPoint;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label customMountPointLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ProgressIndicator progressIndicator;
|
private ProgressIndicator progressIndicator;
|
||||||
|
|
||||||
@@ -130,19 +150,23 @@ public class UnlockController implements ViewController {
|
|||||||
@FXML
|
@FXML
|
||||||
private CheckBox unlockAfterStartup;
|
private CheckBox unlockAfterStartup;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox useReadOnlyMode;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
|
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
|
||||||
unlockButton.disableProperty().bind(passwordField.textProperty().isEmpty());
|
unlockButton.disableProperty().bind(passwordField.textProperty().isEmpty());
|
||||||
mountName.disableProperty().bind(mountAfterUnlock.selectedProperty().not());
|
|
||||||
revealAfterMount.disableProperty().bind(mountAfterUnlock.selectedProperty().not());
|
|
||||||
mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
|
mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
|
||||||
mountName.textProperty().addListener(this::mountNameDidChange);
|
mountName.textProperty().addListener(this::mountNameDidChange);
|
||||||
savePassword.setDisable(!keychainAccess.isPresent());
|
savePassword.setDisable(!keychainAccess.isPresent());
|
||||||
unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
|
unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
|
customMountPoint.visibleProperty().bind(useCustomMountPoint.selectedProperty());
|
||||||
} else {
|
customMountPoint.managedProperty().bind(useCustomMountPoint.selectedProperty());
|
||||||
|
winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
|
||||||
|
|
||||||
|
if (!SystemUtils.IS_OS_WINDOWS) {
|
||||||
winDriveLetterLabel.setVisible(false);
|
winDriveLetterLabel.setVisible(false);
|
||||||
winDriveLetterLabel.setManaged(false);
|
winDriveLetterLabel.setManaged(false);
|
||||||
winDriveLetter.setVisible(false);
|
winDriveLetter.setVisible(false);
|
||||||
@@ -150,12 +174,18 @@ public class UnlockController implements ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Parent getRoot() {
|
public Parent getRoot() {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setVault(Vault vault) {
|
@Override
|
||||||
|
public void focus() {
|
||||||
|
passwordField.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setVault(Vault vault, State state) {
|
||||||
vaultSubs.unsubscribe();
|
vaultSubs.unsubscribe();
|
||||||
vaultSubs = Subscription.EMPTY;
|
vaultSubs = Subscription.EMPTY;
|
||||||
|
|
||||||
@@ -167,10 +197,11 @@ public class UnlockController implements ViewController {
|
|||||||
}
|
}
|
||||||
assert vault != null;
|
assert vault != null;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
passwordField.swipe();
|
|
||||||
advancedOptions.setVisible(false);
|
advancedOptions.setVisible(false);
|
||||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
||||||
progressIndicator.setVisible(false);
|
progressIndicator.setVisible(false);
|
||||||
|
successMessage.setVisible(state.successMessage().isPresent());
|
||||||
|
state.successMessage().map(localization::getString).ifPresent(successMessage::setText);
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
winDriveLetter.valueProperty().removeListener(driveLetterChangeListener);
|
winDriveLetter.valueProperty().removeListener(driveLetterChangeListener);
|
||||||
winDriveLetter.getItems().clear();
|
winDriveLetter.getItems().clear();
|
||||||
@@ -178,32 +209,78 @@ public class UnlockController implements ViewController {
|
|||||||
winDriveLetter.getItems().addAll(driveLetters.getAvailableDriveLetters());
|
winDriveLetter.getItems().addAll(driveLetters.getAvailableDriveLetters());
|
||||||
winDriveLetter.getItems().sort(new WinDriveLetterComparator());
|
winDriveLetter.getItems().sort(new WinDriveLetterComparator());
|
||||||
winDriveLetter.valueProperty().addListener(driveLetterChangeListener);
|
winDriveLetter.valueProperty().addListener(driveLetterChangeListener);
|
||||||
|
chooseSelectedDriveLetter();
|
||||||
}
|
}
|
||||||
downloadsPageLink.setVisible(false);
|
downloadsPageLink.setVisible(false);
|
||||||
messageText.setText(null);
|
messageText.setText(null);
|
||||||
mountName.setText(vault.getMountName());
|
mountName.setText(vault.getMountName());
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
chooseSelectedDriveLetter();
|
|
||||||
}
|
|
||||||
savePassword.setSelected(false);
|
savePassword.setSelected(false);
|
||||||
// auto-fill pw from keychain:
|
// auto-fill pw from keychain:
|
||||||
if (keychainAccess.isPresent()) {
|
if (keychainAccess.isPresent()) {
|
||||||
char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
|
char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
|
||||||
if (storedPw != null) {
|
if (storedPw != null) {
|
||||||
savePassword.setSelected(true);
|
savePassword.setSelected(true);
|
||||||
passwordField.setText(new String(storedPw));
|
passwordField.setPassword(storedPw);
|
||||||
passwordField.selectRange(storedPw.length, storedPw.length);
|
passwordField.selectRange(storedPw.length, storedPw.length);
|
||||||
Arrays.fill(storedPw, ' ');
|
Arrays.fill(storedPw, ' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VaultSettings settings = vault.getVaultSettings();
|
VaultSettings vaultSettings = vault.getVaultSettings();
|
||||||
unlockAfterStartup.setSelected(savePassword.isSelected() && settings.unlockAfterStartup().get());
|
unlockAfterStartup.setSelected(savePassword.isSelected() && vaultSettings.unlockAfterStartup().get());
|
||||||
mountAfterUnlock.setSelected(settings.mountAfterUnlock().get());
|
revealAfterMount.setSelected(vaultSettings.revealAfterMount().get());
|
||||||
revealAfterMount.setSelected(settings.revealAfterMount().get());
|
|
||||||
|
|
||||||
vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), settings.unlockAfterStartup()::set));
|
// WEBDAV-dependent controls:
|
||||||
vaultSubs = vaultSubs.and(EasyBind.subscribe(mountAfterUnlock.selectedProperty(), settings.mountAfterUnlock()::set));
|
if (VolumeImpl.WEBDAV.equals(settings.preferredVolumeImpl().get())) {
|
||||||
vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), settings.revealAfterMount()::set));
|
useCustomMountPoint.setVisible(false);
|
||||||
|
useCustomMountPoint.setManaged(false);
|
||||||
|
} else {
|
||||||
|
useCustomMountPoint.setVisible(true);
|
||||||
|
useCustomMountPoint.setSelected(vaultSettings.usesIndividualMountPath().get());
|
||||||
|
if (Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
|
||||||
|
customMountPointLabel.setText(localization.getString("unlock.label.chooseMountPath"));
|
||||||
|
} else {
|
||||||
|
customMountPointLabel.setText(displayablePath(vaultSettings.individualMountPath().getValueSafe()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOKANY-dependent controls:
|
||||||
|
if (VolumeImpl.DOKANY.equals(settings.preferredVolumeImpl().get())) {
|
||||||
|
winDriveLetter.visibleProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
winDriveLetter.managedProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
winDriveLetterLabel.visibleProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
winDriveLetterLabel.managedProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
// readonly not yet supported by dokany
|
||||||
|
useReadOnlyMode.setSelected(false);
|
||||||
|
useReadOnlyMode.setVisible(false);
|
||||||
|
useReadOnlyMode.setManaged(false);
|
||||||
|
} else {
|
||||||
|
useReadOnlyMode.setSelected(vaultSettings.usesReadOnlyMode().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// OS-dependent controls:
|
||||||
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
winDriveLetter.visibleProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
winDriveLetter.managedProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
winDriveLetterLabel.visibleProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
winDriveLetterLabel.managedProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||||
|
}
|
||||||
|
|
||||||
|
vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), vaultSettings.unlockAfterStartup()::set));
|
||||||
|
vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), vaultSettings.revealAfterMount()::set));
|
||||||
|
vaultSubs = vaultSubs.and(EasyBind.subscribe(useCustomMountPoint.selectedProperty(), vaultSettings.usesIndividualMountPath()::set));
|
||||||
|
vaultSubs = vaultSubs.and(EasyBind.subscribe(useReadOnlyMode.selectedProperty(), vaultSettings.usesReadOnlyMode()::set));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String displayablePath(String path) {
|
||||||
|
Path homeDir = Paths.get(SystemUtils.USER_HOME);
|
||||||
|
Path p = Paths.get(path);
|
||||||
|
if (p.startsWith(homeDir)) {
|
||||||
|
Path relativePath = homeDir.relativize(p);
|
||||||
|
String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/";
|
||||||
|
return homePrefix + relativePath.toString();
|
||||||
|
} else {
|
||||||
|
return p.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************************
|
// ****************************************
|
||||||
@@ -221,6 +298,7 @@ public class UnlockController implements ViewController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void didClickAdvancedOptionsButton(ActionEvent event) {
|
private void didClickAdvancedOptionsButton(ActionEvent event) {
|
||||||
|
successMessage.setVisible(false);
|
||||||
advancedOptions.setVisible(!advancedOptions.isVisible());
|
advancedOptions.setVisible(!advancedOptions.isVisible());
|
||||||
if (advancedOptions.isVisible()) {
|
if (advancedOptions.isVisible()) {
|
||||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.hide"));
|
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.hide"));
|
||||||
@@ -244,6 +322,15 @@ public class UnlockController implements ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void didClickChooseCustomMountPoint(ActionEvent actionEvent) {
|
||||||
|
DirectoryChooser dirChooser = new DirectoryChooser();
|
||||||
|
File file = dirChooser.showDialog(mainWindow);
|
||||||
|
if (file != null) {
|
||||||
|
customMountPointLabel.setText(displayablePath(file.toString()));
|
||||||
|
vault.setCustomMountPath(file.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts 'C' to "C:" to translate between model and GUI.
|
* Converts 'C' to "C:" to translate between model and GUI.
|
||||||
*/
|
*/
|
||||||
@@ -254,7 +341,7 @@ public class UnlockController implements ViewController {
|
|||||||
if (letter == null) {
|
if (letter == null) {
|
||||||
return localization.getString("unlock.choicebox.winDriveLetter.auto");
|
return localization.getString("unlock.choicebox.winDriveLetter.auto");
|
||||||
} else {
|
} else {
|
||||||
return Character.toString(letter) + ":";
|
return letter + ":";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,10 +430,12 @@ public class UnlockController implements ViewController {
|
|||||||
@FXML
|
@FXML
|
||||||
private void didClickUnlockButton(ActionEvent event) {
|
private void didClickUnlockButton(ActionEvent event) {
|
||||||
advancedOptions.setDisable(true);
|
advancedOptions.setDisable(true);
|
||||||
|
advancedOptions.setVisible(false);
|
||||||
progressIndicator.setVisible(true);
|
progressIndicator.setVisible(true);
|
||||||
|
|
||||||
CharSequence password = passwordField.getCharacters();
|
CharSequence password = passwordField.getCharacters();
|
||||||
asyncTaskService.asyncTaskOf(() -> {
|
Tasks.create(() -> {
|
||||||
|
messageText.setText(localization.getString("unlock.pendingMessage.unlocking"));
|
||||||
vault.unlock(password);
|
vault.unlock(password);
|
||||||
if (keychainAccess.isPresent() && savePassword.isSelected()) {
|
if (keychainAccess.isPresent() && savePassword.isSelected()) {
|
||||||
keychainAccess.get().storePassphrase(vault.getId(), password);
|
keychainAccess.get().storePassphrase(vault.getId(), password);
|
||||||
@@ -370,10 +459,17 @@ public class UnlockController implements ViewController {
|
|||||||
} else if (e.getDetectedVersion() == Integer.MAX_VALUE) {
|
} else if (e.getDetectedVersion() == Integer.MAX_VALUE) {
|
||||||
messageText.setText(localization.getString("unlock.errorMessage.unauthenticVersionMac"));
|
messageText.setText(localization.getString("unlock.errorMessage.unauthenticVersionMac"));
|
||||||
}
|
}
|
||||||
}).onError(ServerLifecycleException.class, e -> {
|
}).onError(NotDirectoryException.class, e -> {
|
||||||
LOG.error("Unlock failed for technical reasons.", e);
|
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
|
||||||
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
|
advancedOptions.setVisible(true);
|
||||||
}).onError(IOException.class, e -> {
|
messageText.setText(null);
|
||||||
|
showUnlockFailedErrorDialog("unlock.failedDialog.content.mountPathNonExisting");
|
||||||
|
}).onError(DirectoryNotEmptyException.class, e -> {
|
||||||
|
LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage());
|
||||||
|
advancedOptions.setVisible(true);
|
||||||
|
messageText.setText(null);
|
||||||
|
showUnlockFailedErrorDialog("unlock.failedDialog.content.mountPathNotEmpty");
|
||||||
|
}).onError(Exception.class, e -> { // including RuntimeExceptions
|
||||||
LOG.error("Unlock failed for technical reasons.", e);
|
LOG.error("Unlock failed for technical reasons.", e);
|
||||||
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
|
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
|
||||||
}).andFinally(() -> {
|
}).andFinally(() -> {
|
||||||
@@ -382,7 +478,15 @@ public class UnlockController implements ViewController {
|
|||||||
}
|
}
|
||||||
advancedOptions.setDisable(false);
|
advancedOptions.setDisable(false);
|
||||||
progressIndicator.setVisible(false);
|
progressIndicator.setVisible(false);
|
||||||
}).run();
|
}).runOnce(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showUnlockFailedErrorDialog(String localizableContentKey) {
|
||||||
|
String title = localization.getString("unlock.failedDialog.title");
|
||||||
|
String header = localization.getString("unlock.failedDialog.header");
|
||||||
|
String content = localization.getString(localizableContentKey);
|
||||||
|
Alert alert = DialogBuilderUtil.buildErrorDialog(title, header, content, ButtonType.OK);
|
||||||
|
alert.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* callback */
|
/* callback */
|
||||||
@@ -393,7 +497,28 @@ public class UnlockController implements ViewController {
|
|||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
interface UnlockListener {
|
interface UnlockListener {
|
||||||
|
|
||||||
void didUnlock(Vault vault);
|
void didUnlock(Vault vault);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* state */
|
||||||
|
|
||||||
|
public enum State {
|
||||||
|
UNLOCKING(null), //
|
||||||
|
INITIALIZED("unlock.successLabel.vaultCreated"), //
|
||||||
|
PASSWORD_CHANGED("unlock.successLabel.passwordChanged"), //
|
||||||
|
UPGRADED("unlock.successLabel.upgraded");
|
||||||
|
|
||||||
|
private Optional<String> successMessage;
|
||||||
|
|
||||||
|
State(String successMessage) {
|
||||||
|
this.successMessage = Optional.ofNullable(successMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> successMessage() {
|
||||||
|
return successMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,29 +8,13 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.cryptomator.ui.controllers;
|
package org.cryptomator.ui.controllers;
|
||||||
|
|
||||||
import static java.lang.String.format;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import java.util.Optional;
|
||||||
import org.cryptomator.frontend.webdav.ServerLifecycleException;
|
import java.util.concurrent.ExecutorService;
|
||||||
import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
|
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
|
||||||
import org.cryptomator.ui.model.Vault;
|
|
||||||
import org.cryptomator.ui.util.AsyncTaskService;
|
|
||||||
import org.cryptomator.ui.util.DialogBuilderUtil;
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.Runnables;
|
|
||||||
|
|
||||||
import javafx.animation.Animation;
|
import javafx.animation.Animation;
|
||||||
import javafx.animation.KeyFrame;
|
import javafx.animation.KeyFrame;
|
||||||
import javafx.animation.Timeline;
|
import javafx.animation.Timeline;
|
||||||
import javafx.beans.binding.ObjectExpression;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
@@ -46,13 +30,19 @@ import javafx.scene.control.Alert;
|
|||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.scene.control.ContextMenu;
|
import javafx.scene.control.ContextMenu;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.MenuItem;
|
|
||||||
import javafx.scene.control.ToggleButton;
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.scene.input.Clipboard;
|
|
||||||
import javafx.scene.input.ClipboardContent;
|
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.stage.PopupWindow.AnchorLocation;
|
import javafx.stage.PopupWindow.AnchorLocation;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
|
import org.cryptomator.ui.model.Vault;
|
||||||
|
import org.cryptomator.ui.util.DialogBuilderUtil;
|
||||||
|
import org.cryptomator.ui.util.Tasks;
|
||||||
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
|
||||||
public class UnlockedController implements ViewController {
|
public class UnlockedController implements ViewController {
|
||||||
|
|
||||||
@@ -62,9 +52,8 @@ public class UnlockedController implements ViewController {
|
|||||||
private static final double IO_SAMPLING_INTERVAL = 0.25;
|
private static final double IO_SAMPLING_INTERVAL = 0.25;
|
||||||
|
|
||||||
private final Localization localization;
|
private final Localization localization;
|
||||||
private final AsyncTaskService asyncTaskService;
|
private final ExecutorService executor;
|
||||||
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
||||||
private final ObjectExpression<Vault.State> vaultState = ObjectExpression.objectExpression(EasyBind.select(vault).selectObject(Vault::stateProperty));
|
|
||||||
private Optional<LockListener> listener = Optional.empty();
|
private Optional<LockListener> listener = Optional.empty();
|
||||||
private Timeline ioAnimation;
|
private Timeline ioAnimation;
|
||||||
|
|
||||||
@@ -83,30 +72,17 @@ public class UnlockedController implements ViewController {
|
|||||||
@FXML
|
@FXML
|
||||||
private ContextMenu moreOptionsMenu;
|
private ContextMenu moreOptionsMenu;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private MenuItem mountVaultMenuItem;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private MenuItem unmountVaultMenuItem;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private MenuItem revealVaultMenuItem;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private VBox root;
|
private VBox root;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UnlockedController(Localization localization, AsyncTaskService asyncTaskService) {
|
public UnlockedController(Localization localization, ExecutorService executor) {
|
||||||
this.localization = localization;
|
this.localization = localization;
|
||||||
this.asyncTaskService = asyncTaskService;
|
this.executor = executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
mountVaultMenuItem.disableProperty().bind(vaultState.isEqualTo(Vault.State.UNLOCKED).not()); // enable when unlocked
|
|
||||||
unmountVaultMenuItem.disableProperty().bind(vaultState.isEqualTo(Vault.State.MOUNTED).not()); // enable when mounted
|
|
||||||
revealVaultMenuItem.disableProperty().bind(vaultState.isEqualTo(Vault.State.MOUNTED).not()); // enable when mounted
|
|
||||||
|
|
||||||
EasyBind.subscribe(vault, this::vaultChanged);
|
EasyBind.subscribe(vault, this::vaultChanged);
|
||||||
EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected);
|
EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected);
|
||||||
}
|
}
|
||||||
@@ -121,10 +97,6 @@ public class UnlockedController implements ViewController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newVault.getState() == Vault.State.UNLOCKED && newVault.getVaultSettings().mountAfterUnlock().get()) {
|
|
||||||
mountVault(newVault);
|
|
||||||
}
|
|
||||||
|
|
||||||
// (re)start throughput statistics:
|
// (re)start throughput statistics:
|
||||||
stopIoSampling();
|
stopIoSampling();
|
||||||
startIoSampling();
|
startIoSampling();
|
||||||
@@ -132,75 +104,22 @@ public class UnlockedController implements ViewController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void didClickLockVault(ActionEvent event) {
|
private void didClickLockVault(ActionEvent event) {
|
||||||
regularUnmountVault(this::lockVault);
|
regularLockVault(this::lockVaultSucceeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void lockVault() {
|
private void lockVaultSucceeded() {
|
||||||
try {
|
|
||||||
vault.get().lock();
|
|
||||||
} catch (ServerLifecycleException | IOException e) {
|
|
||||||
LOG.error("Lock failed", e);
|
|
||||||
}
|
|
||||||
listener.ifPresent(listener -> listener.didLock(this));
|
listener.ifPresent(listener -> listener.didLock(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
private void regularLockVault(Runnable onSuccess) {
|
||||||
private void didClickMoreOptions(ActionEvent event) {
|
Tasks.create(() -> {
|
||||||
if (moreOptionsMenu.isShowing()) {
|
vault.get().lock(false);
|
||||||
moreOptionsMenu.hide();
|
|
||||||
} else {
|
|
||||||
moreOptionsMenu.setAnchorLocation(AnchorLocation.CONTENT_TOP_RIGHT);
|
|
||||||
moreOptionsMenu.show(moreOptionsButton, Side.BOTTOM, moreOptionsButton.getWidth(), 0.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void didClickMountVault(ActionEvent event) {
|
|
||||||
mountVault(vault.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void mountVault(Vault vault) {
|
|
||||||
asyncTaskService.asyncTaskOf(() -> {
|
|
||||||
vault.mount();
|
|
||||||
}).onSuccess(() -> {
|
|
||||||
LOG.trace("Mount succeeded.");
|
|
||||||
messageLabel.setText(null);
|
|
||||||
if (vault.getVaultSettings().revealAfterMount().get()) {
|
|
||||||
revealVault(vault);
|
|
||||||
}
|
|
||||||
}).onError(CommandFailedException.class, e -> {
|
|
||||||
LOG.error("Mount failed.", e);
|
|
||||||
// TODO Markus Kreusch #393: hyperlink auf FAQ oder sowas?
|
|
||||||
messageLabel.setText(localization.getString("unlocked.label.mountFailed"));
|
|
||||||
}).run();
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void didClickUnmountVault(ActionEvent event) {
|
|
||||||
regularUnmountVault(Runnables.doNothing());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void regularUnmountVault(Runnable onSuccess) {
|
|
||||||
asyncTaskService.asyncTaskOf(() -> {
|
|
||||||
vault.get().unmount();
|
|
||||||
}).onSuccess(() -> {
|
}).onSuccess(() -> {
|
||||||
LOG.trace("Regular unmount succeeded.");
|
LOG.trace("Regular unmount succeeded.");
|
||||||
onSuccess.run();
|
onSuccess.run();
|
||||||
}).onError(Exception.class, e -> {
|
}).onError(Exception.class, e -> {
|
||||||
onRegularUnmountVaultFailed(e, onSuccess);
|
onRegularUnmountVaultFailed(e, onSuccess);
|
||||||
}).run();
|
}).runOnce(executor);
|
||||||
}
|
|
||||||
|
|
||||||
private void forcedUnmountVault(Runnable onSuccess) {
|
|
||||||
asyncTaskService.asyncTaskOf(() -> {
|
|
||||||
vault.get().unmountForced();
|
|
||||||
}).onSuccess(() -> {
|
|
||||||
LOG.trace("Forced unmount succeeded.");
|
|
||||||
onSuccess.run();
|
|
||||||
}).onError(Exception.class, e -> {
|
|
||||||
LOG.error("Forced unmount failed.", e);
|
|
||||||
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
|
|
||||||
}).run();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRegularUnmountVaultFailed(Exception e, Runnable onSuccess) {
|
private void onRegularUnmountVaultFailed(Exception e, Runnable onSuccess) {
|
||||||
@@ -214,7 +133,7 @@ public class UnlockedController implements ViewController {
|
|||||||
|
|
||||||
Optional<ButtonType> choice = confirmDialog.showAndWait();
|
Optional<ButtonType> choice = confirmDialog.showAndWait();
|
||||||
if (ButtonType.YES.equals(choice.get())) {
|
if (ButtonType.YES.equals(choice.get())) {
|
||||||
forcedUnmountVault(onSuccess);
|
forcedLockVault(onSuccess);
|
||||||
} else {
|
} else {
|
||||||
LOG.trace("Unmount cancelled.", e);
|
LOG.trace("Unmount cancelled.", e);
|
||||||
}
|
}
|
||||||
@@ -224,29 +143,43 @@ public class UnlockedController implements ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void forcedLockVault(Runnable onSuccess) {
|
||||||
|
Tasks.create(() -> {
|
||||||
|
vault.get().lock(true);
|
||||||
|
}).onSuccess(() -> {
|
||||||
|
LOG.trace("Forced unmount succeeded.");
|
||||||
|
onSuccess.run();
|
||||||
|
}).onError(Exception.class, e -> {
|
||||||
|
LOG.error("Forced unmount failed.", e);
|
||||||
|
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
|
||||||
|
}).runOnce(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void didClickMoreOptions(ActionEvent event) {
|
||||||
|
if (moreOptionsMenu.isShowing()) {
|
||||||
|
moreOptionsMenu.hide();
|
||||||
|
} else {
|
||||||
|
moreOptionsMenu.setAnchorLocation(AnchorLocation.CONTENT_TOP_RIGHT);
|
||||||
|
moreOptionsMenu.show(moreOptionsButton, Side.BOTTOM, moreOptionsButton.getWidth(), 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void didClickRevealVault(ActionEvent event) {
|
private void didClickRevealVault(ActionEvent event) {
|
||||||
revealVault(vault.get());
|
revealVault(vault.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void revealVault(Vault vault) {
|
void revealVault(Vault vault) {
|
||||||
asyncTaskService.asyncTaskOf(() -> {
|
Tasks.create(() -> {
|
||||||
vault.reveal();
|
vault.reveal();
|
||||||
}).onSuccess(() -> {
|
}).onSuccess(() -> {
|
||||||
LOG.trace("Reveal succeeded.");
|
LOG.trace("Reveal succeeded.");
|
||||||
messageLabel.setText(null);
|
messageLabel.setText(null);
|
||||||
}).onError(CommandFailedException.class, e -> {
|
}).onError(Exception.class, e -> {
|
||||||
LOG.error("Reveal failed.", e);
|
LOG.error("Reveal failed.", e);
|
||||||
messageLabel.setText(localization.getString("unlocked.label.revealFailed"));
|
messageLabel.setText(localization.getString("unlocked.label.revealFailed"));
|
||||||
}).run();
|
}).runOnce(executor);
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void didClickCopyUrl(ActionEvent event) {
|
|
||||||
ClipboardContent clipboardContent = new ClipboardContent();
|
|
||||||
clipboardContent.putUrl(vault.get().getWebDavUrl());
|
|
||||||
clipboardContent.putString(vault.get().getWebDavUrl());
|
|
||||||
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************************
|
// ****************************************
|
||||||
|
|||||||
@@ -5,19 +5,10 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.controllers;
|
package org.cryptomator.ui.controllers;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import org.cryptomator.ui.controls.SecPasswordField;
|
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
|
||||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
|
||||||
import org.cryptomator.ui.model.UpgradeStrategy;
|
|
||||||
import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException;
|
|
||||||
import org.cryptomator.ui.model.Vault;
|
|
||||||
import org.cryptomator.ui.util.AsyncTaskService;
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
|
|
||||||
import javafx.beans.binding.BooleanExpression;
|
import javafx.beans.binding.BooleanExpression;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
@@ -30,19 +21,26 @@ import javafx.scene.control.CheckBox;
|
|||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.ProgressIndicator;
|
import javafx.scene.control.ProgressIndicator;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
|
import org.cryptomator.ui.controls.SecPasswordField;
|
||||||
|
import org.cryptomator.ui.model.UpgradeStrategies;
|
||||||
|
import org.cryptomator.ui.model.UpgradeStrategy;
|
||||||
|
import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException;
|
||||||
|
import org.cryptomator.ui.model.Vault;
|
||||||
|
import org.cryptomator.ui.util.Tasks;
|
||||||
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
|
||||||
public class UpgradeController implements ViewController {
|
public class UpgradeController implements ViewController {
|
||||||
|
|
||||||
private final ObjectProperty<UpgradeStrategy> strategy = new SimpleObjectProperty<>();
|
private final ObjectProperty<UpgradeStrategy> strategy = new SimpleObjectProperty<>();
|
||||||
private final UpgradeStrategies strategies;
|
private final UpgradeStrategies strategies;
|
||||||
private final AsyncTaskService asyncTaskService;
|
private final ExecutorService executor;
|
||||||
private Optional<UpgradeListener> listener = Optional.empty();
|
private Optional<UpgradeListener> listener = Optional.empty();
|
||||||
private Vault vault;
|
private Vault vault;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UpgradeController(Localization localization, UpgradeStrategies strategies, AsyncTaskService asyncTaskService) {
|
public UpgradeController(UpgradeStrategies strategies, ExecutorService executor) {
|
||||||
this.strategies = strategies;
|
this.strategies = strategies;
|
||||||
this.asyncTaskService = asyncTaskService;
|
this.executor = executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -84,6 +82,11 @@ public class UpgradeController implements ViewController {
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void focus() {
|
||||||
|
passwordField.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
void setVault(Vault vault) {
|
void setVault(Vault vault) {
|
||||||
this.vault = Objects.requireNonNull(vault);
|
this.vault = Objects.requireNonNull(vault);
|
||||||
errorLabel.setText(null);
|
errorLabel.setText(null);
|
||||||
@@ -117,8 +120,8 @@ public class UpgradeController implements ViewController {
|
|||||||
private void upgrade(UpgradeStrategy instruction) {
|
private void upgrade(UpgradeStrategy instruction) {
|
||||||
passwordField.setDisable(true);
|
passwordField.setDisable(true);
|
||||||
progressIndicator.setVisible(true);
|
progressIndicator.setVisible(true);
|
||||||
asyncTaskService //
|
Tasks //
|
||||||
.asyncTaskOf(() -> {
|
.create(() -> {
|
||||||
if (!instruction.isApplicable(vault)) {
|
if (!instruction.isApplicable(vault)) {
|
||||||
throw new IllegalStateException("No ugprade needed for " + vault.getPath());
|
throw new IllegalStateException("No ugprade needed for " + vault.getPath());
|
||||||
}
|
}
|
||||||
@@ -132,7 +135,7 @@ public class UpgradeController implements ViewController {
|
|||||||
progressIndicator.setVisible(false);
|
progressIndicator.setVisible(false);
|
||||||
passwordField.setDisable(false);
|
passwordField.setDisable(false);
|
||||||
passwordField.swipe();
|
passwordField.swipe();
|
||||||
}).run();
|
}).runOnce(executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showNextUpgrade() {
|
private void showNextUpgrade() {
|
||||||
|
|||||||
@@ -24,4 +24,8 @@ public interface ViewController extends Initializable {
|
|||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void focus() {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,53 +2,50 @@
|
|||||||
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
||||||
*
|
*
|
||||||
* Contributors:
|
* Contributors:
|
||||||
* Sebastian Stenzel - initial API and implementation
|
* Sebastian Stenzel - initial API and implementation
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.cryptomator.ui.controllers;
|
package org.cryptomator.ui.controllers;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.net.HttpURLConnection;
|
||||||
import java.io.Reader;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import javax.inject.Inject;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.apache.http.client.config.RequestConfig;
|
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
|
||||||
import org.apache.http.impl.client.HttpClients;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
|
||||||
import org.cryptomator.ui.util.AsyncTaskService;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.scene.control.Hyperlink;
|
import javafx.scene.control.Hyperlink;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.ProgressIndicator;
|
import javafx.scene.control.ProgressIndicator;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
|
import org.cryptomator.ui.util.Tasks;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static org.cryptomator.ui.util.DialogBuilderUtil.buildYesNoDialog;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class WelcomeController implements ViewController {
|
public class WelcomeController implements ViewController {
|
||||||
@@ -60,17 +57,17 @@ public class WelcomeController implements ViewController {
|
|||||||
private final Localization localization;
|
private final Localization localization;
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final Comparator<String> semVerComparator;
|
private final Comparator<String> semVerComparator;
|
||||||
private final AsyncTaskService asyncTaskService;
|
private final ScheduledExecutorService executor;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WelcomeController(Application app, @Named("applicationVersion") Optional<String> applicationVersion, Localization localization, Settings settings, @Named("SemVer") Comparator<String> semVerComparator,
|
public WelcomeController(Application app, @Named("applicationVersion") Optional<String> applicationVersion, Localization localization, Settings settings, @Named("SemVer") Comparator<String> semVerComparator,
|
||||||
AsyncTaskService asyncTaskService) {
|
ScheduledExecutorService executor) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.applicationVersion = applicationVersion;
|
this.applicationVersion = applicationVersion;
|
||||||
this.localization = localization;
|
this.localization = localization;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.semVerComparator = semVerComparator;
|
this.semVerComparator = semVerComparator;
|
||||||
this.asyncTaskService = asyncTaskService;
|
this.executor = executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -92,6 +89,8 @@ public class WelcomeController implements ViewController {
|
|||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
if (areUpdatesManagedExternally()) {
|
if (areUpdatesManagedExternally()) {
|
||||||
checkForUpdatesContainer.setVisible(false);
|
checkForUpdatesContainer.setVisible(false);
|
||||||
|
} else if (!settings.askedForUpdateCheck().get()) {
|
||||||
|
this.askForUpdateCheck();
|
||||||
} else if (settings.checkForUpdates().get()) {
|
} else if (settings.checkForUpdates().get()) {
|
||||||
this.checkForUpdates();
|
this.checkForUpdates();
|
||||||
}
|
}
|
||||||
@@ -110,44 +109,63 @@ public class WelcomeController implements ViewController {
|
|||||||
return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
|
return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void askForUpdateCheck() {
|
||||||
|
Tasks.create(() -> {}).onSuccess(() -> {
|
||||||
|
Optional<ButtonType> result = buildYesNoDialog(
|
||||||
|
localization.getString("welcome.askForUpdateCheck.dialog.title"),
|
||||||
|
localization.getString("welcome.askForUpdateCheck.dialog.header"),
|
||||||
|
localization.getString("welcome.askForUpdateCheck.dialog.content"),
|
||||||
|
ButtonType.YES).showAndWait();
|
||||||
|
if (result.isPresent()) {
|
||||||
|
settings.askedForUpdateCheck().set(true);
|
||||||
|
settings.checkForUpdates().set(result.get().equals(ButtonType.YES));
|
||||||
|
}
|
||||||
|
if (settings.checkForUpdates().get()) {
|
||||||
|
this.checkForUpdates();
|
||||||
|
}
|
||||||
|
}).scheduleOnce(executor, 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
private void checkForUpdates() {
|
private void checkForUpdates() {
|
||||||
checkForUpdatesStatus.setText(localization.getString("welcome.checkForUpdates.label.currentlyChecking"));
|
checkForUpdatesStatus.setText(localization.getString("welcome.checkForUpdates.label.currentlyChecking"));
|
||||||
checkForUpdatesIndicator.setVisible(true);
|
checkForUpdatesIndicator.setVisible(true);
|
||||||
asyncTaskService.asyncTaskOf(() -> {
|
Tasks.create(() -> {
|
||||||
RequestConfig requestConfig = RequestConfig.custom() //
|
|
||||||
.setConnectTimeout(5000) //
|
|
||||||
.setConnectionRequestTimeout(5000) //
|
|
||||||
.setSocketTimeout(5000) //
|
|
||||||
.build();
|
|
||||||
String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||||
HttpClientBuilder httpClientBuilder = HttpClients.custom() //
|
URL url = URI.create("https://api.cryptomator.org/updates/latestVersion.json").toURL();
|
||||||
.disableCookieManagement() //
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
.setDefaultRequestConfig(requestConfig) //
|
conn.addRequestProperty("User-Agent", userAgent);
|
||||||
.setUserAgent(userAgent);
|
conn.connect();
|
||||||
LOG.debug("Checking for updates...");
|
try {
|
||||||
try (CloseableHttpClient client = httpClientBuilder.build()) {
|
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||||
HttpGet request = new HttpGet("https://api.cryptomator.org/updates/latestVersion.json");
|
return Optional.<byte[]>empty();
|
||||||
try (CloseableHttpResponse response = client.execute(request)) {
|
|
||||||
if (response.getStatusLine().getStatusCode() == 200 && response.getEntity() != null) {
|
|
||||||
try (InputStream in = response.getEntity().getContent()) {
|
|
||||||
Gson gson = new GsonBuilder().setLenient().create();
|
|
||||||
Reader utf8Reader = new InputStreamReader(in, StandardCharsets.UTF_8);
|
|
||||||
Map<String, String> map = gson.fromJson(utf8Reader, new TypeToken<Map<String, String>>() {
|
|
||||||
}.getType());
|
|
||||||
if (map != null) {
|
|
||||||
this.compareVersions(map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
try (InputStream in = conn.getInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||||
|
in.transferTo(out);
|
||||||
|
return Optional.of(out.toByteArray());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
conn.disconnect();
|
||||||
}
|
}
|
||||||
|
}).onSuccess(response -> {
|
||||||
|
response.ifPresent(bytes -> {
|
||||||
|
Gson gson = new GsonBuilder().setLenient().create();
|
||||||
|
String json = new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
Map<String, String> map = gson.fromJson(json, new TypeToken<Map<String, String>>() {
|
||||||
|
}.getType());
|
||||||
|
if (map != null) {
|
||||||
|
this.compareVersions(map);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).onError(Exception.class, e -> {
|
||||||
|
LOG.warn("Error checking for updates", e);
|
||||||
}).andFinally(() -> {
|
}).andFinally(() -> {
|
||||||
checkForUpdatesStatus.setText("");
|
checkForUpdatesStatus.setText("");
|
||||||
checkForUpdatesIndicator.setVisible(false);
|
checkForUpdatesIndicator.setVisible(false);
|
||||||
}).run();
|
}).runOnce(executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void compareVersions(final Map<String, String> latestVersions) {
|
private void compareVersions(final Map<String, String> latestVersions) {
|
||||||
|
assert Platform.isFxApplicationThread();
|
||||||
final String latestVersion;
|
final String latestVersion;
|
||||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||||
latestVersion = latestVersions.get("mac");
|
latestVersion = latestVersions.get("mac");
|
||||||
@@ -163,11 +181,9 @@ public class WelcomeController implements ViewController {
|
|||||||
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
|
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
|
||||||
if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) {
|
if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) {
|
||||||
final String msg = String.format(localization.getString("welcome.newVersionMessage"), latestVersion, currentVersion);
|
final String msg = String.format(localization.getString("welcome.newVersionMessage"), latestVersion, currentVersion);
|
||||||
Platform.runLater(() -> {
|
this.updateLink.setText(msg);
|
||||||
this.updateLink.setText(msg);
|
this.updateLink.setVisible(true);
|
||||||
this.updateLink.setVisible(true);
|
this.updateLink.setDisable(false);
|
||||||
this.updateLink.setDisable(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,9 +68,7 @@ public class DirectoryListCell extends DraggableListCell<Vault> {
|
|||||||
}
|
}
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case UNLOCKED:
|
case UNLOCKED:
|
||||||
case MOUNTED:
|
case PROCESSING:
|
||||||
case MOUNTING:
|
|
||||||
case UNMOUNTING:
|
|
||||||
return "\uf09c";
|
return "\uf09c";
|
||||||
case LOCKED:
|
case LOCKED:
|
||||||
default:
|
default:
|
||||||
@@ -84,9 +82,7 @@ public class DirectoryListCell extends DraggableListCell<Vault> {
|
|||||||
}
|
}
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case UNLOCKED:
|
case UNLOCKED:
|
||||||
case MOUNTED:
|
case PROCESSING:
|
||||||
case MOUNTING:
|
|
||||||
case UNMOUNTING:
|
|
||||||
return UNLOCKED_ICON_COLOR;
|
return UNLOCKED_ICON_COLOR;
|
||||||
case LOCKED:
|
case LOCKED:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -2,43 +2,105 @@
|
|||||||
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
||||||
*
|
*
|
||||||
* Contributors:
|
* Contributors:
|
||||||
* Sebastian Stenzel - initial API and implementation
|
* Sebastian Stenzel - initial API and implementation
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.cryptomator.ui.controls;
|
package org.cryptomator.ui.controls;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import javafx.scene.control.PasswordField;
|
||||||
|
import javafx.scene.input.DragEvent;
|
||||||
|
import javafx.scene.input.Dragboard;
|
||||||
|
import javafx.scene.input.TransferMode;
|
||||||
|
|
||||||
|
import java.nio.CharBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import javafx.scene.control.PasswordField;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compromise in security. While the text can be swiped, any access to the {@link #getText()} method will create a copy of the String in the heap.
|
* Patched PasswordField that doesn't create String copies of the password in memory. Instead the password is stored in a char[] that can be swiped.
|
||||||
|
*
|
||||||
|
* @implNote Since {@link #setText(String)} is final, we can not override its behaviour. For that reason you should not use the {@link #textProperty()} for anything else than display purposes.
|
||||||
*/
|
*/
|
||||||
public class SecPasswordField extends PasswordField {
|
public class SecPasswordField extends PasswordField {
|
||||||
|
|
||||||
private static final char SWIPE_CHAR = ' ';
|
private static final char SWIPE_CHAR = ' ';
|
||||||
|
private static final int INITIAL_BUFFER_SIZE = 50;
|
||||||
|
private static final int GROW_BUFFER_SIZE = 50;
|
||||||
|
private static final String PLACEHOLDER = "*";
|
||||||
|
|
||||||
|
private char[] content = new char[INITIAL_BUFFER_SIZE];
|
||||||
|
private int length = 0;
|
||||||
|
|
||||||
|
public SecPasswordField() {
|
||||||
|
this.onDragOverProperty().set(this::handleDragOver);
|
||||||
|
this.onDragDroppedProperty().set(this::handleDragDropped);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDragOver(DragEvent event) {
|
||||||
|
Dragboard dragboard = event.getDragboard();
|
||||||
|
if (dragboard.hasString() && dragboard.getString() != null) {
|
||||||
|
event.acceptTransferModes(TransferMode.COPY);
|
||||||
|
}
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDragDropped(DragEvent event) {
|
||||||
|
Dragboard dragboard = event.getDragboard();
|
||||||
|
if (dragboard.hasString() && dragboard.getString() != null) {
|
||||||
|
insertText(getCaretPosition(), dragboard.getString());
|
||||||
|
}
|
||||||
|
event.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void replaceText(int start, int end, String text) {
|
||||||
|
int removed = end - start;
|
||||||
|
int added = text.length();
|
||||||
|
this.length += added - removed;
|
||||||
|
growContentIfNeeded();
|
||||||
|
text.getChars(0, text.length(), content, start);
|
||||||
|
|
||||||
|
String placeholderString = Strings.repeat(PLACEHOLDER, text.length());
|
||||||
|
super.replaceText(start, end, placeholderString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void growContentIfNeeded() {
|
||||||
|
if (this.length > content.length) {
|
||||||
|
char[] newContent = new char[content.length + GROW_BUFFER_SIZE];
|
||||||
|
System.arraycopy(content, 0, newContent, 0, content.length);
|
||||||
|
swipe();
|
||||||
|
this.content = newContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link #getContent()} uses a StringBuilder, which in turn is backed by a char[].
|
* Creates a CharSequence by wrapping the password characters.
|
||||||
* The delete operation of AbstractStringBuilder closes the gap, that forms by deleting chars, by moving up the following chars.
|
*
|
||||||
* <br/>
|
* @return A character sequence backed by the SecPasswordField's buffer (not a copy).
|
||||||
* Imagine the following example with <code>pass</code> being the password, <code>x</code> being the swipe char and <code>'</code> being the offset of the char array:
|
* @implNote The CharSequence will not copy the backing char[].
|
||||||
* <ol>
|
* Therefore any mutation to the SecPasswordField's content will mutate or eventually swipe the returned CharSequence.
|
||||||
* <li>Append filling chars to the end of the password: <code>passxxxx'</code></li>
|
* @see #swipe()
|
||||||
* <li>Delete first 4 chars. Internal implementation will then copy subsequent chars to the position, where the deletion occured: <code>xxxx'xxxx</code></li>
|
*/
|
||||||
* <li>Delete first 4 chars again, as we appended 4 chars in step 1: <code>'xxxxxx</code></li>
|
@Override
|
||||||
* </ol>
|
public CharSequence getCharacters() {
|
||||||
|
return CharBuffer.wrap(content, 0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(char[] password) {
|
||||||
|
swipe();
|
||||||
|
content = Arrays.copyOf(password, password.length);
|
||||||
|
length = password.length;
|
||||||
|
|
||||||
|
String placeholderString = Strings.repeat(PLACEHOLDER, password.length);
|
||||||
|
setText(placeholderString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the stored password by overriding each character with a different character.
|
||||||
*/
|
*/
|
||||||
public void swipe() {
|
public void swipe() {
|
||||||
final int pwLength = this.getContent().length();
|
Arrays.fill(content, SWIPE_CHAR);
|
||||||
final char[] fillingChars = new char[pwLength];
|
|
||||||
Arrays.fill(fillingChars, SWIPE_CHAR);
|
|
||||||
this.getContent().insert(pwLength, new String(fillingChars), false);
|
|
||||||
this.getContent().delete(0, pwLength, true);
|
|
||||||
this.getContent().delete(0, pwLength, true);
|
|
||||||
// previous text has now been overwritten. but we still need to update the text to trigger some property bindings:
|
|
||||||
this.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import javax.inject.Inject;
|
|||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import org.cryptomator.cryptolib.api.CryptoException;
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
|
|
||||||
import org.cryptomator.keychain.KeychainAccess;
|
import org.cryptomator.keychain.KeychainAccess;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -74,33 +73,21 @@ public class AutoUnlocker {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
vault.unlock(CharBuffer.wrap(storedPw));
|
vault.unlock(CharBuffer.wrap(storedPw));
|
||||||
mountSilently(vault);
|
revealSilently(vault);
|
||||||
} catch (IOException | CryptoException e) {
|
} catch (IOException | CryptoException | Volume.VolumeException e) {
|
||||||
LOG.error("Auto unlock failed.", e);
|
LOG.error("Auto unlock failed.", e);
|
||||||
} finally {
|
} finally {
|
||||||
Arrays.fill(storedPw, ' ');
|
Arrays.fill(storedPw, ' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mountSilently(Vault unlockedVault) {
|
|
||||||
if (!unlockedVault.getVaultSettings().mountAfterUnlock().get()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
unlockedVault.mount();
|
|
||||||
revealSilently(unlockedVault);
|
|
||||||
} catch (CommandFailedException e) {
|
|
||||||
LOG.error("Auto unlock succeded, but mounting the drive failed.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void revealSilently(Vault mountedVault) {
|
private void revealSilently(Vault mountedVault) {
|
||||||
if (!mountedVault.getVaultSettings().revealAfterMount().get()) {
|
if (!mountedVault.getVaultSettings().revealAfterMount().get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
mountedVault.reveal();
|
mountedVault.reveal();
|
||||||
} catch (CommandFailedException e) {
|
} catch (Volume.VolumeException e) {
|
||||||
LOG.error("Auto unlock succeded, but revealing the drive failed.", e);
|
LOG.error("Auto unlock succeded, but revealing the drive failed.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
105
main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java
Normal file
105
main/ui/src/main/java/org/cryptomator/ui/model/DokanyVolume.java
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package org.cryptomator.ui.model;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
|
import org.cryptomator.frontend.dokany.Mount;
|
||||||
|
import org.cryptomator.frontend.dokany.MountFactory;
|
||||||
|
import org.cryptomator.frontend.dokany.MountFailedException;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.NotDirectoryException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
public class DokanyVolume implements Volume {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(DokanyVolume.class);
|
||||||
|
|
||||||
|
private static final String FS_TYPE_NAME = "Cryptomator File System";
|
||||||
|
|
||||||
|
private final VaultSettings vaultSettings;
|
||||||
|
private final MountFactory mountFactory;
|
||||||
|
private final WindowsDriveLetters windowsDriveLetters;
|
||||||
|
private Mount mount;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, WindowsDriveLetters windowsDriveLetters) {
|
||||||
|
this.vaultSettings = vaultSettings;
|
||||||
|
this.mountFactory = new MountFactory(executorService);
|
||||||
|
this.windowsDriveLetters = windowsDriveLetters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
return DokanyVolume.isSupportedStatic();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mount(CryptoFileSystem fs) throws VolumeException, IOException {
|
||||||
|
Path mountPath = getMountPoint();
|
||||||
|
String mountName = vaultSettings.mountName().get();
|
||||||
|
try {
|
||||||
|
this.mount = mountFactory.mount(fs.getPath("/"), mountPath, mountName, FS_TYPE_NAME);
|
||||||
|
} catch (MountFailedException e) {
|
||||||
|
if (vaultSettings.getIndividualMountPath().isPresent()) {
|
||||||
|
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPath);
|
||||||
|
}
|
||||||
|
throw new VolumeException("Unable to mount Filesystem", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path getMountPoint() 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 Paths.get(vaultSettings.winDriveLetter().get().charAt(0) + ":\\");
|
||||||
|
} else {
|
||||||
|
//auto assign drive letter
|
||||||
|
if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
|
||||||
|
return Paths.get(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\");
|
||||||
|
} else {
|
||||||
|
throw new VolumeException("No free drive letter available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkProvidedMountPoint(Path mountPoint) throws IOException {
|
||||||
|
if (!Files.isDirectory(mountPoint)) {
|
||||||
|
throw new NotDirectoryException(mountPoint.toString());
|
||||||
|
}
|
||||||
|
try (DirectoryStream<Path> ds = Files.newDirectoryStream(mountPoint)) {
|
||||||
|
if (ds.iterator().hasNext()) {
|
||||||
|
throw new DirectoryNotEmptyException(mountPoint.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reveal() throws VolumeException {
|
||||||
|
boolean success = mount.reveal();
|
||||||
|
if (!success) {
|
||||||
|
throw new VolumeException("Reveal failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unmount() {
|
||||||
|
mount.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSupportedStatic() {
|
||||||
|
return MountFactory.isApplicable();
|
||||||
|
}
|
||||||
|
}
|
||||||
158
main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java
Normal file
158
main/ui/src/main/java/org/cryptomator/ui/model/FuseVolume.java
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package org.cryptomator.ui.model;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
|
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
|
||||||
|
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
|
||||||
|
import org.cryptomator.frontend.fuse.mount.FuseMountFactory;
|
||||||
|
import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException;
|
||||||
|
import org.cryptomator.frontend.fuse.mount.Mount;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.FileAlreadyExistsException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.NotDirectoryException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class FuseVolume implements Volume {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
|
||||||
|
|
||||||
|
// TODO: dont use fixed Strings and rather set them in some system environment variables in the cryptomator installer and load those!
|
||||||
|
private static final String DEFAULT_MOUNTROOTPATH_MAC = System.getProperty("user.home") + "/Library/Application Support/Cryptomator";
|
||||||
|
private static final String DEFAULT_MOUNTROOTPATH_LINUX = System.getProperty("user.home") + "/.Cryptomator";
|
||||||
|
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
|
||||||
|
|
||||||
|
private final VaultSettings vaultSettings;
|
||||||
|
|
||||||
|
private Mount fuseMnt;
|
||||||
|
private Path mountPoint;
|
||||||
|
private boolean createdTemporaryMountPoint;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public FuseVolume(VaultSettings vaultSettings) {
|
||||||
|
this.vaultSettings = vaultSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mount(CryptoFileSystem fs) throws IOException, FuseNotSupportedException, VolumeException {
|
||||||
|
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
|
||||||
|
if (optionalCustomMountPoint.isPresent()) {
|
||||||
|
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
||||||
|
checkProvidedMountPoint(customMountPoint);
|
||||||
|
this.mountPoint = customMountPoint;
|
||||||
|
this.createdTemporaryMountPoint = false;
|
||||||
|
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
|
||||||
|
} else {
|
||||||
|
this.mountPoint = createTemporaryMountPoint();
|
||||||
|
this.createdTemporaryMountPoint = true;
|
||||||
|
LOG.debug("Successfully created mount point: {}", mountPoint);
|
||||||
|
}
|
||||||
|
mount(fs.getPath("/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 createTemporaryMountPoint() throws IOException {
|
||||||
|
Path parent = Paths.get(SystemUtils.IS_OS_MAC ? DEFAULT_MOUNTROOTPATH_MAC : DEFAULT_MOUNTROOTPATH_LINUX);
|
||||||
|
String basename = vaultSettings.getId();
|
||||||
|
for (int i = 0; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
|
||||||
|
try {
|
||||||
|
Path mountPath = parent.resolve(basename + "_" + i);
|
||||||
|
Files.createDirectory(mountPath);
|
||||||
|
return mountPath;
|
||||||
|
} catch (FileAlreadyExistsException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.error("Failed to create mount path at {}/{}_x. Giving up after {} attempts.", parent, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
|
||||||
|
throw new FileAlreadyExistsException(parent.toString() + "/" + basename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mount(Path root) throws VolumeException {
|
||||||
|
try {
|
||||||
|
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
||||||
|
.withMountName(vaultSettings.mountName().getValue()) //
|
||||||
|
.withMountPath(mountPoint) //
|
||||||
|
.build();
|
||||||
|
this.fuseMnt = FuseMountFactory.getMounter().mount(root, envVars);
|
||||||
|
} catch (CommandFailedException e) {
|
||||||
|
throw new VolumeException("Unable to mount Filesystem", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reveal() throws VolumeException {
|
||||||
|
try {
|
||||||
|
fuseMnt.revealInFileManager();
|
||||||
|
} catch (CommandFailedException e) {
|
||||||
|
LOG.debug("Revealing the vault in file manger failed: " + e.getMessage());
|
||||||
|
throw new VolumeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsForcedUnmount() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void unmountForced() throws VolumeException {
|
||||||
|
try {
|
||||||
|
fuseMnt.unmountForced();
|
||||||
|
fuseMnt.close();
|
||||||
|
} catch (CommandFailedException e) {
|
||||||
|
throw new VolumeException(e);
|
||||||
|
}
|
||||||
|
deleteTemporaryMountPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void unmount() throws VolumeException {
|
||||||
|
try {
|
||||||
|
fuseMnt.unmount();
|
||||||
|
fuseMnt.close();
|
||||||
|
} catch (CommandFailedException e) {
|
||||||
|
throw new VolumeException(e);
|
||||||
|
}
|
||||||
|
deleteTemporaryMountPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteTemporaryMountPoint() {
|
||||||
|
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
|
||||||
|
public boolean isSupported() {
|
||||||
|
return FuseVolume.isSupportedStatic();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSupportedStatic() {
|
||||||
|
return (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) && FuseMountFactory.isFuseSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -112,13 +112,8 @@ public abstract class UpgradeStrategy {
|
|||||||
public boolean isApplicable(Vault vault) {
|
public boolean isApplicable(Vault vault) {
|
||||||
final Path masterkeyFile = vault.getPath().resolve(MASTERKEY_FILENAME);
|
final Path masterkeyFile = vault.getPath().resolve(MASTERKEY_FILENAME);
|
||||||
try {
|
try {
|
||||||
if (Files.isRegularFile(masterkeyFile)) {
|
byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
|
||||||
byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
|
return KeyFile.parse(masterkeyFileContents).getVersion() == vaultVersionBeforeUpgrade;
|
||||||
return KeyFile.parse(masterkeyFileContents).getVersion() == vaultVersionBeforeUpgrade;
|
|
||||||
} else {
|
|
||||||
LOG.warn("Not a file: {}", masterkeyFile);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Could not determine, whether upgrade is applicable.", e);
|
LOG.warn("Could not determine, whether upgrade is applicable.", e);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.ui.model;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.FileVisitOption;
|
import java.nio.file.FileVisitOption;
|
||||||
import java.nio.file.FileVisitResult;
|
import java.nio.file.FileVisitResult;
|
||||||
@@ -19,11 +19,7 @@ import java.util.EnumSet;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import com.google.common.io.BaseEncoding;
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Base32;
|
|
||||||
import org.apache.commons.codec.binary.BaseNCodec;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.cryptomator.cryptolib.Cryptors;
|
import org.cryptomator.cryptolib.Cryptors;
|
||||||
import org.cryptomator.cryptolib.api.Cryptor;
|
import org.cryptomator.cryptolib.api.Cryptor;
|
||||||
@@ -32,6 +28,8 @@ import org.cryptomator.ui.l10n.Localization;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains the collective knowledge of all creatures who were alive during the development of vault format 3.
|
* Contains the collective knowledge of all creatures who were alive during the development of vault format 3.
|
||||||
* This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these.
|
* This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these.
|
||||||
@@ -42,14 +40,15 @@ class UpgradeVersion3to4 extends UpgradeStrategy {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3to4.class);
|
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3to4.class);
|
||||||
private static final Pattern LVL1_DIR_PATTERN = Pattern.compile("[A-Z2-7]{2}");
|
private static final Pattern LVL1_DIR_PATTERN = Pattern.compile("[A-Z2-7]{2}");
|
||||||
private static final Pattern LVL2_DIR_PATTERN = Pattern.compile("[A-Z2-7]{30}");
|
private static final Pattern LVL2_DIR_PATTERN = Pattern.compile("[A-Z2-7]{30}");
|
||||||
private static final Pattern BASE32_FOLLOWED_BY_UNDERSCORE_PATTERN = Pattern.compile("^(([A-Z2-7]{8})*[A-Z2-7=]{8})_");
|
private static final Pattern BASE32_PATTERN = Pattern.compile("^(([A-Z2-7]{8})*[A-Z2-7=]{8})");
|
||||||
|
private static final Pattern BASE32_FOLLOWED_BY_UNDERSCORE_PATTERN = Pattern.compile(BASE32_PATTERN.pattern() + "_");
|
||||||
private static final int FILE_MIN_SIZE = 88; // vault version 3 files have a header of 88 bytes (assuming no chunks at all)
|
private static final int FILE_MIN_SIZE = 88; // vault version 3 files have a header of 88 bytes (assuming no chunks at all)
|
||||||
private static final String LONG_FILENAME_SUFFIX = ".lng";
|
private static final String LONG_FILENAME_EXT = ".lng";
|
||||||
private static final String OLD_FOLDER_SUFFIX = "_";
|
private static final String OLD_FOLDER_SUFFIX = "_";
|
||||||
private static final String NEW_FOLDER_PREFIX = "0";
|
private static final String NEW_FOLDER_PREFIX = "0";
|
||||||
|
|
||||||
private final MessageDigest sha1 = MessageDigestSupplier.SHA1.get();
|
private final MessageDigest sha1 = MessageDigestSupplier.SHA1.get();
|
||||||
private final BaseNCodec base32 = new Base32();
|
private final BaseEncoding base32 = BaseEncoding.base32();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UpgradeVersion3to4(Localization localization) {
|
public UpgradeVersion3to4(Localization localization) {
|
||||||
@@ -96,10 +95,12 @@ class UpgradeVersion3to4 extends UpgradeStrategy {
|
|||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
String name = file.getFileName().toString();
|
String name = file.getFileName().toString();
|
||||||
if (name.endsWith(LONG_FILENAME_SUFFIX)) {
|
if (attrs.size() > FILE_MIN_SIZE) {
|
||||||
|
LOG.trace("Skipping non-directory file {}.", file);
|
||||||
|
} else if (name.endsWith(LONG_FILENAME_EXT)) {
|
||||||
migrateLong(metadataDir, file);
|
migrateLong(metadataDir, file);
|
||||||
} else {
|
} else {
|
||||||
migrate(file, attrs);
|
migrate(file);
|
||||||
}
|
}
|
||||||
return FileVisitResult.CONTINUE;
|
return FileVisitResult.CONTINUE;
|
||||||
}
|
}
|
||||||
@@ -112,14 +113,13 @@ class UpgradeVersion3to4 extends UpgradeStrategy {
|
|||||||
LOG.info("Migration finished.");
|
LOG.info("Migration finished.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void migrate(Path file, BasicFileAttributes attrs) throws IOException {
|
private void migrate(Path file) throws IOException {
|
||||||
String name = file.getFileName().toString();
|
String name = file.getFileName().toString();
|
||||||
long size = attrs.size();
|
|
||||||
Matcher m = BASE32_FOLLOWED_BY_UNDERSCORE_PATTERN.matcher(name);
|
Matcher m = BASE32_FOLLOWED_BY_UNDERSCORE_PATTERN.matcher(name);
|
||||||
if (attrs.isRegularFile() && m.find(0) && size < FILE_MIN_SIZE) {
|
if (m.find(0)) {
|
||||||
String base32 = m.group(1);
|
String base32 = m.group(1);
|
||||||
String suffix = name.substring(m.end());
|
String suffix = name.substring(m.end());
|
||||||
String renamed = NEW_FOLDER_PREFIX + base32 + (suffix.isEmpty() ? "" : " " + suffix);
|
String renamed = NEW_FOLDER_PREFIX + base32 + suffix;
|
||||||
renameWithoutOverwriting(file, renamed);
|
renameWithoutOverwriting(file, renamed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,22 +135,35 @@ class UpgradeVersion3to4 extends UpgradeStrategy {
|
|||||||
|
|
||||||
private void migrateLong(Path metadataDir, Path path) throws IOException {
|
private void migrateLong(Path metadataDir, Path path) throws IOException {
|
||||||
String oldName = path.getFileName().toString();
|
String oldName = path.getFileName().toString();
|
||||||
Path oldMetadataFile = metadataDir.resolve(oldName.substring(0, 2)).resolve(oldName.substring(2, 4)).resolve(oldName);
|
assert oldName.endsWith(LONG_FILENAME_EXT);
|
||||||
if (Files.isRegularFile(oldMetadataFile)) {
|
String oldNameBase = StringUtils.removeEnd(oldName, LONG_FILENAME_EXT);
|
||||||
String oldContent = new String(Files.readAllBytes(oldMetadataFile), UTF_8);
|
|
||||||
if (oldContent.endsWith(OLD_FOLDER_SUFFIX)) {
|
Matcher m = BASE32_PATTERN.matcher(oldNameBase);
|
||||||
String newContent = NEW_FOLDER_PREFIX + StringUtils.removeEnd(oldContent, OLD_FOLDER_SUFFIX);
|
if (m.find(0)) {
|
||||||
String newName = base32.encodeAsString(sha1.digest(newContent.getBytes(UTF_8))) + LONG_FILENAME_SUFFIX;
|
String oldNameBase32 = m.group(1);
|
||||||
|
String oldNameSuffix = oldNameBase.substring(m.end());
|
||||||
|
String oldCanonicalName = oldNameBase32 + LONG_FILENAME_EXT;
|
||||||
|
|
||||||
|
Path oldMetadataFile = metadataDir.resolve(oldCanonicalName.substring(0, 2)).resolve(oldCanonicalName.substring(2, 4)).resolve(oldCanonicalName);
|
||||||
|
if (!Files.isReadable(oldMetadataFile)) {
|
||||||
|
LOG.warn("Found uninflatable long file name. Expected: {}", oldMetadataFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String oldLongName = new String(Files.readAllBytes(oldMetadataFile), UTF_8);
|
||||||
|
if (oldLongName.endsWith(OLD_FOLDER_SUFFIX)) {
|
||||||
|
String newLongName = NEW_FOLDER_PREFIX + StringUtils.removeEnd(oldLongName, OLD_FOLDER_SUFFIX);
|
||||||
|
String newCanonicalBase32 = base32.encode(sha1.digest(newLongName.getBytes(UTF_8)));
|
||||||
|
String newCanonicalName = newCanonicalBase32 + LONG_FILENAME_EXT;
|
||||||
|
Path newMetadataFile = metadataDir.resolve(newCanonicalName.substring(0, 2)).resolve(newCanonicalName.substring(2, 4)).resolve(newCanonicalName);
|
||||||
|
String newName = newCanonicalBase32 + oldNameSuffix + LONG_FILENAME_EXT;
|
||||||
Path newPath = path.resolveSibling(newName);
|
Path newPath = path.resolveSibling(newName);
|
||||||
Path newMetadataFile = metadataDir.resolve(newName.substring(0, 2)).resolve(newName.substring(2, 4)).resolve(newName);
|
|
||||||
Files.move(path, newPath);
|
Files.move(path, newPath);
|
||||||
Files.createDirectories(newMetadataFile.getParent());
|
Files.createDirectories(newMetadataFile.getParent());
|
||||||
Files.write(newMetadataFile, newContent.getBytes(UTF_8));
|
Files.write(newMetadataFile, newLongName.getBytes(UTF_8));
|
||||||
Files.delete(oldMetadataFile);
|
LOG.info("Renaming {} to {}.", path, newName);
|
||||||
LOG.info("Renaming {} to {}\nDeleting {}\nCreating {}", path, newName, oldMetadataFile, newMetadataFile);
|
LOG.info("Creating {}.", newMetadataFile);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
LOG.warn("Found uninflatable long file name. Expected: {}", oldMetadataFile);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class UpgradeVersion4to5 extends UpgradeStrategy {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
if (attrs.isRegularFile() && BASE32_PATTERN.matcher(file.getFileName().toString()).find() && attrs.size() > cryptor.fileHeaderCryptor().headerSize()) {
|
if (BASE32_PATTERN.matcher(file.getFileName().toString()).find() && attrs.size() > cryptor.fileHeaderCryptor().headerSize()) {
|
||||||
migrate(file, attrs, cryptor);
|
migrate(file, attrs, cryptor);
|
||||||
} else {
|
} else {
|
||||||
LOG.info("Skipping irrelevant file {}.", file);
|
LOG.info("Skipping irrelevant file {}.", file);
|
||||||
|
|||||||
@@ -8,51 +8,43 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.ui.model;
|
||||||
|
|
||||||
import java.io.IOException;
|
import com.google.common.base.Strings;
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
|
||||||
import java.nio.file.DirectoryStream;
|
|
||||||
import java.nio.file.FileAlreadyExistsException;
|
|
||||||
import java.nio.file.FileSystem;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.NoSuchFileException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.LazyInitializer;
|
|
||||||
import org.cryptomator.common.Optionals;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
|
||||||
import org.cryptomator.cryptolib.api.CryptoException;
|
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
|
||||||
import org.cryptomator.frontend.webdav.ServerLifecycleException;
|
|
||||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
|
||||||
import org.cryptomator.frontend.webdav.mount.MountParams;
|
|
||||||
import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
|
|
||||||
import org.cryptomator.frontend.webdav.mount.Mounter.Mount;
|
|
||||||
import org.cryptomator.frontend.webdav.mount.Mounter.UnmountOperation;
|
|
||||||
import org.cryptomator.frontend.webdav.servlet.WebDavServletController;
|
|
||||||
import org.cryptomator.ui.model.VaultModule.PerVault;
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.Binding;
|
import javafx.beans.binding.Binding;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.LazyInitializer;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
|
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||||
|
import org.cryptomator.ui.model.VaultModule.PerVault;
|
||||||
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileAlreadyExistsException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.nio.file.NotDirectoryException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
@PerVault
|
@PerVault
|
||||||
public class Vault {
|
public class Vault {
|
||||||
@@ -60,25 +52,25 @@ public class Vault {
|
|||||||
public static final Predicate<Vault> NOT_LOCKED = hasState(State.LOCKED).negate();
|
public static final Predicate<Vault> NOT_LOCKED = hasState(State.LOCKED).negate();
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
|
||||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
||||||
|
private static final String LOCALHOST_ALIAS = "cryptomator-vault";
|
||||||
|
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
private final WebDavServer server;
|
private final Provider<Volume> volumeProvider;
|
||||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem = new AtomicReference<>();
|
private final AtomicReference<CryptoFileSystem> cryptoFileSystem = new AtomicReference<>();
|
||||||
private final ObjectProperty<State> state = new SimpleObjectProperty<State>(State.LOCKED);
|
private final ObjectProperty<State> state = new SimpleObjectProperty<State>(State.LOCKED);
|
||||||
|
|
||||||
private WebDavServletController servlet;
|
private Volume volume;
|
||||||
private Mount mount;
|
|
||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
LOCKED, UNLOCKED, MOUNTING, MOUNTED, UNMOUNTING
|
LOCKED, PROCESSING, UNLOCKED
|
||||||
};
|
}
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Vault(Settings settings, VaultSettings vaultSettings, WebDavServer server) {
|
Vault(Settings settings, VaultSettings vaultSettings, Provider<Volume> volumeProvider) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.server = server;
|
this.volumeProvider = volumeProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
@@ -90,22 +82,19 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CryptoFileSystem unlockCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
|
private CryptoFileSystem unlockCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
|
||||||
|
List<FileSystemFlags> flags = new ArrayList<>();
|
||||||
|
if (vaultSettings.usesReadOnlyMode().get()) {
|
||||||
|
flags.add(FileSystemFlags.READONLY);
|
||||||
|
}
|
||||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
||||||
.withPassphrase(passphrase) //
|
.withPassphrase(passphrase) //
|
||||||
.withFlags() //
|
.withFlags(flags) //
|
||||||
.withMasterkeyFilename(MASTERKEY_FILENAME) //
|
.withMasterkeyFilename(MASTERKEY_FILENAME) //
|
||||||
.build();
|
.build();
|
||||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void create(CharSequence passphrase) throws IOException {
|
public void create(CharSequence passphrase) throws IOException {
|
||||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(getPath())) {
|
|
||||||
for (Path file : stream) {
|
|
||||||
if (!file.getFileName().toString().startsWith(".")) {
|
|
||||||
throw new DirectoryNotEmptyException(getPath().toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isValidVaultDirectory()) {
|
if (!isValidVaultDirectory()) {
|
||||||
CryptoFileSystemProvider.initialize(getPath(), MASTERKEY_FILENAME, passphrase);
|
CryptoFileSystemProvider.initialize(getPath(), MASTERKEY_FILENAME, passphrase);
|
||||||
} else {
|
} else {
|
||||||
@@ -117,69 +106,40 @@ public class Vault {
|
|||||||
CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase);
|
CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void unlock(CharSequence passphrase) throws ServerLifecycleException, CryptoException, IOException {
|
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
|
||||||
FileSystem fs = getCryptoFileSystem(passphrase);
|
Platform.runLater(() -> state.set(State.PROCESSING));
|
||||||
if (!server.isRunning()) {
|
try {
|
||||||
server.start();
|
if (vaultSettings.usesIndividualMountPath().get() && Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
|
||||||
|
throw new NotDirectoryException("");
|
||||||
|
}
|
||||||
|
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
|
||||||
|
volume = volumeProvider.get();
|
||||||
|
volume.mount(fs);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
state.set(State.UNLOCKED);
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
Platform.runLater(() -> state.set(State.LOCKED));
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get());
|
}
|
||||||
servlet.start();
|
|
||||||
|
public synchronized void lock(boolean forced) throws Volume.VolumeException {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
state.set(State.UNLOCKED);
|
state.set(State.PROCESSING);
|
||||||
});
|
});
|
||||||
}
|
if (forced && volume.supportsForcedUnmount()) {
|
||||||
|
volume.unmountForced();
|
||||||
public synchronized void mount() throws CommandFailedException {
|
} else {
|
||||||
if (servlet == null) {
|
volume.unmount();
|
||||||
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet.");
|
|
||||||
}
|
|
||||||
|
|
||||||
MountParams mountParams = MountParams.create() //
|
|
||||||
.withWindowsDriveLetter(vaultSettings.winDriveLetter().get()) //
|
|
||||||
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get()) //
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
state.set(State.MOUNTING);
|
|
||||||
});
|
|
||||||
mount = servlet.mount(mountParams); // might block this thread for a while
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
state.set(State.MOUNTED);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void unmount() throws CommandFailedException {
|
|
||||||
unmount(Function.identity());
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void unmountForced() throws CommandFailedException {
|
|
||||||
unmount(Optionals.unwrap(Mount::forced));
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void unmount(Function<Mount, ? extends UnmountOperation> unmountOperationChooser) throws CommandFailedException {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
state.set(State.UNMOUNTING);
|
|
||||||
});
|
|
||||||
if (mount != null) {
|
|
||||||
unmountOperationChooser.apply(mount).unmount();
|
|
||||||
mount = null;
|
|
||||||
}
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
state.set(State.UNLOCKED);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean supportsForcedUnmount() {
|
|
||||||
return mount != null && mount.forced().isPresent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void lock() throws ServerLifecycleException, IOException {
|
|
||||||
if (servlet != null) {
|
|
||||||
servlet.stop();
|
|
||||||
}
|
}
|
||||||
CryptoFileSystem fs = cryptoFileSystem.getAndSet(null);
|
CryptoFileSystem fs = cryptoFileSystem.getAndSet(null);
|
||||||
if (fs != null) {
|
if (fs != null) {
|
||||||
fs.close();
|
try {
|
||||||
|
fs.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Error closing file system.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
state.set(State.LOCKED);
|
state.set(State.LOCKED);
|
||||||
@@ -191,29 +151,22 @@ public class Vault {
|
|||||||
*/
|
*/
|
||||||
public void prepareForShutdown() {
|
public void prepareForShutdown() {
|
||||||
try {
|
try {
|
||||||
unmount();
|
lock(false);
|
||||||
} catch (CommandFailedException e) {
|
} catch (Volume.VolumeException e) {
|
||||||
if (supportsForcedUnmount()) {
|
if (volume.supportsForcedUnmount()) {
|
||||||
try {
|
try {
|
||||||
unmountForced();
|
lock(true);
|
||||||
} catch (CommandFailedException e1) {
|
} catch (Volume.VolumeException e1) {
|
||||||
LOG.warn("Failed to force unmount vault.");
|
LOG.warn("Failed to force lock vault.", e1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOG.warn("Failed to gracefully unmount vault.");
|
LOG.warn("Failed to gracefully lock vault.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
lock();
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.warn("Failed to lock vault.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reveal() throws CommandFailedException {
|
public void reveal() throws Volume.VolumeException {
|
||||||
if (mount != null) {
|
volume.reveal();
|
||||||
mount.reveal();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
@@ -235,17 +188,13 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Observable[] observables() {
|
public Observable[] observables() {
|
||||||
return new Observable[] {state};
|
return new Observable[]{state};
|
||||||
}
|
}
|
||||||
|
|
||||||
public VaultSettings getVaultSettings() {
|
public VaultSettings getVaultSettings() {
|
||||||
return vaultSettings;
|
return vaultSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized String getWebDavUrl() {
|
|
||||||
return servlet.getServletRootUri().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path getPath() {
|
public Path getPath() {
|
||||||
return vaultSettings.path().getValue();
|
return vaultSettings.path().getValue();
|
||||||
}
|
}
|
||||||
@@ -300,6 +249,14 @@ public class Vault {
|
|||||||
return vaultSettings.mountName().get();
|
return vaultSettings.mountName().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCustomMountPath() {
|
||||||
|
return vaultSettings.individualMountPath().getValueSafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomMountPath(String mountPath) {
|
||||||
|
vaultSettings.individualMountPath().set(mountPath);
|
||||||
|
}
|
||||||
|
|
||||||
public void setMountName(String mountName) throws IllegalArgumentException {
|
public void setMountName(String mountName) throws IllegalArgumentException {
|
||||||
if (StringUtils.isBlank(mountName)) {
|
if (StringUtils.isBlank(mountName)) {
|
||||||
throw new IllegalArgumentException("mount name is empty");
|
throw new IllegalArgumentException("mount name is empty");
|
||||||
@@ -347,4 +304,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean supportsForcedUnmount() {
|
||||||
|
return volume.supportsForcedUnmount();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,11 @@ public class VaultList extends TransformationList<Vault, VaultSettings> {
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getViewIndex(int index) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Vault get(int index) {
|
public Vault get(int index) {
|
||||||
VaultSettings s = source.get(index);
|
VaultSettings s = source.get(index);
|
||||||
|
|||||||
@@ -12,14 +12,19 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import javax.inject.Scope;
|
import javax.inject.Scope;
|
||||||
|
|
||||||
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class VaultModule {
|
public class VaultModule {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class);
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
|
|
||||||
public VaultModule(VaultSettings vaultSettings) {
|
public VaultModule(VaultSettings vaultSettings) {
|
||||||
@@ -36,6 +41,23 @@ public class VaultModule {
|
|||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@interface PerVault {
|
@interface PerVault {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
|
||||||
|
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
|
||||||
|
if (VolumeImpl.DOKANY == preferredImpl && dokanyVolume.isSupported()) {
|
||||||
|
return dokanyVolume;
|
||||||
|
} else if (VolumeImpl.FUSE == preferredImpl && fuseVolume.isSupported()) {
|
||||||
|
return fuseVolume;
|
||||||
|
} else {
|
||||||
|
if (VolumeImpl.WEBDAV != preferredImpl) {
|
||||||
|
LOG.warn("Using WebDAV, because {} is not supported.", preferredImpl.getDisplayName());
|
||||||
|
}
|
||||||
|
assert webDavVolume.isSupported();
|
||||||
|
return webDavVolume;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
75
main/ui/src/main/java/org/cryptomator/ui/model/Volume.java
Normal file
75
main/ui/src/main/java/org/cryptomator/ui/model/Volume.java
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package org.cryptomator.ui.model;
|
||||||
|
|
||||||
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a Volume and usess it to mount an unlocked vault
|
||||||
|
*/
|
||||||
|
public interface Volume {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks in constant time whether this volume type is supported on the system running Cryptomator.
|
||||||
|
*
|
||||||
|
* @return true if this volume can be mounted
|
||||||
|
*/
|
||||||
|
boolean isSupported();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param fs
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
void mount(CryptoFileSystem fs) throws IOException, VolumeException;
|
||||||
|
|
||||||
|
void reveal() throws VolumeException;
|
||||||
|
|
||||||
|
void unmount() throws VolumeException;
|
||||||
|
|
||||||
|
// optional forced unmounting:
|
||||||
|
|
||||||
|
default boolean supportsForcedUnmount() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void unmountForced() throws VolumeException {
|
||||||
|
throw new VolumeException("Operation not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static VolumeImpl[] getCurrentSupportedAdapters() {
|
||||||
|
return Stream.of(VolumeImpl.values()).filter(impl -> {
|
||||||
|
switch (impl) {
|
||||||
|
case WEBDAV:
|
||||||
|
return WebDavVolume.isSupportedStatic();
|
||||||
|
case DOKANY:
|
||||||
|
return DokanyVolume.isSupportedStatic();
|
||||||
|
case FUSE:
|
||||||
|
return FuseVolume.isSupportedStatic();
|
||||||
|
default:
|
||||||
|
return false;//throw new IllegalStateException("Adapter not implemented.");
|
||||||
|
}
|
||||||
|
}).toArray(VolumeImpl[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when a volume-specific command such as mount/unmount/reveal failed.
|
||||||
|
*/
|
||||||
|
class VolumeException extends Exception {
|
||||||
|
|
||||||
|
public VolumeException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VolumeException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VolumeException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
131
main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java
Normal file
131
main/ui/src/main/java/org/cryptomator/ui/model/WebDavVolume.java
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package org.cryptomator.ui.model;
|
||||||
|
|
||||||
|
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
|
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||||
|
import org.cryptomator.frontend.webdav.mount.MountParams;
|
||||||
|
import org.cryptomator.frontend.webdav.mount.Mounter;
|
||||||
|
import org.cryptomator.frontend.webdav.servlet.WebDavServletController;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
public class WebDavVolume implements Volume {
|
||||||
|
|
||||||
|
private static final String LOCALHOST_ALIAS = "cryptomator-vault";
|
||||||
|
|
||||||
|
private final Provider<WebDavServer> serverProvider;
|
||||||
|
private final VaultSettings vaultSettings;
|
||||||
|
private final Settings settings;
|
||||||
|
|
||||||
|
private WebDavServer server;
|
||||||
|
private WebDavServletController servlet;
|
||||||
|
private Mounter.Mount mount;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings) {
|
||||||
|
this.serverProvider = serverProvider;
|
||||||
|
this.vaultSettings = vaultSettings;
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mount(CryptoFileSystem fs) throws VolumeException {
|
||||||
|
if (server == null) {
|
||||||
|
server = serverProvider.get();
|
||||||
|
}
|
||||||
|
if (!server.isRunning()) {
|
||||||
|
server.start();
|
||||||
|
}
|
||||||
|
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get());
|
||||||
|
servlet.start();
|
||||||
|
mount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mount() throws VolumeException {
|
||||||
|
if (servlet == null) {
|
||||||
|
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet.");
|
||||||
|
}
|
||||||
|
MountParams mountParams = MountParams.create() //
|
||||||
|
.withWindowsDriveLetter(vaultSettings.winDriveLetter().get()) //
|
||||||
|
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get())//
|
||||||
|
.withWebdavHostname(getLocalhostAliasOrNull()) //
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
this.mount = servlet.mount(mountParams); // might block this thread for a while
|
||||||
|
} catch (Mounter.CommandFailedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new VolumeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reveal() throws VolumeException {
|
||||||
|
try {
|
||||||
|
mount.reveal();
|
||||||
|
} catch (Mounter.CommandFailedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new VolumeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void unmount() throws VolumeException {
|
||||||
|
try {
|
||||||
|
mount.unmount();
|
||||||
|
} catch (Mounter.CommandFailedException e) {
|
||||||
|
throw new VolumeException(e);
|
||||||
|
}
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void unmountForced() throws VolumeException {
|
||||||
|
try {
|
||||||
|
mount.forced().orElseThrow(IllegalStateException::new).unmount();
|
||||||
|
} catch (Mounter.CommandFailedException e) {
|
||||||
|
throw new VolumeException(e);
|
||||||
|
}
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLocalhostAliasOrNull() {
|
||||||
|
try {
|
||||||
|
InetAddress alias = InetAddress.getByName(LOCALHOST_ALIAS);
|
||||||
|
if (alias.getHostAddress().equals("127.0.0.1")) {
|
||||||
|
return LOCALHOST_ALIAS;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanup() {
|
||||||
|
if (servlet != null) {
|
||||||
|
servlet.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
return WebDavVolume.isSupportedStatic();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsForcedUnmount() {
|
||||||
|
return mount != null && mount.forced().isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isSupportedStatic() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,11 +22,11 @@ import org.apache.commons.lang3.SystemUtils;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public final class WindowsDriveLetters {
|
public final class WindowsDriveLetters {
|
||||||
|
|
||||||
private static final Set<Character> A_TO_Z;
|
private static final Set<Character> D_TO_Z;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try (IntStream stream = IntStream.rangeClosed('A', 'Z')) {
|
try (IntStream stream = IntStream.rangeClosed('D', 'Z')) {
|
||||||
A_TO_Z = stream.mapToObj(i -> (char) i).collect(Collectors.toSet());
|
D_TO_Z = stream.mapToObj(i -> (char) i).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ public final class WindowsDriveLetters {
|
|||||||
public Set<Character> getAvailableDriveLetters() {
|
public Set<Character> getAvailableDriveLetters() {
|
||||||
Set<Character> occupiedDriveLetters = getOccupiedDriveLetters();
|
Set<Character> occupiedDriveLetters = getOccupiedDriveLetters();
|
||||||
Predicate<Character> isOccupiedDriveLetter = occupiedDriveLetters::contains;
|
Predicate<Character> isOccupiedDriveLetter = occupiedDriveLetters::contains;
|
||||||
return A_TO_Z.stream().filter(isOccupiedDriveLetter.negate()).collect(Collectors.toSet());
|
return D_TO_Z.stream().filter(isOccupiedDriveLetter.negate()).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.ui.util;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.cryptomator.common.ConsumerThrowingException;
|
|
||||||
import org.cryptomator.common.RunnableThrowingException;
|
|
||||||
import org.cryptomator.common.SupplierThrowingException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class AsyncTaskService {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AsyncTaskService.class);
|
|
||||||
|
|
||||||
private final ExecutorService executor;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public AsyncTaskService(ExecutorService executor) {
|
|
||||||
this.executor = executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new async task
|
|
||||||
*
|
|
||||||
* @param task Tasks to be invoked in a background thread.
|
|
||||||
* @return The async task
|
|
||||||
*/
|
|
||||||
public AsyncTaskWithoutSuccessHandler<Void> asyncTaskOf(RunnableThrowingException<?> task) {
|
|
||||||
return new AsyncTaskImpl<>(() -> {
|
|
||||||
task.run();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new async task
|
|
||||||
*
|
|
||||||
* @param task Tasks to be invoked in a background thread.
|
|
||||||
* @return The async task
|
|
||||||
*/
|
|
||||||
public <ResultType> AsyncTaskWithoutSuccessHandler<ResultType> asyncTaskOf(SupplierThrowingException<ResultType, ?> task) {
|
|
||||||
return new AsyncTaskImpl<>(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AsyncTaskImpl<ResultType> implements AsyncTaskWithoutSuccessHandler<ResultType> {
|
|
||||||
|
|
||||||
private final SupplierThrowingException<ResultType, ?> task;
|
|
||||||
|
|
||||||
private ConsumerThrowingException<ResultType, ?> successHandler = value -> {
|
|
||||||
};
|
|
||||||
private final List<ErrorHandler<Throwable>> errorHandlers = new ArrayList<>();
|
|
||||||
private RunnableThrowingException<?> finallyHandler = () -> {
|
|
||||||
};
|
|
||||||
|
|
||||||
public AsyncTaskImpl(SupplierThrowingException<ResultType, ?> task) {
|
|
||||||
this.task = task;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AsyncTaskWithoutErrorHandler onSuccess(ConsumerThrowingException<ResultType, ?> handler) {
|
|
||||||
successHandler = handler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AsyncTaskWithoutErrorHandler onSuccess(RunnableThrowingException<?> handler) {
|
|
||||||
return onSuccess(result -> handler.run());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
@Override
|
|
||||||
public <ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, ConsumerThrowingException<ErrorType, ?> handler) {
|
|
||||||
errorHandlers.add((ErrorHandler) new ErrorHandler<>(type, handler));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, RunnableThrowingException<?> handler) {
|
|
||||||
return onError(type, error -> handler.run());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AsyncTask andFinally(RunnableThrowingException<?> handler) {
|
|
||||||
finallyHandler = handler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
errorHandlers.add(ErrorHandler.LOGGING_HANDLER);
|
|
||||||
executor.execute(() -> logExceptions(() -> {
|
|
||||||
try {
|
|
||||||
ResultType result = task.get();
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
try {
|
|
||||||
successHandler.accept(result);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
LOG.error("Uncaught exception", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Throwable e) {
|
|
||||||
ErrorHandler<Throwable> errorHandler = errorHandlerFor(e);
|
|
||||||
Platform.runLater(toRunnableLoggingException(() -> errorHandler.accept(e)));
|
|
||||||
} finally {
|
|
||||||
Platform.runLater(toRunnableLoggingException(finallyHandler));
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ErrorHandler<Throwable> errorHandlerFor(Throwable e) {
|
|
||||||
return errorHandlers.stream().filter(handler -> handler.handles(e)).findFirst().get();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Runnable toRunnableLoggingException(RunnableThrowingException<?> delegate) {
|
|
||||||
return () -> logExceptions(delegate);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void logExceptions(RunnableThrowingException<?> delegate) {
|
|
||||||
try {
|
|
||||||
delegate.run();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
LOG.error("Uncaught exception", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ErrorHandler<ErrorType> implements ConsumerThrowingException<ErrorType, Throwable> {
|
|
||||||
|
|
||||||
public static final ErrorHandler<Throwable> LOGGING_HANDLER = new ErrorHandler<Throwable>(Throwable.class, error -> {
|
|
||||||
LOG.error("Uncaught exception", error);
|
|
||||||
});
|
|
||||||
|
|
||||||
private final Class<ErrorType> type;
|
|
||||||
private final ConsumerThrowingException<ErrorType, ?> delegate;
|
|
||||||
|
|
||||||
public ErrorHandler(Class<ErrorType> type, ConsumerThrowingException<ErrorType, ?> delegate) {
|
|
||||||
this.type = type;
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean handles(Throwable error) {
|
|
||||||
return type.isInstance(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void accept(ErrorType error) throws Throwable {
|
|
||||||
delegate.accept(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface AsyncTaskWithoutSuccessHandler<ResultType> extends AsyncTaskWithoutErrorHandler {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param handler Tasks to be invoked on the JavaFX application thread.
|
|
||||||
* @return The async task
|
|
||||||
*/
|
|
||||||
AsyncTaskWithoutErrorHandler onSuccess(ConsumerThrowingException<ResultType, ?> handler);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param handler Tasks to be invoked on the JavaFX application thread.
|
|
||||||
* @return The async task
|
|
||||||
*/
|
|
||||||
AsyncTaskWithoutErrorHandler onSuccess(RunnableThrowingException<?> handler);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface AsyncTaskWithoutErrorHandler extends AsyncTaskWithoutFinallyHandler {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param type Exception type to catch
|
|
||||||
* @param handler Tasks to be invoked on the JavaFX application thread.
|
|
||||||
* @return The async task
|
|
||||||
*/
|
|
||||||
<ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, ConsumerThrowingException<ErrorType, ?> handler);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param type Exception type to catch
|
|
||||||
* @param handler Tasks to be invoked on the JavaFX application thread.
|
|
||||||
* @return The async task
|
|
||||||
*/
|
|
||||||
<ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, RunnableThrowingException<?> handler);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface AsyncTaskWithoutFinallyHandler extends AsyncTask {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param handler Tasks to be invoked on the JavaFX application thread.
|
|
||||||
* @return The async task
|
|
||||||
*/
|
|
||||||
AsyncTask andFinally(RunnableThrowingException<?> handler);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface AsyncTask extends Runnable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the async task.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
void run();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -38,6 +38,10 @@ public class DialogBuilderUtil {
|
|||||||
return buildDialog(title, header, content, Alert.AlertType.CONFIRMATION, defaultButton, ButtonType.YES, ButtonType.NO);
|
return buildDialog(title, header, content, Alert.AlertType.CONFIRMATION, defaultButton, ButtonType.YES, ButtonType.NO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Alert buildGracefulShutdownDialog(String title, String header, String content, ButtonType defaultButton, ButtonType... buttons) {
|
||||||
|
return buildDialog(title, header, content, Alert.AlertType.WARNING, defaultButton, buttons);
|
||||||
|
}
|
||||||
|
|
||||||
private static Alert buildDialog(String title, String header, String content, Alert.AlertType type, ButtonType defaultButton, ButtonType... buttons) {
|
private static Alert buildDialog(String title, String header, String content, Alert.AlertType type, ButtonType defaultButton, ButtonType... buttons) {
|
||||||
Text contentText = new Text(content);
|
Text contentText = new Text(content);
|
||||||
contentText.setWrappingWidth(360.0);
|
contentText.setWrappingWidth(360.0);
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.ui.util;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.reflect.InvocationHandler;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Proxy;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.SupplierThrowingException;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reflection-based wrapper for com.apple.eawt.Application.
|
|
||||||
*/
|
|
||||||
public class EawtApplicationWrapper {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(EawtApplicationWrapper.class);
|
|
||||||
|
|
||||||
private final Class<?> applicationClass;
|
|
||||||
private final Object application;
|
|
||||||
|
|
||||||
private EawtApplicationWrapper() throws ReflectiveOperationException {
|
|
||||||
this.applicationClass = Class.forName("com.apple.eawt.Application");
|
|
||||||
this.application = applicationClass.getMethod("getApplication").invoke(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A wrapper for com.apple.ewat.Application if the current OS is macOS and the class is available in this JVM.
|
|
||||||
*/
|
|
||||||
public static Optional<EawtApplicationWrapper> getApplication() {
|
|
||||||
if (!SystemUtils.IS_OS_MAC_OSX) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Optional.of(new EawtApplicationWrapper());
|
|
||||||
} catch (ReflectiveOperationException e) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setOpenFileHandler(InvocationHandler handler) throws ReflectiveOperationException {
|
|
||||||
Class<?> handlerClass = Class.forName("com.apple.eawt.OpenFilesHandler");
|
|
||||||
Method setter = applicationClass.getMethod("setOpenFileHandler", handlerClass);
|
|
||||||
Object proxy = Proxy.newProxyInstance(applicationClass.getClassLoader(), new Class<?>[] {handlerClass}, handler);
|
|
||||||
setter.invoke(application, proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOpenFileHandler(Consumer<List<File>> handler) {
|
|
||||||
try {
|
|
||||||
Class<?> openFilesEventClass = Class.forName("com.apple.eawt.AppEvent$OpenFilesEvent");
|
|
||||||
Method getFiles = openFilesEventClass.getMethod("getFiles");
|
|
||||||
setOpenFileHandler(methodSpecificInvocationHandler("openFiles", args -> {
|
|
||||||
Object openFilesEvent = args[0];
|
|
||||||
assert openFilesEventClass.isInstance(openFilesEvent);
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
List<File> files = (List<File>) uncheckedReflectiveOperation(() -> getFiles.invoke(openFilesEvent));
|
|
||||||
handler.accept(files);
|
|
||||||
return null;
|
|
||||||
}));
|
|
||||||
} catch (ReflectiveOperationException e) {
|
|
||||||
LOG.error("Exception setting openFileHandler.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setPreferencesHandler(InvocationHandler handler) throws ReflectiveOperationException {
|
|
||||||
Class<?> handlerClass = Class.forName("com.apple.eawt.PreferencesHandler");
|
|
||||||
Method setter = applicationClass.getMethod("setPreferencesHandler", handlerClass);
|
|
||||||
Object proxy = Proxy.newProxyInstance(applicationClass.getClassLoader(), new Class<?>[] {handlerClass}, handler);
|
|
||||||
setter.invoke(application, proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPreferencesHandler(Runnable handler) {
|
|
||||||
try {
|
|
||||||
setPreferencesHandler(methodSpecificInvocationHandler("handlePreferences", args -> {
|
|
||||||
handler.run();
|
|
||||||
return null;
|
|
||||||
}));
|
|
||||||
} catch (ReflectiveOperationException e) {
|
|
||||||
LOG.error("Exception setting preferencesHandler.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static InvocationHandler methodSpecificInvocationHandler(String methodName, Function<Object[], Object> handler) {
|
|
||||||
return new InvocationHandler() {
|
|
||||||
@Override
|
|
||||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
|
||||||
if (method.getName().equals(methodName)) {
|
|
||||||
return handler.apply(args);
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException("Unexpected invocation " + method.getName() + ", expected " + methodName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps {@link ReflectiveOperationException}s as {@link UncheckedReflectiveOperationException}.
|
|
||||||
*
|
|
||||||
* @param operation Invokation throwing an ReflectiveOperationException
|
|
||||||
* @return Result returned by <code>operation</code>
|
|
||||||
* @throws UncheckedReflectiveOperationException in case <code>operation</code> throws an ReflectiveOperationException.
|
|
||||||
*/
|
|
||||||
private static <T> T uncheckedReflectiveOperation(SupplierThrowingException<T, ReflectiveOperationException> operation) throws UncheckedReflectiveOperationException {
|
|
||||||
try {
|
|
||||||
return operation.get();
|
|
||||||
} catch (ReflectiveOperationException e) {
|
|
||||||
throw new UncheckedReflectiveOperationException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class UncheckedReflectiveOperationException extends RuntimeException {
|
|
||||||
public UncheckedReflectiveOperationException(ReflectiveOperationException cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -14,9 +14,9 @@ import java.util.List;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
import org.cryptomator.ui.l10n.Localization;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||||
|
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
@@ -28,6 +28,8 @@ import javafx.scene.paint.Color;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class PasswordStrengthUtil {
|
public class PasswordStrengthUtil {
|
||||||
|
|
||||||
|
private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length
|
||||||
|
|
||||||
private final Zxcvbn zxcvbn;
|
private final Zxcvbn zxcvbn;
|
||||||
private final List<String> sanitizedInputs;
|
private final List<String> sanitizedInputs;
|
||||||
private final Localization localization;
|
private final Localization localization;
|
||||||
@@ -41,10 +43,11 @@ public class PasswordStrengthUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int computeRate(String password) {
|
public int computeRate(String password) {
|
||||||
if (StringUtils.isEmpty(password)) {
|
if (Strings.isNullOrEmpty(password)) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return zxcvbn.measure(password, sanitizedInputs).getScore();
|
int numCharsToRate = Math.min(PW_TRUNC_LEN, password.length());
|
||||||
|
return zxcvbn.measure(password.substring(0, numCharsToRate), sanitizedInputs).getScore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
192
main/ui/src/main/java/org/cryptomator/ui/util/Tasks.java
Normal file
192
main/ui/src/main/java/org/cryptomator/ui/util/Tasks.java
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (c) 2018 Skymatic UG (haftungsbeschränkt).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the accompanying LICENSE file.
|
||||||
|
*******************************************************************************/
|
||||||
|
package org.cryptomator.ui.util;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.concurrent.ScheduledService;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class Tasks {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Tasks.class);
|
||||||
|
public static <T> TaskBuilder<T> create(Callable<T> callable) {
|
||||||
|
return new TaskBuilder<>(callable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TaskBuilder<Void> create(VoidCallable callable) {
|
||||||
|
return create(() -> {
|
||||||
|
callable.call();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TaskBuilder<T> {
|
||||||
|
|
||||||
|
private final Callable<T> callable;
|
||||||
|
private Consumer<T> successHandler = x -> {
|
||||||
|
};
|
||||||
|
private List<ErrorHandler<?>> errorHandlers = new ArrayList<>();
|
||||||
|
private Runnable finallyHandler = () -> {};
|
||||||
|
|
||||||
|
TaskBuilder(Callable<T> callable) {
|
||||||
|
this.callable = callable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskBuilder<T> onSuccess(Runnable successHandler) {
|
||||||
|
this.successHandler = x -> {
|
||||||
|
successHandler.run();
|
||||||
|
};
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskBuilder<T> onSuccess(Consumer<T> successHandler) {
|
||||||
|
this.successHandler = successHandler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <E extends Throwable> TaskBuilder<T> onError(Class<E> type, Consumer<E> exceptionHandler) {
|
||||||
|
errorHandlers.add(new ErrorHandler<>(type, exceptionHandler));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TaskBuilder<T> andFinally(Runnable finallyHandler) {
|
||||||
|
this.finallyHandler = finallyHandler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<T> build() {
|
||||||
|
return new TaskImpl<>(callable, successHandler, errorHandlers, finallyHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<T> runOnce(ExecutorService executorService) {
|
||||||
|
Task<T> task = build();
|
||||||
|
executorService.submit(task);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<T> scheduleOnce(ScheduledExecutorService executorService, long delay, TimeUnit unit) {
|
||||||
|
Task<T> task = build();
|
||||||
|
executorService.schedule(task, delay, unit);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScheduledService<T> schedulePeriodically(ExecutorService executorService, Duration initialDelay, Duration period) {
|
||||||
|
ScheduledService<T> service = new RestartingService<>(this::build);
|
||||||
|
service.setExecutor(executorService);
|
||||||
|
service.setDelay(initialDelay);
|
||||||
|
service.setPeriod(period);
|
||||||
|
Platform.runLater(service::start);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ErrorHandler<ErrorType extends Throwable> {
|
||||||
|
|
||||||
|
private final Class<ErrorType> type;
|
||||||
|
private final Consumer<ErrorType> errorHandler;
|
||||||
|
|
||||||
|
public ErrorHandler(Class<ErrorType> type, Consumer<ErrorType> errorHandler) {
|
||||||
|
this.type = type;
|
||||||
|
this.errorHandler = errorHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean handles(Throwable error) {
|
||||||
|
return type.isInstance(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(Throwable error) throws ClassCastException {
|
||||||
|
ErrorType typedError = type.cast(error);
|
||||||
|
errorHandler.accept(typedError);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter between java.util.function.* and javafx.concurrent.*
|
||||||
|
*/
|
||||||
|
private static class TaskImpl<T> extends Task<T> {
|
||||||
|
|
||||||
|
private final Callable<T> callable;
|
||||||
|
private final Consumer<T> successHandler;
|
||||||
|
private List<ErrorHandler<?>> errorHandlers;
|
||||||
|
private final Runnable finallyHandler;
|
||||||
|
|
||||||
|
TaskImpl(Callable<T> callable, Consumer<T> successHandler, List<ErrorHandler<?>> errorHandlers, Runnable finallyHandler) {
|
||||||
|
this.callable = callable;
|
||||||
|
this.successHandler = successHandler;
|
||||||
|
this.errorHandlers = errorHandlers;
|
||||||
|
this.finallyHandler = finallyHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected T call() throws Exception {
|
||||||
|
return callable.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void succeeded() {
|
||||||
|
try {
|
||||||
|
successHandler.accept(getValue());
|
||||||
|
} finally {
|
||||||
|
finallyHandler.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void failed() {
|
||||||
|
try {
|
||||||
|
Throwable exception = getException();
|
||||||
|
errorHandlers.stream().filter(handler -> handler.handles(exception)).findFirst().ifPresentOrElse(exceptionHandler -> {
|
||||||
|
exceptionHandler.handle(exception);
|
||||||
|
}, () -> {
|
||||||
|
LOG.error("Unhandled exception", exception);
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
finallyHandler.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service that keeps executing the next task long as tasks are succeeding.
|
||||||
|
*/
|
||||||
|
private static class RestartingService<T> extends ScheduledService<T> {
|
||||||
|
|
||||||
|
private final Supplier<Task<T>> taskFactory;
|
||||||
|
|
||||||
|
RestartingService(Supplier<Task<T>> taskFactory) {
|
||||||
|
this.taskFactory = taskFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<T> createTask() {
|
||||||
|
return taskFactory.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface VoidCallable {
|
||||||
|
|
||||||
|
void call() throws Exception;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -108,6 +108,13 @@
|
|||||||
-fx-text-fill: COLOR_TEXT_DISABLED;
|
-fx-text-fill: COLOR_TEXT_DISABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button:focused,
|
||||||
|
.toggle-button:focused {
|
||||||
|
-fx-border-color: black;
|
||||||
|
-fx-border-insets: 2px;
|
||||||
|
-fx-border-style: dotted inside;
|
||||||
|
}
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* *
|
* *
|
||||||
* Segmented Buttons *
|
* Segmented Buttons *
|
||||||
@@ -157,6 +164,10 @@
|
|||||||
-fx-padding: 1;
|
-fx-padding: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-view:focused {
|
||||||
|
-fx-border-style: dotted inside;
|
||||||
|
}
|
||||||
|
|
||||||
.list-cell {
|
.list-cell {
|
||||||
-fx-padding: 0.5em;
|
-fx-padding: 0.5em;
|
||||||
-fx-text-fill: COLOR_TEXT;
|
-fx-text-fill: COLOR_TEXT;
|
||||||
@@ -243,6 +254,12 @@
|
|||||||
-fx-background-color: COLOR_VGRAD_DARK;
|
-fx-background-color: COLOR_VGRAD_DARK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool-bar.list-related-toolbar .toggle-button:focused,
|
||||||
|
.tool-bar.list-related-toolbar .button:focused {
|
||||||
|
-fx-border-style: dotted inside;
|
||||||
|
-fx-border-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* *
|
* *
|
||||||
* PopupMenu *
|
* PopupMenu *
|
||||||
@@ -333,6 +350,10 @@
|
|||||||
.check-box:selected > .box > .mark {
|
.check-box:selected > .box > .mark {
|
||||||
-fx-background-color: COLOR_TEXT;
|
-fx-background-color: COLOR_TEXT;
|
||||||
}
|
}
|
||||||
|
/* focused */
|
||||||
|
.check-box:focused {
|
||||||
|
-fx-border-style: dotted inside;
|
||||||
|
}
|
||||||
/* disabled: */
|
/* disabled: */
|
||||||
.check-box:disabled > .box {
|
.check-box:disabled > .box {
|
||||||
-fx-background-color: COLOR_BORDER, COLOR_BACKGROUND;
|
-fx-background-color: COLOR_BORDER, COLOR_BACKGROUND;
|
||||||
@@ -355,6 +376,10 @@
|
|||||||
-fx-text-fill: COLOR_TEXT;
|
-fx-text-fill: COLOR_TEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.choice-box:focused {
|
||||||
|
-fx-border-style: dotted inside;
|
||||||
|
}
|
||||||
|
|
||||||
.choice-box > .open-button > .arrow {
|
.choice-box > .open-button > .arrow {
|
||||||
-fx-background-color: transparent, COLOR_TEXT;
|
-fx-background-color: transparent, COLOR_TEXT;
|
||||||
-fx-background-insets: 0 0 -1 0, 0;
|
-fx-background-insets: 0 0 -1 0, 0;
|
||||||
|
|||||||
@@ -434,6 +434,52 @@
|
|||||||
-fx-background-color: COLOR_TEXT_DISABLED;
|
-fx-background-color: COLOR_TEXT_DISABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* *
|
||||||
|
* ChoiceBox *
|
||||||
|
* *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
.choice-box {
|
||||||
|
-fx-background-color: COLOR_HGRAD_BTN_BORDER, #FFFFFF;
|
||||||
|
-fx-background-insets: 0, 1;
|
||||||
|
-fx-background-radius: 4;
|
||||||
|
-fx-padding: 0 0 0 0.8em;
|
||||||
|
-fx-text-fill: COLOR_TEXT;
|
||||||
|
-fx-alignment: CENTER;
|
||||||
|
-fx-effect: dropshadow(one-pass-box, #DCDCDC, 0.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-box > .open-button {
|
||||||
|
-fx-background-insets: 0, 1 1 1 0;
|
||||||
|
-fx-background-radius: 0 4 4 0;
|
||||||
|
-fx-padding: 4 5 4 4;
|
||||||
|
}
|
||||||
|
.root.active-window .choice-box > .open-button {
|
||||||
|
-fx-background-color: COLOR_HGRAD_BTN_DEF_BORDER, COLOR_HGRAD_BTN_DEF_BACKGROUND;
|
||||||
|
}
|
||||||
|
.root.inactive-window .choice-box > .open-button {
|
||||||
|
-fx-background-color: COLOR_HGRAD_BTN_BORDER, #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-box > .open-button > .arrow {
|
||||||
|
-fx-shape: "M 0 5 L 4 0 L 8 5 L 6 5 L 4 2 L 2 5 Z M 0 8 L 4 13 L 8 8 L 6 8 L 4 11 L 2 8 Z";
|
||||||
|
-fx-scale-shape: true;
|
||||||
|
-fx-min-width: 9px;
|
||||||
|
-fx-min-height: 13px;
|
||||||
|
}
|
||||||
|
.root.active-window .choice-box > .open-button > .arrow {
|
||||||
|
-fx-background-color: #FFFFFF;
|
||||||
|
}
|
||||||
|
.root.inactive-window .choice-box > .open-button > .arrow {
|
||||||
|
-fx-background-color: #444444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-box .context-menu {
|
||||||
|
-fx-background-color: COLOR_BORDER, #FFF;
|
||||||
|
-fx-background-insets: 0, 1;
|
||||||
|
}
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* *
|
* *
|
||||||
* ProgressIndicator *
|
* ProgressIndicator *
|
||||||
|
|||||||
@@ -157,36 +157,48 @@
|
|||||||
|
|
||||||
.list-view {
|
.list-view {
|
||||||
-fx-background-color: COLOR_BORDER, #FFF;
|
-fx-background-color: COLOR_BORDER, #FFF;
|
||||||
|
-fx-border-style: dotted inside;
|
||||||
|
-fx-border-color: transparent;
|
||||||
-fx-background-insets: 0, 1;
|
-fx-background-insets: 0, 1;
|
||||||
-fx-padding: 1;
|
-fx-padding: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-view:focused {
|
||||||
|
-fx-border-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
.list-view > .virtual-flow > .scroll-bar:vertical{
|
.list-view > .virtual-flow > .scroll-bar:vertical{
|
||||||
-fx-background-insets: 0, 0 0 0 1;
|
-fx-background-insets: 0, 0 0 0 1;
|
||||||
-fx-padding: -1 -1 -1 0;
|
-fx-padding: -1 -1 -1 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-view > .virtual-flow > .scroll-bar:horizontal{
|
.list-view > .virtual-flow > .scroll-bar:horizontal{
|
||||||
-fx-background-insets: 0, 1 0 0 0;
|
-fx-background-insets: 0, 1 0 0 0;
|
||||||
-fx-padding: 0 -1 -1 -1;
|
-fx-padding: 0 -1 -1 -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-view > .virtual-flow > .corner {
|
.list-view > .virtual-flow > .corner {
|
||||||
-fx-background-color: COLOR_BORDER, COLOR_CONTROL_BASE;
|
-fx-background-color: COLOR_BORDER, COLOR_CONTROL_BASE;
|
||||||
-fx-background-insets: 0, 1 0 0 1;
|
-fx-background-insets: 0, 1 0 0 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-cell {
|
.list-cell {
|
||||||
-fx-background-color: #FFF;
|
-fx-background-color: #FFF;
|
||||||
-fx-padding: 0.6em;
|
-fx-padding: 0.6em;
|
||||||
-fx-text-fill: COLOR_TEXT;
|
-fx-text-fill: COLOR_TEXT;
|
||||||
-fx-opacity: 1;
|
-fx-opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-view:focused > .virtual-flow > .clipped-container > .sheet > .list-cell:focused {
|
.list-view:focused > .virtual-flow > .clipped-container > .sheet > .list-cell:focused {
|
||||||
-fx-background-color: COLOR_BORDER_FOCUS, #FFF;
|
-fx-background-color: COLOR_BORDER_FOCUS, #FFF;
|
||||||
-fx-background-insets: 0, 1;
|
-fx-background-insets: 0, 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected {
|
.list-view > .virtual-flow > .clipped-container > .sheet > .list-cell:filled:selected {
|
||||||
-fx-background-color: #DEDEDE, #F7F7F7;
|
-fx-background-color: #DEDEDE, #F7F7F7;
|
||||||
-fx-background-insets: 0, 1;
|
-fx-background-insets: 0, 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-cell:filled:hover {
|
.list-cell:filled:hover {
|
||||||
-fx-background-color: #F7F7F7;
|
-fx-background-color: #F7F7F7;
|
||||||
}
|
}
|
||||||
@@ -364,6 +376,8 @@
|
|||||||
.check-box {
|
.check-box {
|
||||||
-fx-label-padding: 0 0 0 6px;
|
-fx-label-padding: 0 0 0 6px;
|
||||||
-fx-text-fill: COLOR_TEXT;
|
-fx-text-fill: COLOR_TEXT;
|
||||||
|
-fx-border-style: dotted inside;
|
||||||
|
-fx-border-color: transparent;
|
||||||
}
|
}
|
||||||
.check-box > .box {
|
.check-box > .box {
|
||||||
-fx-padding: 1px;
|
-fx-padding: 1px;
|
||||||
@@ -380,6 +394,10 @@
|
|||||||
.check-box:armed > .box {
|
.check-box:armed > .box {
|
||||||
-fx-border-color: COLOR_BORDER_FOCUS;
|
-fx-border-color: COLOR_BORDER_FOCUS;
|
||||||
}
|
}
|
||||||
|
/* focused */
|
||||||
|
.check-box:focused {
|
||||||
|
-fx-border-color: black;
|
||||||
|
}
|
||||||
/* selected: */
|
/* selected: */
|
||||||
.check-box:selected > .box > .mark {
|
.check-box:selected > .box > .mark {
|
||||||
-fx-background-color: black;
|
-fx-background-color: black;
|
||||||
@@ -406,6 +424,10 @@
|
|||||||
-fx-text-fill: COLOR_TEXT;
|
-fx-text-fill: COLOR_TEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.choice-box:focused {
|
||||||
|
-fx-border-style: dotted inside;
|
||||||
|
}
|
||||||
|
|
||||||
.choice-box > .open-button > .arrow {
|
.choice-box > .open-button > .arrow {
|
||||||
-fx-background-color: transparent, COLOR_TEXT;
|
-fx-background-color: transparent, COLOR_TEXT;
|
||||||
-fx-background-insets: 0 0 -1 0, 0;
|
-fx-background-insets: 0 0 -1 0, 0;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
|
|
||||||
<VBox prefWidth="200.0" cacheShape="true" cache="true">
|
<VBox prefWidth="200.0" cacheShape="true" cache="true">
|
||||||
<StackPane VBox.vgrow="ALWAYS" cacheShape="true" cache="true">
|
<StackPane VBox.vgrow="ALWAYS" cacheShape="true" cache="true">
|
||||||
<ListView fx:id="vaultList" focusTraversable="false" cacheShape="true" cache="true"/>
|
<ListView fx:id="vaultList" focusTraversable="true" cacheShape="true" cache="true"/>
|
||||||
<AnchorPane fx:id="emptyListInstructions" cacheShape="true" cache="true">
|
<AnchorPane fx:id="emptyListInstructions" cacheShape="true" cache="true">
|
||||||
<HBox AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.bottomAnchor="100.0" alignment="CENTER" cacheShape="true" cache="true">
|
<HBox AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.bottomAnchor="100.0" alignment="CENTER" cacheShape="true" cache="true">
|
||||||
<Label textAlignment="CENTER" text="%main.emptyListInstructions" wrapText="true" cacheShape="true" cache="true"/>
|
<Label textAlignment="CENTER" text="%main.emptyListInstructions" wrapText="true" cacheShape="true" cache="true"/>
|
||||||
@@ -63,11 +63,11 @@
|
|||||||
</StackPane>
|
</StackPane>
|
||||||
<ToolBar VBox.vgrow="NEVER" styleClass="list-related-toolbar" cacheShape="true" cache="true">
|
<ToolBar VBox.vgrow="NEVER" styleClass="list-related-toolbar" cacheShape="true" cache="true">
|
||||||
<items>
|
<items>
|
||||||
<ToggleButton text="" styleClass="ionicons" fx:id="addVaultButton" onAction="#didClickAddVault" focusTraversable="false" cacheShape="true" cache="true"/>
|
<ToggleButton text="" styleClass="ionicons" fx:id="addVaultButton" onAction="#didClickAddVault" focusTraversable="true" cacheShape="true" cache="true"/>
|
||||||
<Button text="" styleClass="ionicons" fx:id="removeVaultButton" onAction="#didClickRemoveSelectedEntry" focusTraversable="false" cacheShape="true" cache="true"/>
|
<Button text="" styleClass="ionicons" fx:id="removeVaultButton" onAction="#didClickRemoveSelectedEntry" focusTraversable="true" cacheShape="true" cache="true"/>
|
||||||
<Pane HBox.hgrow="ALWAYS" styleClass="spacer" />
|
<Pane HBox.hgrow="ALWAYS" styleClass="spacer" />
|
||||||
<!-- future use: -->
|
<!-- future use: -->
|
||||||
<ToggleButton text="" styleClass="ionicons" fx:id="settingsButton" onAction="#didClickShowSettings" focusTraversable="false" cacheShape="true" cache="true"/>
|
<ToggleButton text="" styleClass="ionicons" fx:id="settingsButton" onAction="#didClickShowSettings" focusTraversable="true" cacheShape="true" cache="true"/>
|
||||||
</items>
|
</items>
|
||||||
</ToolBar>
|
</ToolBar>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
Contributors:
|
Contributors:
|
||||||
Sebastian Stenzel - initial API and implementation
|
Sebastian Stenzel - initial API and implementation
|
||||||
-->
|
-->
|
||||||
<?import java.net.URL?>
|
|
||||||
<?import javafx.scene.layout.GridPane?>
|
<?import javafx.scene.layout.GridPane?>
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.layout.ColumnConstraints?>
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
@@ -27,30 +26,34 @@
|
|||||||
</padding>
|
</padding>
|
||||||
|
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints percentWidth="38.2"/>
|
<ColumnConstraints percentWidth="45.00"/>
|
||||||
<ColumnConstraints percentWidth="61.8"/>
|
<ColumnConstraints percentWidth="55.00"/>
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
|
|
||||||
<children>
|
<children>
|
||||||
<!-- Row 0 -->
|
<!-- Row 0 -->
|
||||||
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%settings.checkForUpdates.label" cacheShape="true" cache="true" />
|
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%settings.checkForUpdates.label" cacheShape="true" cache="true" />
|
||||||
<CheckBox GridPane.rowIndex="0" GridPane.columnIndex="1" fx:id="checkForUpdatesCheckbox" cacheShape="true" cache="true" />
|
<CheckBox GridPane.rowIndex="0" GridPane.columnIndex="1" fx:id="checkForUpdatesCheckbox" cacheShape="true" cache="true" />
|
||||||
|
|
||||||
<!-- Row 1 -->
|
<!-- Row 1 -->
|
||||||
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%settings.port.label" cacheShape="true" cache="true" />
|
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%settings.debugMode.label" cacheShape="true" cache="true" />
|
||||||
<HBox GridPane.rowIndex="1" GridPane.columnIndex="1" spacing="6.0">
|
<CheckBox GridPane.rowIndex="1" GridPane.columnIndex="1" fx:id="debugModeCheckbox" cacheShape="true" cache="true" />
|
||||||
<TextField fx:id="portField" cacheShape="true" cache="true" promptText="%settings.port.prompt" />
|
|
||||||
<Button text="%settings.port.apply" fx:id="changePortButton" onAction="#changePort"/>
|
|
||||||
</HBox>
|
|
||||||
|
|
||||||
<!-- Row 2 -->
|
<!-- Row 2 -->
|
||||||
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" fx:id="prefGvfsSchemeLabel" text="%settings.prefGvfsScheme.label" cacheShape="true" cache="true" />
|
<Label fx:id="volumeLabel" GridPane.rowIndex="2" GridPane.columnIndex="0" text="%settings.volume.label" cacheShape="true" cache="true" />
|
||||||
<ChoiceBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="prefGvfsScheme" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
<ChoiceBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="volume" cacheShape="true" cache="true" />
|
||||||
|
|
||||||
<!-- Row 3 -->
|
<!-- Row 3 Alt 1-->
|
||||||
<Label GridPane.rowIndex="3" GridPane.columnIndex="0" text="%settings.debugMode.label" cacheShape="true" cache="true" />
|
<Label fx:id="portFieldLabel" GridPane.rowIndex="3" GridPane.columnIndex="0" text="%settings.webdav.port.label" cacheShape="true" cache="true" />
|
||||||
<CheckBox GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="debugModeCheckbox" cacheShape="true" cache="true" />
|
<HBox GridPane.rowIndex="3" GridPane.columnIndex="1" spacing="6.0">
|
||||||
|
<TextField fx:id="portField" cacheShape="true" cache="true" promptText="%settings.webdav.port.prompt" />
|
||||||
|
<Button text="%settings.webdav.port.apply" fx:id="changePortButton" onAction="#changePort"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<!-- Row 4 -->
|
||||||
|
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" fx:id="prefGvfsSchemeLabel" text="%settings.webdav.prefGvfsScheme.label" cacheShape="true" cache="true" />
|
||||||
|
<ChoiceBox GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="prefGvfsScheme" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||||
|
|
||||||
</children>
|
</children>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
<Label VBox.vgrow="NEVER" text="%settings.requiresRestartLabel" alignment="CENTER" cacheShape="true" cache="true" />
|
<Label VBox.vgrow="NEVER" text="%settings.requiresRestartLabel" alignment="CENTER" cacheShape="true" cache="true" />
|
||||||
|
|||||||
@@ -7,29 +7,25 @@
|
|||||||
Contributors:
|
Contributors:
|
||||||
Sebastian Stenzel - initial API and implementation
|
Sebastian Stenzel - initial API and implementation
|
||||||
-->
|
-->
|
||||||
<?import java.net.URL?>
|
|
||||||
<?import java.lang.String?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.CheckBox?>
|
||||||
|
<?import javafx.scene.control.ChoiceBox?>
|
||||||
|
<?import javafx.scene.control.Hyperlink?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.ProgressIndicator?>
|
<?import javafx.scene.control.ProgressIndicator?>
|
||||||
<?import javafx.scene.control.CheckBox?>
|
|
||||||
<?import javafx.scene.control.Button?>
|
|
||||||
<?import javafx.scene.layout.GridPane?>
|
|
||||||
<?import javafx.geometry.Insets?>
|
|
||||||
<?import javafx.scene.layout.ColumnConstraints?>
|
|
||||||
<?import javafx.scene.control.TextField?>
|
|
||||||
<?import javafx.scene.text.TextFlow?>
|
|
||||||
<?import javafx.scene.control.Hyperlink?>
|
|
||||||
<?import javafx.scene.text.Text?>
|
|
||||||
<?import javafx.scene.layout.HBox?>
|
|
||||||
<?import javafx.scene.control.Separator?>
|
<?import javafx.scene.control.Separator?>
|
||||||
<?import javafx.scene.control.ChoiceBox?>
|
<?import javafx.scene.control.TextField?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import javafx.scene.text.Text?>
|
||||||
|
<?import javafx.scene.text.TextFlow?>
|
||||||
|
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||||
<GridPane fx:controller="org.cryptomator.ui.controllers.UnlockController" fx:id="root" vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
<GridPane fx:controller="org.cryptomator.ui.controllers.UnlockController" fx:id="root" vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets top="24.0" right="12.0" bottom="24.0" left="12.0" />
|
<Insets top="24.0" right="12.0" bottom="24.0" left="12.0" />
|
||||||
</padding>
|
</padding>
|
||||||
|
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints percentWidth="38.2"/>
|
<ColumnConstraints percentWidth="38.2"/>
|
||||||
<ColumnConstraints percentWidth="61.8"/>
|
<ColumnConstraints percentWidth="61.8"/>
|
||||||
@@ -45,18 +41,21 @@
|
|||||||
<Button fx:id="advancedOptionsButton" text="%unlock.button.advancedOptions.show" prefWidth="150.0" onAction="#didClickAdvancedOptionsButton" cacheShape="true" cache="true" />
|
<Button fx:id="advancedOptionsButton" text="%unlock.button.advancedOptions.show" prefWidth="150.0" onAction="#didClickAdvancedOptionsButton" cacheShape="true" cache="true" />
|
||||||
<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" prefWidth="150.0" onAction="#didClickUnlockButton" disable="true" cacheShape="true" cache="true" />
|
<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" prefWidth="150.0" onAction="#didClickUnlockButton" disable="true" cacheShape="true" cache="true" />
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
|
<!-- Row 3 -->
|
||||||
|
<Label fx:id="successMessage" cacheShape="true" cache="true" visible="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2"/>
|
||||||
|
|
||||||
<!-- Row 3 -->
|
<!-- Row 3 -->
|
||||||
<GridPane fx:id="advancedOptions" vgap="12.0" hgap="12.0" prefWidth="400.0" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" visible="false" cacheShape="true" cache="true">
|
<GridPane fx:id="advancedOptions" vgap="12.0" hgap="12.0" prefWidth="400.0" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" visible="false" cacheShape="true" cache="true">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets top="24.0" />
|
<Insets top="24.0" />
|
||||||
</padding>
|
</padding>
|
||||||
|
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints percentWidth="38.2"/>
|
<ColumnConstraints percentWidth="38.2"/>
|
||||||
<ColumnConstraints percentWidth="61.8"/>
|
<ColumnConstraints percentWidth="61.8"/>
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
|
|
||||||
<!-- Row 3.0 -->
|
<!-- Row 3.0 -->
|
||||||
<Separator GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true"/>
|
<Separator GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true"/>
|
||||||
<HBox alignment="CENTER" prefWidth="400.0" GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
|
<HBox alignment="CENTER" prefWidth="400.0" GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
|
||||||
@@ -66,42 +65,54 @@
|
|||||||
</padding>
|
</padding>
|
||||||
</Label>
|
</Label>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<!-- Row 3.1 -->
|
<!-- Row 3.1 -->
|
||||||
<CheckBox GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="savePassword" text="%unlock.label.savePassword" onAction="#didClickSavePasswordCheckbox" cacheShape="true" cache="true" />
|
<CheckBox GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="savePassword" text="%unlock.label.savePassword" onAction="#didClickSavePasswordCheckbox" cacheShape="true" cache="true" />
|
||||||
|
|
||||||
<!-- Row 3.2 -->
|
<!-- Row 3.2 -->
|
||||||
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="unlockAfterStartup" text="%unlock.label.unlockAfterStartup" cacheShape="true" cache="true" />
|
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="unlockAfterStartup" text="%unlock.label.unlockAfterStartup" cacheShape="true" cache="true" />
|
||||||
|
|
||||||
<!-- Row 3.3 -->
|
<!-- Row 3.3 -->
|
||||||
<CheckBox GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="mountAfterUnlock" text="%unlock.label.mountAfterUnlock" cacheShape="true" cache="true" />
|
<Label GridPane.rowIndex="3" GridPane.columnIndex="0" text="%unlock.label.mountName" cacheShape="true" cache="true" />
|
||||||
|
<TextField GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="mountName" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||||
|
|
||||||
<!-- Row 3.4 -->
|
<!-- Row 3.4 -->
|
||||||
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" text="%unlock.label.mountName" cacheShape="true" cache="true" />
|
<CheckBox GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="revealAfterMount" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
|
||||||
<TextField GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="mountName" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
|
||||||
|
|
||||||
<!-- Row 3.5 -->
|
<!-- Row 3.5 -->
|
||||||
<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="revealAfterMount" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
|
<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="useReadOnlyMode" text="%unlock.label.useReadOnlyMode" cacheShape="true" cache="true" />
|
||||||
|
|
||||||
<!-- Row 3.6 -->
|
<!-- Row 3.6 -->
|
||||||
<Label GridPane.rowIndex="6" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
|
<CheckBox GridPane.rowIndex="6" GridPane.columnIndex="0" GridPane.columnSpan="2" fx:id="useCustomMountPoint" text="%unlock.label.useOwnMountPath" cacheShape="true" cache="true" />
|
||||||
<ChoiceBox GridPane.rowIndex="6" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
|
||||||
|
<!-- Row 3.7 Alt1 -->
|
||||||
|
<Label GridPane.rowIndex="7" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
|
||||||
|
<ChoiceBox GridPane.rowIndex="7" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
|
||||||
|
|
||||||
|
<!-- Row 3.7 Alt2 -->
|
||||||
|
<HBox fx:id="customMountPoint" GridPane.rowIndex="7" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="6" alignment="BASELINE_LEFT" cacheShape="true" cache="true">
|
||||||
|
<padding>
|
||||||
|
<Insets left="20.0" />
|
||||||
|
</padding>
|
||||||
|
<Label HBox.hgrow="ALWAYS" fx:id="customMountPointLabel" textOverrun="LEADING_ELLIPSIS" cacheShape="true" cache="true" />
|
||||||
|
<Button HBox.hgrow="NEVER" minWidth="-Infinity" text="" styleClass="ionicons" onAction="#didClickChooseCustomMountPoint" focusTraversable="true" cacheShape="true" cache="true" />
|
||||||
|
</HBox>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
|
|
||||||
<!-- Row 4 -->
|
<!-- Row 4 -->
|
||||||
<TextFlow GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
|
<TextFlow GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets top="24.0"/>
|
<Insets top="24.0"/>
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
<children>
|
<children>
|
||||||
<Text fx:id="messageText" cache="true" />
|
|
||||||
<Hyperlink fx:id="downloadsPageLink" text="%unlock.label.downloadsPageLink" visible="false" onAction="#didClickDownloadsLink" cacheShape="true" cache="true" />
|
<Hyperlink fx:id="downloadsPageLink" text="%unlock.label.downloadsPageLink" visible="false" onAction="#didClickDownloadsLink" cacheShape="true" cache="true" />
|
||||||
</children>
|
</children>
|
||||||
</TextFlow>
|
</TextFlow>
|
||||||
|
|
||||||
<!-- Row 5-->
|
<!-- Row 5 -->
|
||||||
<ProgressIndicator progress="-1" fx:id="progressIndicator" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="CENTER" cacheShape="true" cache="true" cacheHint="SPEED" />
|
<VBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="12.0" alignment="CENTER" cacheShape="true" cache="true">
|
||||||
|
<ProgressIndicator progress="-1" fx:id="progressIndicator" cacheShape="true" cache="true" cacheHint="SPEED" />
|
||||||
|
<Text fx:id="messageText" cache="true" />
|
||||||
|
</VBox>
|
||||||
</children>
|
</children>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,18 +28,9 @@
|
|||||||
<fx:define>
|
<fx:define>
|
||||||
<ContextMenu fx:id="moreOptionsMenu">
|
<ContextMenu fx:id="moreOptionsMenu">
|
||||||
<items>
|
<items>
|
||||||
<MenuItem fx:id="mountVaultMenuItem" text="%unlocked.moreOptions.mount" onAction="#didClickMountVault">
|
|
||||||
<graphic><Label text="" styleClass="ionicons"/></graphic>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem fx:id="unmountVaultMenuItem" text="%unlocked.moreOptions.unmount" onAction="#didClickUnmountVault">
|
|
||||||
<graphic><Label text="" styleClass="ionicons"/></graphic>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem fx:id="revealVaultMenuItem" text="%unlocked.moreOptions.reveal" onAction="#didClickRevealVault">
|
<MenuItem fx:id="revealVaultMenuItem" text="%unlocked.moreOptions.reveal" onAction="#didClickRevealVault">
|
||||||
<graphic><Label text="" styleClass="ionicons"/></graphic>
|
<graphic><Label text="" styleClass="ionicons"/></graphic>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem text="%unlocked.moreOptions.copyUrl" onAction="#didClickCopyUrl">
|
|
||||||
<graphic><Label text="" styleClass="ionicons"/></graphic>
|
|
||||||
</MenuItem>
|
|
||||||
</items>
|
</items>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</fx:define>
|
</fx:define>
|
||||||
@@ -54,8 +45,8 @@
|
|||||||
<Label fx:id="messageLabel" cacheShape="true" cache="true" />
|
<Label fx:id="messageLabel" cacheShape="true" cache="true" />
|
||||||
</Pane>
|
</Pane>
|
||||||
<HBox styleClass="segmented-button-bar" HBox.hgrow="NEVER" alignment="CENTER_RIGHT" cacheShape="true" cache="true">
|
<HBox styleClass="segmented-button-bar" HBox.hgrow="NEVER" alignment="CENTER_RIGHT" cacheShape="true" cache="true">
|
||||||
<Button text="%unlocked.button.lock" styleClass="first" onAction="#didClickLockVault" focusTraversable="false" cacheShape="true" cache="true"/>
|
<Button text="%unlocked.button.lock" styleClass="first" onAction="#didClickLockVault" focusTraversable="true" cacheShape="true" cache="true"/>
|
||||||
<ToggleButton text="" styleClass="last,ionicons" focusTraversable="false" fx:id="moreOptionsButton" onAction="#didClickMoreOptions" />
|
<ToggleButton text="" styleClass="last,ionicons" focusTraversable="true" fx:id="moreOptionsButton" onAction="#didClickMoreOptions" />
|
||||||
</HBox>
|
</HBox>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
|
|||||||
@@ -7,31 +7,28 @@
|
|||||||
Contributors:
|
Contributors:
|
||||||
Sebastian Stenzel - initial API and implementation
|
Sebastian Stenzel - initial API and implementation
|
||||||
-->
|
-->
|
||||||
<?import java.net.URL?>
|
|
||||||
<?import javafx.scene.image.ImageView?>
|
<?import javafx.scene.image.ImageView?>
|
||||||
<?import javafx.scene.image.Image?>
|
<?import javafx.scene.image.Image?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.Hyperlink?>
|
<?import javafx.scene.control.Hyperlink?>
|
||||||
<?import javafx.scene.control.CheckBox?>
|
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.control.ProgressIndicator?>
|
<?import javafx.scene.control.ProgressIndicator?>
|
||||||
<?import javafx.scene.control.Button?>
|
|
||||||
|
|
||||||
<VBox fx:controller="org.cryptomator.ui.controllers.WelcomeController" fx:id="root" prefWidth="400.0" prefHeight="400.0" spacing="24.0" alignment="CENTER" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
<VBox fx:controller="org.cryptomator.ui.controllers.WelcomeController" fx:id="root" prefWidth="400.0" prefHeight="400.0" spacing="24.0" alignment="CENTER" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||||
|
|
||||||
<VBox fx:id="checkForUpdatesContainer" spacing="6.0" alignment="CENTER" cacheShape="true" cache="true" prefHeight="64.0">
|
<VBox fx:id="checkForUpdatesContainer" spacing="6.0" alignment="CENTER" cacheShape="true" cache="true" prefHeight="64.0">
|
||||||
<HBox alignment="CENTER" spacing="5.0" cacheShape="true" cache="true">
|
<HBox alignment="CENTER" spacing="5.0" cacheShape="true" cache="true">
|
||||||
<Label fx:id="checkForUpdatesStatus" cacheShape="true" cache="true" />
|
<Label fx:id="checkForUpdatesStatus" cacheShape="true" cache="true" />
|
||||||
<ProgressIndicator fx:id="checkForUpdatesIndicator" progress="-1" prefWidth="15.0" prefHeight="15.0" cacheShape="true" cache="true" cacheHint="SPEED" />
|
<ProgressIndicator fx:id="checkForUpdatesIndicator" progress="-1" prefWidth="15.0" prefHeight="15.0" visible="false" cacheShape="true" cache="true" cacheHint="SPEED" />
|
||||||
</HBox>
|
</HBox>
|
||||||
<Hyperlink wrapText="true" textAlignment="CENTER" fx:id="updateLink" onAction="#didClickUpdateLink" cacheShape="true" cache="true" disable="true" />
|
<Hyperlink wrapText="true" textAlignment="CENTER" fx:id="updateLink" onAction="#didClickUpdateLink" cacheShape="true" cache="true" disable="true" />
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
||||||
<ImageView fitHeight="200.0" preserveRatio="true" smooth="true" cache="true" style="-fx-background-color: green;">
|
<ImageView fitHeight="200.0" preserveRatio="true" smooth="true" cache="true" style="-fx-background-color: green;">
|
||||||
<Image url="/bot_welcome.png"/>
|
<Image url="/bot_welcome.png"/>
|
||||||
</ImageView>
|
</ImageView>
|
||||||
|
|
||||||
<VBox prefHeight="64.0"/>
|
<VBox prefHeight="64.0"/>
|
||||||
|
|
||||||
</VBox>
|
</VBox>
|
||||||
@@ -45,7 +45,6 @@ changePassword.errorMessage.decryptionFailed = فشل فك التشفير
|
|||||||
# unlocked.fxml
|
# unlocked.fxml
|
||||||
unlocked.button.lock = اغلاق المحفظة
|
unlocked.button.lock = اغلاق المحفظة
|
||||||
unlocked.moreOptions.reveal = Reveal Drive
|
unlocked.moreOptions.reveal = Reveal Drive
|
||||||
unlocked.moreOptions.copyUrl = نسخ رابط ويب داف ( WebDAV )
|
|
||||||
unlocked.label.revealFailed = Command failed
|
unlocked.label.revealFailed = Command failed
|
||||||
unlocked.label.unmountFailed = Ejecting drive failed
|
unlocked.label.unmountFailed = Ejecting drive failed
|
||||||
unlocked.label.statsEncrypted = مشفر
|
unlocked.label.statsEncrypted = مشفر
|
||||||
@@ -54,8 +53,6 @@ unlocked.ioGraph.yAxis.label = Throughput (MiB/s)
|
|||||||
# settings.fxml
|
# settings.fxml
|
||||||
settings.version.label = الاصدار %s
|
settings.version.label = الاصدار %s
|
||||||
settings.checkForUpdates.label = افحص التحديثات
|
settings.checkForUpdates.label = افحص التحديثات
|
||||||
settings.port.label = منفذ ويب داف ( WebDAV )
|
|
||||||
settings.port.prompt = 0 \= Choose automatically
|
|
||||||
settings.requiresRestartLabel = * Cryptomator needs to restart
|
settings.requiresRestartLabel = * Cryptomator needs to restart
|
||||||
# tray icon
|
# tray icon
|
||||||
tray.menu.open = فتح
|
tray.menu.open = فتح
|
||||||
@@ -74,10 +71,8 @@ main.directoryList.remove.confirmation.header = هل تريد فعلا ازال
|
|||||||
main.directoryList.remove.confirmation.content = The vault will only be removed from the list. To permanently delete it, please delete the vault from your filesystem.
|
main.directoryList.remove.confirmation.content = The vault will only be removed from the list. To permanently delete it, please delete the vault from your filesystem.
|
||||||
upgrade.version3to4.msg = This vault needs to be migrated to a newer format.\nEncrypted folder names will be updated.\nPlease make sure synchronization has finished before proceeding.
|
upgrade.version3to4.msg = This vault needs to be migrated to a newer format.\nEncrypted folder names will be updated.\nPlease make sure synchronization has finished before proceeding.
|
||||||
upgrade.version3to4.err.io = Migration failed due to an I/O Exception. See log file for details.
|
upgrade.version3to4.err.io = Migration failed due to an I/O Exception. See log file for details.
|
||||||
settings.prefGvfsScheme.label = WebDAV Scheme
|
|
||||||
# upgrade.fxml
|
# upgrade.fxml
|
||||||
upgrade.confirmation.label = Yes, I've made sure that synchronization has finished
|
upgrade.confirmation.label = Yes, I've made sure that synchronization has finished
|
||||||
initialize.messageLabel.notEmpty = المحفظة غير فارغة
|
|
||||||
unlock.label.savePassword = حفظ كلمة المرور
|
unlock.label.savePassword = حفظ كلمة المرور
|
||||||
unlock.errorMessage.unauthenticVersionMac = Could not authenticate version MAC.
|
unlock.errorMessage.unauthenticVersionMac = Could not authenticate version MAC.
|
||||||
unlocked.label.mountFailed = Connecting drive failed
|
unlocked.label.mountFailed = Connecting drive failed
|
||||||
@@ -90,15 +85,41 @@ upgrade.version3to4.title = Vault Version 3 to 4 Upgrade
|
|||||||
upgrade.version4to5.title = Vault Version 4 to 5 Upgrade
|
upgrade.version4to5.title = Vault Version 4 to 5 Upgrade
|
||||||
upgrade.version4to5.msg = This vault needs to be migrated to a newer format.\nEncrypted files will be updated.\nPlease make sure synchronization has finished before proceeding.\n\nNote\: Modification date of all files will be changed to the current date/time in the process.
|
upgrade.version4to5.msg = This vault needs to be migrated to a newer format.\nEncrypted files will be updated.\nPlease make sure synchronization has finished before proceeding.\n\nNote\: Modification date of all files will be changed to the current date/time in the process.
|
||||||
upgrade.version4to5.err.io = Migration failed due to an I/O Exception. See log file for details.
|
upgrade.version4to5.err.io = Migration failed due to an I/O Exception. See log file for details.
|
||||||
settings.port.apply = Apply
|
|
||||||
unlock.label.mountAfterUnlock = Mount Drive
|
|
||||||
unlock.label.revealAfterMount = Reveal Drive
|
unlock.label.revealAfterMount = Reveal Drive
|
||||||
unlocked.lock.force.confirmation.title = Locking of %1$s failed
|
unlocked.lock.force.confirmation.title = Locking of %1$s failed
|
||||||
unlocked.lock.force.confirmation.header = Do you want to force locking?
|
unlocked.lock.force.confirmation.header = Do you want to force locking?
|
||||||
unlocked.lock.force.confirmation.content = This may be because other programs are still accessing files in the vault or because some other problem occurred.\n\nPrograms still accessing the files may not work correctly and data not already written by those programs may be lost.
|
unlocked.lock.force.confirmation.content = This may be because other programs are still accessing files in the vault or because some other problem occurred.\n\nPrograms still accessing the files may not work correctly and data not already written by those programs may be lost.
|
||||||
unlock.label.unlockAfterStartup = Auto-Unlock on Start (Experimental)
|
unlock.label.unlockAfterStartup = Auto-Unlock on Start (Experimental)
|
||||||
unlock.errorMessage.unlockFailed = Unlock failed. See log file for details.
|
unlock.errorMessage.unlockFailed = Unlock failed. See log file for details.
|
||||||
unlocked.moreOptions.mount = Mount Drive
|
|
||||||
unlocked.moreOptions.unmount = Eject Drive
|
|
||||||
upgrade.version5toX.title = Vault Version Upgrade
|
upgrade.version5toX.title = Vault Version Upgrade
|
||||||
upgrade.version5toX.msg = This vault needs to be migrated to a newer format.\nPlease make sure synchronization has finished before proceeding.
|
upgrade.version5toX.msg = This vault needs to be migrated to a newer format.\nPlease make sure synchronization has finished before proceeding.
|
||||||
|
main.createVault.nonEmptyDir.title = Creating vault failed
|
||||||
|
main.createVault.nonEmptyDir.header = Chosen directory is not empty
|
||||||
|
main.createVault.nonEmptyDir.content = The selected directory already contains files (possibly hidden). A vault can only be created in an empty directory.
|
||||||
|
settings.webdav.port.label = WebDAV Port
|
||||||
|
settings.webdav.port.prompt = 0 \= Choose automatically
|
||||||
|
settings.webdav.port.apply = Apply
|
||||||
|
settings.webdav.prefGvfsScheme.label = WebDAV Scheme
|
||||||
|
settings.volume.label = Preferred Volume Type
|
||||||
|
settings.volume.webdav = WebDAV
|
||||||
|
settings.volume.fuse = FUSE
|
||||||
|
unlock.successLabel.vaultCreated = Vault was successfully created.
|
||||||
|
unlock.successLabel.passwordChanged = Password was successfully changed.
|
||||||
|
unlock.successLabel.upgraded = Cryptomator was successfully upgraded.
|
||||||
|
unlock.label.useOwnMountPath = Use Custom Mount Point
|
||||||
|
welcome.askForUpdateCheck.dialog.title = Update check
|
||||||
|
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
|
||||||
|
welcome.askForUpdateCheck.dialog.content = Recommended\: Enable the update check to always be sure you have the newest version of Cryptomator, with all security patches, installed.\n\nYou can change this from within the settings at any time.
|
||||||
|
settings.volume.dokany = Dokany
|
||||||
|
main.gracefulShutdown.dialog.title = Locking vault(s) failed
|
||||||
|
main.gracefulShutdown.dialog.header = Vault(s) in use
|
||||||
|
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
|
||||||
|
main.gracefulShutdown.button.tryAgain = Try again
|
||||||
|
main.gracefulShutdown.button.forceShutdown = Force shutdown
|
||||||
|
unlock.pendingMessage.unlocking = Unlocking vault...
|
||||||
|
unlock.failedDialog.title = Unlock failed
|
||||||
|
unlock.failedDialog.header = Unlock failed
|
||||||
|
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
|
||||||
|
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
|
||||||
|
unlock.label.useReadOnlyMode = Read-Only
|
||||||
|
unlock.label.chooseMountPath = Choose empty directory…
|
||||||
@@ -1,35 +1,35 @@
|
|||||||
app.name = Криптоматор
|
app.name = Криптоматор
|
||||||
# main.fxml
|
# main.fxml
|
||||||
main.emptyListInstructions = Натиснете тук за да добавите сейф
|
main.emptyListInstructions = Натиснете тук за добавяне на сейф
|
||||||
main.directoryList.contextMenu.remove = Премахнете от листа
|
main.directoryList.contextMenu.remove = Премахване от листата
|
||||||
main.directoryList.contextMenu.changePassword = Сменете парола
|
main.directoryList.contextMenu.changePassword = Смяна на парола
|
||||||
main.addDirectory.contextMenu.new = Създайте нов сейф
|
main.addDirectory.contextMenu.new = Създаване на нов сейф
|
||||||
main.addDirectory.contextMenu.open = Отворете съществуващ сейф
|
main.addDirectory.contextMenu.open = Отворяне на съществуващ сейф
|
||||||
# welcome.fxml
|
# welcome.fxml
|
||||||
welcome.checkForUpdates.label.currentlyChecking = Проверете за нови версии
|
welcome.checkForUpdates.label.currentlyChecking = Проверка за обновления...
|
||||||
welcome.newVersionMessage = Version %1$s can be downloaded.\nThis is %2$s.
|
welcome.newVersionMessage = Версия %1$s може да бъде свалена.\nТази е %2$s.
|
||||||
# initialize.fxml
|
# initialize.fxml
|
||||||
initialize.label.password = Парола
|
initialize.label.password = Парола
|
||||||
initialize.label.retypePassword = Повторете Паролата
|
initialize.label.retypePassword = Повторете паролата
|
||||||
initialize.button.ok = Създайте Сейф
|
initialize.button.ok = Създаване на сейф
|
||||||
initialize.messageLabel.alreadyInitialized = Сейфа е вече активен
|
initialize.messageLabel.alreadyInitialized = Сейфа е вече активен
|
||||||
initialize.messageLabel.initializationFailed = Неуспешно активиране на сейф. Проверете лог файловете за повече информация.
|
initialize.messageLabel.initializationFailed = Неуспешно активиране на сейф. Проверете лог файловете за повече информация.
|
||||||
# notfound.fxml
|
# notfound.fxml
|
||||||
notfound.label = Сейфа не може да бъде намерен. Може би е бил преместен?
|
notfound.label = Сейфа не може да бъде намерен. Може би е бил преместен?
|
||||||
# upgrade.fxml
|
# upgrade.fxml
|
||||||
upgrade.button = Обновете версията на Сейф
|
upgrade.button = Обновете сейфа
|
||||||
upgrade.version3dropBundleExtension.msg = This vault needs to be migrated to a newer format.\n"%1$s" will be renamed to "%2$s".\nPlease make sure synchronization has finished before proceeding.
|
upgrade.version3dropBundleExtension.msg = Този сейф трябва да бъде променен към новия формат.\n"%1$s" ще бъде преименуван в "%2$s".\nМоля, уверете се, че синхронизацията е преключила преди да продължите.
|
||||||
upgrade.version3dropBundleExtension.err.alreadyExists = Автоматичната миграция е неуспешна. "%s" вече съществува.
|
upgrade.version3dropBundleExtension.err.alreadyExists = Автоматичната промяна е неуспешна. "%s" вече съществува.
|
||||||
# unlock.fxml
|
# unlock.fxml
|
||||||
unlock.label.password = Парола
|
unlock.label.password = Парола
|
||||||
unlock.label.mountName = Име на диск
|
unlock.label.mountName = Име на диск
|
||||||
unlock.label.winDriveLetter = Инициали на диск
|
unlock.label.winDriveLetter = Инициали на диск
|
||||||
unlock.label.downloadsPageLink = Покажи всички версии на Криптоматор
|
unlock.label.downloadsPageLink = Всички версии на Криптоматор
|
||||||
unlock.label.advancedHeading = Опции за напреднали
|
unlock.label.advancedHeading = Опции за напреднали
|
||||||
unlock.button.unlock = Отключи Сейф
|
unlock.button.unlock = Отключване на сейф
|
||||||
unlock.button.advancedOptions.show = Покажи повече опции
|
unlock.button.advancedOptions.show = Повече опции
|
||||||
unlock.button.advancedOptions.hide = Покажи по-малко опции
|
unlock.button.advancedOptions.hide = По-малко опции
|
||||||
unlock.choicebox.winDriveLetter.auto = Автоматично наименование на Диска
|
unlock.choicebox.winDriveLetter.auto = Автоматично наименование на диска
|
||||||
unlock.errorMessage.wrongPassword = Неправилна парола
|
unlock.errorMessage.wrongPassword = Неправилна парола
|
||||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Неподържана версия. Този сейф е бил създаден със стара версия на Криптоматор.
|
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Неподържана версия. Този сейф е бил създаден със стара версия на Криптоматор.
|
||||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Неподържана версия. Този сейф е бил създаден с по-нова версия на Криптоматор.
|
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Неподържана версия. Този сейф е бил създаден с по-нова версия на Криптоматор.
|
||||||
@@ -38,67 +38,88 @@ unlock.messageLabel.startServerFailed = Неуспешно стартиране
|
|||||||
changePassword.label.oldPassword = Стара парола
|
changePassword.label.oldPassword = Стара парола
|
||||||
changePassword.label.newPassword = Нова парола
|
changePassword.label.newPassword = Нова парола
|
||||||
changePassword.label.retypePassword = Повторете паролата
|
changePassword.label.retypePassword = Повторете паролата
|
||||||
changePassword.label.downloadsPageLink = All Cryptomator versions
|
changePassword.label.downloadsPageLink = Всички версии на Криптоматор
|
||||||
changePassword.button.change = Смени паролата
|
changePassword.button.change = Смени паролата
|
||||||
changePassword.errorMessage.wrongPassword = Неправилна парола
|
changePassword.errorMessage.wrongPassword = Неправилна парола
|
||||||
changePassword.errorMessage.decryptionFailed = Неуспешно декриптиране
|
changePassword.errorMessage.decryptionFailed = Неуспешно декриптиране
|
||||||
# unlocked.fxml
|
# unlocked.fxml
|
||||||
unlocked.button.lock = Заключи Сейфа
|
unlocked.button.lock = Заключване на Сейфа
|
||||||
unlocked.moreOptions.reveal = Покажи
|
unlocked.moreOptions.reveal = Покажи диска
|
||||||
unlocked.moreOptions.copyUrl = Copy WebDAV URL
|
|
||||||
unlocked.label.revealFailed = Командата е неуспешна
|
unlocked.label.revealFailed = Командата е неуспешна
|
||||||
unlocked.label.unmountFailed = Ejecting drive failed
|
unlocked.label.unmountFailed = Изваждането на диска е неуспешно
|
||||||
unlocked.label.statsEncrypted = encrypted
|
unlocked.label.statsEncrypted = криптирано
|
||||||
unlocked.label.statsDecrypted = decrypted
|
unlocked.label.statsDecrypted = декрептирано
|
||||||
unlocked.ioGraph.yAxis.label = Throughput (MiB/s)
|
unlocked.ioGraph.yAxis.label = Скорост (MB/s)
|
||||||
# settings.fxml
|
# settings.fxml
|
||||||
settings.version.label = Version %s
|
settings.version.label = Версия %s
|
||||||
settings.checkForUpdates.label = Check for Updates
|
settings.checkForUpdates.label = Проверка за обновления
|
||||||
settings.port.label = WebDAV Port
|
settings.requiresRestartLabel = * Криптоматор трябва да се рестартира
|
||||||
settings.port.prompt = 0 \= Choose automatically
|
|
||||||
settings.requiresRestartLabel = * Cryptomator needs to restart
|
|
||||||
# tray icon
|
# tray icon
|
||||||
tray.menu.open = Open
|
tray.menu.open = Отворяне
|
||||||
tray.menu.quit = Quit
|
tray.menu.quit = Изход
|
||||||
tray.infoMsg.title = Still Running
|
tray.infoMsg.title = Все още върви
|
||||||
tray.infoMsg.msg = Cryptomator is still alive. Quit it from the tray icon.
|
tray.infoMsg.msg = Криптоматор все още върви. Излезте от иконата в трея.
|
||||||
tray.infoMsg.msg.osx = Cryptomator is still alive. Quit it from the menu bar icon.
|
tray.infoMsg.msg.osx = Криптоматор все още върви. Излезте от иконата в менюто.
|
||||||
initialize.messageLabel.passwordStrength.0 = Very weak
|
initialize.messageLabel.passwordStrength.0 = Прекалено слаба
|
||||||
initialize.messageLabel.passwordStrength.1 = Weak
|
initialize.messageLabel.passwordStrength.1 = Слаба
|
||||||
initialize.messageLabel.passwordStrength.2 = Fair
|
initialize.messageLabel.passwordStrength.2 = Добра
|
||||||
initialize.messageLabel.passwordStrength.3 = Strong
|
initialize.messageLabel.passwordStrength.3 = Силна
|
||||||
initialize.messageLabel.passwordStrength.4 = Very strong
|
initialize.messageLabel.passwordStrength.4 = Много силна
|
||||||
initialize.label.doNotForget = IMPORTANT\: If you forget your password, there is no way to recover your data.
|
initialize.label.doNotForget = ВАЖНО\: Ако забравите паролата, няма начин да възстановите данните.
|
||||||
main.directoryList.remove.confirmation.title = Remove Vault
|
main.directoryList.remove.confirmation.title = Премахване на сейф
|
||||||
main.directoryList.remove.confirmation.header = Do you really want to remove this vault?
|
main.directoryList.remove.confirmation.header = Наистина ли искате да премахнете този сейф?
|
||||||
main.directoryList.remove.confirmation.content = The vault will only be removed from the list. To permanently delete it, please delete the vault from your filesystem.
|
main.directoryList.remove.confirmation.content = Този сейф ще бъде премахнат само от листа. За да го изтриете напълно, моля, изтрийте сейфа от файл системата.
|
||||||
upgrade.version3to4.msg = This vault needs to be migrated to a newer format.\nEncrypted folder names will be updated.\nPlease make sure synchronization has finished before proceeding.
|
upgrade.version3to4.msg = Този сейф трябва да бъде преместен към по-нов формат.\nКриптираните имена на папки ще бъдат обновени.\nМоля, проверете дали сихронизацията е приключила преди да продължите.
|
||||||
upgrade.version3to4.err.io = Migration failed due to an I/O Exception. See log file for details.
|
upgrade.version3to4.err.io = Преместването е отменено поради грешка в диска. Вижте лог файла за детайли.
|
||||||
settings.prefGvfsScheme.label = WebDAV Scheme
|
|
||||||
# upgrade.fxml
|
# upgrade.fxml
|
||||||
upgrade.confirmation.label = Yes, I've made sure that synchronization has finished
|
upgrade.confirmation.label = Да, сигурен съм, че сихронизацията е приключила
|
||||||
initialize.messageLabel.notEmpty = Vault not empty
|
unlock.label.savePassword = Запазване на парола
|
||||||
unlock.label.savePassword = Save Password
|
unlock.errorMessage.unauthenticVersionMac = Неуспешна оторизация на MAC версията
|
||||||
unlock.errorMessage.unauthenticVersionMac = Could not authenticate version MAC.
|
unlocked.label.mountFailed = Връзката с диска неуспешна
|
||||||
unlocked.label.mountFailed = Connecting drive failed
|
unlock.savePassword.delete.confirmation.title = Изтриване на запазената парола
|
||||||
unlock.savePassword.delete.confirmation.title = Delete Saved Password
|
unlock.savePassword.delete.confirmation.header = Неистина ли искате да изтриете запазената парола за този сейф?
|
||||||
unlock.savePassword.delete.confirmation.header = Do you really want to delete the saved password of this vault?
|
unlock.savePassword.delete.confirmation.content = Запазената парола за този сейф ще бъде незабавно премахната от Вашата система. Ако желаете да запазите паролата отново, трябва да отключите сейса с пусната опция "Запазване на павола".
|
||||||
unlock.savePassword.delete.confirmation.content = The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled.
|
settings.debugMode.label = Режим за отстраняване на грешки *
|
||||||
settings.debugMode.label = Debug Mode *
|
upgrade.version3dropBundleExtension.title = Обновяване до сейф версия 3
|
||||||
upgrade.version3dropBundleExtension.title = Vault Version 3 Upgrade (Drop Bundle Extension)
|
upgrade.version3to4.title = Обновяване на сейф от 3-та до 4-та версия
|
||||||
upgrade.version3to4.title = Vault Version 3 to 4 Upgrade
|
upgrade.version4to5.title = Обновяване на сейф от 4-та до 5-та версия
|
||||||
upgrade.version4to5.title = Vault Version 4 to 5 Upgrade
|
upgrade.version4to5.msg = Този сейф трябва да бъде променен към по-нов формат.\nКриптираните файлове ще бъдат обновени.\nМоля, проверете дали сихронизацията е приключила преди да продължите.\n\nЗабележка\: Датата на промяна на всички файлове ще бъде обновена до момента.
|
||||||
upgrade.version4to5.msg = This vault needs to be migrated to a newer format.\nEncrypted files will be updated.\nPlease make sure synchronization has finished before proceeding.\n\nNote\: Modification date of all files will be changed to the current date/time in the process.
|
upgrade.version4to5.err.io = Преместването провалено поради грешка в диска. Вижте лог файла за детайли.
|
||||||
upgrade.version4to5.err.io = Migration failed due to an I/O Exception. See log file for details.
|
unlock.label.revealAfterMount = Показване на диска
|
||||||
settings.port.apply = Apply
|
unlocked.lock.force.confirmation.title = Заключването на %1$s провалено
|
||||||
unlock.label.mountAfterUnlock = Mount Drive
|
unlocked.lock.force.confirmation.header = Желаете ли принудително заключване?
|
||||||
unlock.label.revealAfterMount = Reveal Drive
|
unlocked.lock.force.confirmation.content = Това е може би защото други програми все още използват файловете в сейфа или защото има някакъв друг проблем.\n\nПрограмите, имащи достъп до файловете, може да не работят правилно и информацията, незаписана от тези програми, може да бъде изгубена.
|
||||||
unlocked.lock.force.confirmation.title = Locking of %1$s failed
|
unlock.label.unlockAfterStartup = Автоматично отключване при стартиране (Експериментално)
|
||||||
unlocked.lock.force.confirmation.header = Do you want to force locking?
|
unlock.errorMessage.unlockFailed = Грешка при отключване. Вижте лог файла за детайли.
|
||||||
unlocked.lock.force.confirmation.content = This may be because other programs are still accessing files in the vault or because some other problem occurred.\n\nPrograms still accessing the files may not work correctly and data not already written by those programs may be lost.
|
upgrade.version5toX.title = Обновяване на версията на сейфа
|
||||||
unlock.label.unlockAfterStartup = Auto-Unlock on Start (Experimental)
|
upgrade.version5toX.msg = Този сейф трябва да бъде обновен до по-нов формат.\nМоля, уверете се, че сихронизацията е приключила, преди да продължите.
|
||||||
unlock.errorMessage.unlockFailed = Unlock failed. See log file for details.
|
main.createVault.nonEmptyDir.title = Неуспешно създаване на сейф
|
||||||
unlocked.moreOptions.mount = Mount Drive
|
main.createVault.nonEmptyDir.header = Избраната директория не е празна
|
||||||
unlocked.moreOptions.unmount = Eject Drive
|
main.createVault.nonEmptyDir.content = Избраната директория съдържа файлове /може би скрити/. Сейфът може да бъде създаден само в празна директория
|
||||||
upgrade.version5toX.title = Vault Version Upgrade
|
settings.webdav.port.label = WebCAM порт
|
||||||
upgrade.version5toX.msg = This vault needs to be migrated to a newer format.\nPlease make sure synchronization has finished before proceeding.
|
settings.webdav.port.prompt = 0 \= Автоматично избиране
|
||||||
|
settings.webdav.port.apply = Приложи
|
||||||
|
settings.webdav.prefGvfsScheme.label = WebDAV схема
|
||||||
|
settings.volume.label = Метод за точка
|
||||||
|
settings.volume.webdav = WebDAV
|
||||||
|
settings.volume.fuse = Предпазител
|
||||||
|
unlock.successLabel.vaultCreated = Сейфът беше създаден успешно
|
||||||
|
unlock.successLabel.passwordChanged = Паролата беше сменена успешно
|
||||||
|
unlock.successLabel.upgraded = Криптоматор беше обновен
|
||||||
|
unlock.label.useOwnMountPath = Използвайте собствена точка за монтиране
|
||||||
|
welcome.askForUpdateCheck.dialog.title = Update check
|
||||||
|
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
|
||||||
|
welcome.askForUpdateCheck.dialog.content = Recommended\: Enable the update check to always be sure you have the newest version of Cryptomator, with all security patches, installed.\n\nYou can change this from within the settings at any time.
|
||||||
|
settings.volume.dokany = Dokany
|
||||||
|
main.gracefulShutdown.dialog.title = Locking vault(s) failed
|
||||||
|
main.gracefulShutdown.dialog.header = Vault(s) in use
|
||||||
|
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
|
||||||
|
main.gracefulShutdown.button.tryAgain = Try again
|
||||||
|
main.gracefulShutdown.button.forceShutdown = Force shutdown
|
||||||
|
unlock.pendingMessage.unlocking = Unlocking vault...
|
||||||
|
unlock.failedDialog.title = Unlock failed
|
||||||
|
unlock.failedDialog.header = Unlock failed
|
||||||
|
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
|
||||||
|
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
|
||||||
|
unlock.label.useReadOnlyMode = Read-Only
|
||||||
|
unlock.label.chooseMountPath = Choose empty directory…
|
||||||
125
main/ui/src/main/resources/localization/ca.txt
Normal file
125
main/ui/src/main/resources/localization/ca.txt
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
app.name = Cryptomator
|
||||||
|
# main.fxml
|
||||||
|
main.emptyListInstructions = Feu click ací per afegir una caixa forta
|
||||||
|
main.directoryList.contextMenu.remove = Elimina de la llista
|
||||||
|
main.directoryList.contextMenu.changePassword = Canvia la contrasenya
|
||||||
|
main.addDirectory.contextMenu.new = Crea una caixa forta nova
|
||||||
|
main.addDirectory.contextMenu.open = Obri una caixa forta existent
|
||||||
|
# welcome.fxml
|
||||||
|
welcome.checkForUpdates.label.currentlyChecking = Comprovant actualitzacions
|
||||||
|
welcome.newVersionMessage = La versió %1$s és disponible per descarregar.\nLa versió actual és %2$s.
|
||||||
|
# initialize.fxml
|
||||||
|
initialize.label.password = Contrasenya
|
||||||
|
initialize.label.retypePassword = Torneu a escriure la contrasenya
|
||||||
|
initialize.button.ok = Crea una caixa forta
|
||||||
|
initialize.messageLabel.alreadyInitialized = La caixa forta ja està inicialitzada
|
||||||
|
initialize.messageLabel.initializationFailed = No s'ha pogut inicialitzar la caixa forta. Consulteu l'arxiu de registre per a més informació.
|
||||||
|
# notfound.fxml
|
||||||
|
notfound.label = No s'ha trobat la caixa forta. S'ha mogut a altre lloc?
|
||||||
|
# upgrade.fxml
|
||||||
|
upgrade.button = Actualitza la caixa forta
|
||||||
|
upgrade.version3dropBundleExtension.msg = Esta caixa forta es deu actualitzar a un format més modern.\nEs va a canviar el nom de "%1$s" a "%2$s".\nAssegureu-vos de que la sincronització ha acabat abans d'iniciar el procés.
|
||||||
|
upgrade.version3dropBundleExtension.err.alreadyExists = Error en la migració automàtica.\n"%s" ja existeix.
|
||||||
|
# unlock.fxml
|
||||||
|
unlock.label.password = Contrasenya
|
||||||
|
unlock.label.mountName = Nom de la unitat
|
||||||
|
unlock.label.winDriveLetter = Lletra de la unitat
|
||||||
|
unlock.label.downloadsPageLink = Totes les versions de Cryptomator
|
||||||
|
unlock.label.advancedHeading = Opcions avançades
|
||||||
|
unlock.button.unlock = Debloqueja la caixa forta
|
||||||
|
unlock.button.advancedOptions.show = Més opcions
|
||||||
|
unlock.button.advancedOptions.hide = Menys opcions
|
||||||
|
unlock.choicebox.winDriveLetter.auto = Assigna automàticament
|
||||||
|
unlock.errorMessage.wrongPassword = Contrasenya incorrecta
|
||||||
|
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = La caixa forta no és compatible. Aquesta caixa forta s'ha creat amb una versió anterior de Cryptomator.
|
||||||
|
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = La caixa forta no és compatible. Aquesta caixa forta s'ha creat amb una versió més nova de Cryptomator.
|
||||||
|
unlock.messageLabel.startServerFailed = S'ha produït un error en iniciar el servidor WebDAV.
|
||||||
|
# change_password.fxml
|
||||||
|
changePassword.label.oldPassword = Contrasenya antiga
|
||||||
|
changePassword.label.newPassword = Contrasenya nova
|
||||||
|
changePassword.label.retypePassword = Torneu a escriure la contrasenya
|
||||||
|
changePassword.label.downloadsPageLink = Totes les versions de Cryptomator
|
||||||
|
changePassword.button.change = Canvia la contrasenya
|
||||||
|
changePassword.errorMessage.wrongPassword = Contrasenya incorrecta
|
||||||
|
changePassword.errorMessage.decryptionFailed = Ha fallat el desencriptatge
|
||||||
|
# unlocked.fxml
|
||||||
|
unlocked.button.lock = Bloqueja la caixa forta
|
||||||
|
unlocked.moreOptions.reveal = Mostra la unitat
|
||||||
|
unlocked.label.revealFailed = L'ordre ha fallat
|
||||||
|
unlocked.label.unmountFailed = Error al expulsar la unidad
|
||||||
|
unlocked.label.statsEncrypted = xifrat
|
||||||
|
unlocked.label.statsDecrypted = desxifrat
|
||||||
|
unlocked.ioGraph.yAxis.label = Velocitat de transferència de dades (MiB/s)
|
||||||
|
# settings.fxml
|
||||||
|
settings.version.label = Versió %s
|
||||||
|
settings.checkForUpdates.label = Comprova si hi ha actualitzacions
|
||||||
|
settings.requiresRestartLabel = És necessari reiniciar * Cryptomator
|
||||||
|
# tray icon
|
||||||
|
tray.menu.open = Obri
|
||||||
|
tray.menu.quit = Surt
|
||||||
|
tray.infoMsg.title = Encara s'està executant
|
||||||
|
tray.infoMsg.msg = Cryptomator encara està executant-se. Sortiu des de la icona de la safata.
|
||||||
|
tray.infoMsg.msg.osx = Cryptomator encara està executant-se. Sortiu des de la icona de la barra de menú
|
||||||
|
initialize.messageLabel.passwordStrength.0 = Molt dèbil
|
||||||
|
initialize.messageLabel.passwordStrength.1 = Dèbil
|
||||||
|
initialize.messageLabel.passwordStrength.2 = Acceptable
|
||||||
|
initialize.messageLabel.passwordStrength.3 = Forta
|
||||||
|
initialize.messageLabel.passwordStrength.4 = Molt forta
|
||||||
|
initialize.label.doNotForget = IMPORTANT\: No hi ha manera de recuperar les dades si oblideu la contrasenya.
|
||||||
|
main.directoryList.remove.confirmation.title = Suprimeix la caixa forta
|
||||||
|
main.directoryList.remove.confirmation.header = ¿Esteu segur que voleu suprimir aquesta caixa forta?
|
||||||
|
main.directoryList.remove.confirmation.content = La caixa forta només es suprimeix de la llista. Per tal de eliminar-la permanentment esborreu la caixa forta del vostre sistema de fitxers.
|
||||||
|
upgrade.version3to4.msg = S'ha de migrar la caixa forta a un format més nou.\nS'actualitzaran els noms xifrats de les carpetes.\nAssegureu-vos que la sincronització ha acabat abans de continuar.
|
||||||
|
upgrade.version3to4.err.io = Error en la migració degut a una excepció de E/S. Comproveu el registre per veure'n els detalls.\n
|
||||||
|
# upgrade.fxml
|
||||||
|
upgrade.confirmation.label = Sí, m'he assegurat que la sincronització hagi acabat
|
||||||
|
unlock.label.savePassword = Desa la contrasenya
|
||||||
|
unlock.errorMessage.unauthenticVersionMac = No s'ha pogut autenticar la versió de MAC.
|
||||||
|
unlocked.label.mountFailed = Ha fallat el muntatge de la unitat
|
||||||
|
unlock.savePassword.delete.confirmation.title = Elimina la contrasenya desada
|
||||||
|
unlock.savePassword.delete.confirmation.header = Esteu segur que voleu eliminar la contrasenya desada d'aquesta unitat?
|
||||||
|
unlock.savePassword.delete.confirmation.content = La contrasenya desada d'aquesta caixa forta va a ser eliminada inmediatament del clauer del seu sistema. Si voleu tornar a desar la contrasenya haureu de tornar a desbloquejar la vostra caixa forta i activar l'opció "Desa la contrasenya".
|
||||||
|
settings.debugMode.label = Mode de depuració *
|
||||||
|
upgrade.version3dropBundleExtension.title = Actualitza la caixa forta a la versió 3 (Drop Bundle Extension)
|
||||||
|
upgrade.version3to4.title = Actualitza la caixa forta de la versió 3 a la 4
|
||||||
|
upgrade.version4to5.title = Actualitza la caixa forta de la versió 4 a la 5
|
||||||
|
upgrade.version4to5.msg = S'ha de migrar la caixa forta a un format més nou.\nS'actualitzaran els fitxers xifrats.\nAssegureu-vos que la sincronització ha acabat abans de continuar.\n\nNota\: la data de modificació de tots els fitxers es canviarà a la data/hora del procés.
|
||||||
|
upgrade.version4to5.err.io = La migració ha fallat a causa d'una excepció d'E/S. Comproveu el registre per veure'n els detalls.
|
||||||
|
unlock.label.revealAfterMount = Mostra la unitat
|
||||||
|
unlocked.lock.force.confirmation.title = Ha fallat el bloqueig de %1$s
|
||||||
|
unlocked.lock.force.confirmation.header = Voleu forçar el bloqueig?
|
||||||
|
unlocked.lock.force.confirmation.content = Això pot ser perquè altres programes encara estan accedint als fitxers de la caixa forta o perquè s'ha produït un altre problema.\n\nEls programes què encara estan accedint als fitxers poden funcionar incorrectament i les dades què aquests programes no hagin escrit es poden perdre.
|
||||||
|
unlock.label.unlockAfterStartup = Desbloqueig automàtic al iniciar (experimental)
|
||||||
|
unlock.errorMessage.unlockFailed = Ha fallat el desbloqueig. Comproveu el registre per veure'n els detalls.
|
||||||
|
upgrade.version5toX.title = Actualització de la versió de la caixa forta
|
||||||
|
upgrade.version5toX.msg = S'ha de migrar la caixa forta a un format més nou.\nAssegureu-vos que la sincronització ha acabat abans de continuar.
|
||||||
|
main.createVault.nonEmptyDir.title = Ha fallat la creació de la caixa forta
|
||||||
|
main.createVault.nonEmptyDir.header = El directori seleccionat no és buit
|
||||||
|
main.createVault.nonEmptyDir.content = Hi ha fitxers (possibement ocults) al directori seleccionat. Només es pot crear una caixa forta a un directori buit.
|
||||||
|
settings.webdav.port.label = Port WebDAV
|
||||||
|
settings.webdav.port.prompt = 0 \= Tria automàticament
|
||||||
|
settings.webdav.port.apply = Aplica
|
||||||
|
settings.webdav.prefGvfsScheme.label = Esquema de WebDAV
|
||||||
|
settings.volume.label = Tipus de volum preferit
|
||||||
|
settings.volume.webdav = WebDAV
|
||||||
|
settings.volume.fuse = FUSE
|
||||||
|
unlock.successLabel.vaultCreated = La caixa forta s'ha creat correctament.
|
||||||
|
unlock.successLabel.passwordChanged = La contrasenya s'ha canviat correctament.
|
||||||
|
unlock.successLabel.upgraded = Cryptomator s'ha actualitzat correctament.
|
||||||
|
unlock.label.useOwnMountPath = Utilitza un punt de muntatge personalitzat
|
||||||
|
welcome.askForUpdateCheck.dialog.title = Comprovació d'actualizacions
|
||||||
|
welcome.askForUpdateCheck.dialog.header = Activo la comprovació automàtica d'actualitzacions?
|
||||||
|
welcome.askForUpdateCheck.dialog.content = Recomanat\: Activa la comprovació d'actualitzacions per assegurar-vos que sempre teniu la darrera versió de Cryptomator instal·lada amb totes les actualitzacions de seguretat.\n\nPodeu canviar aquesta opció des de la configuració en qualsevol moment.
|
||||||
|
settings.volume.dokany = Dokany
|
||||||
|
main.gracefulShutdown.dialog.title = Ha fallat el bloqueig de la caixa(es) forta(es)
|
||||||
|
main.gracefulShutdown.dialog.header = La caixa(es) forta(es) és(són) en ús
|
||||||
|
main.gracefulShutdown.dialog.content = Hi ha programes encara estan utilitzant una caixa forta o més d'una. Tanqueu-los per permetre que Cryptomator es tanqui correctament i, a continuació, torneu-ho a intentar.\n\nSi això no funciona, es pot forçar l'aturada de Cryptomator tot i que no es recomana, donç pot comportar pèrdua de dades.
|
||||||
|
main.gracefulShutdown.button.tryAgain = Torna-ho a intentar
|
||||||
|
main.gracefulShutdown.button.forceShutdown = Força l'aturada
|
||||||
|
unlock.pendingMessage.unlocking = La caixa forta s'està desbloquejant...
|
||||||
|
unlock.failedDialog.title = Unlock failed
|
||||||
|
unlock.failedDialog.header = Unlock failed
|
||||||
|
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
|
||||||
|
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
|
||||||
|
unlock.label.useReadOnlyMode = Read-Only
|
||||||
|
unlock.label.chooseMountPath = Choose empty directory…
|
||||||
127
main/ui/src/main/resources/localization/cs.txt
Normal file
127
main/ui/src/main/resources/localization/cs.txt
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
app.name = Cryptomator
|
||||||
|
# main.fxml
|
||||||
|
main.emptyListInstructions = Trezor přidáte kliknutím zde\n
|
||||||
|
main.directoryList.contextMenu.remove = Odstranit ze seznamu
|
||||||
|
main.directoryList.contextMenu.changePassword = Změnit heslo
|
||||||
|
main.addDirectory.contextMenu.new = Vytvořit nový trezor
|
||||||
|
main.addDirectory.contextMenu.open = Otevřít existující trezor
|
||||||
|
# welcome.fxml
|
||||||
|
welcome.checkForUpdates.label.currentlyChecking = Hledání aktualizací...
|
||||||
|
welcome.newVersionMessage = Ke stažení je verze %1$s.\nNyní je nainstalovaná %2$s.
|
||||||
|
# initialize.fxml
|
||||||
|
initialize.label.password = Heslo
|
||||||
|
initialize.label.retypePassword = Zopakování hesla
|
||||||
|
initialize.button.ok = Vytvořit trezor
|
||||||
|
initialize.messageLabel.alreadyInitialized = Trezor je už připravený
|
||||||
|
initialize.messageLabel.initializationFailed = Trezor se nepodařilo připravit. Podrobnosti naleznete v souboru se záznamem událostí (log).
|
||||||
|
# notfound.fxml
|
||||||
|
notfound.label = Trezor nebyl nalezen. Možná byl přesunut?
|
||||||
|
# upgrade.fxml
|
||||||
|
upgrade.button = Přechod na novější verzi trezoru
|
||||||
|
upgrade.version3dropBundleExtension.msg = Tento trezor je třeba aktualizovat na novější formát.\n„%1$s“ bude přejmenováno na „%2$s“.\nNež budete pokračovat ověřte, že synchronizace byla dokončena.
|
||||||
|
upgrade.version3dropBundleExtension.err.alreadyExists = Automatický převod se nezdařil.\n„%s“ už existuje.
|
||||||
|
# unlock.fxml
|
||||||
|
unlock.label.password = Heslo
|
||||||
|
unlock.label.mountName = Název jednotky
|
||||||
|
unlock.label.winDriveLetter = Písmeno jednotky
|
||||||
|
unlock.label.downloadsPageLink = Všechny verze Cryptomator
|
||||||
|
unlock.label.advancedHeading = Pokročilé volby
|
||||||
|
unlock.button.unlock = Odemknout trezor
|
||||||
|
unlock.button.advancedOptions.show = Více možností
|
||||||
|
unlock.button.advancedOptions.hide = Méně možností
|
||||||
|
unlock.choicebox.winDriveLetter.auto = Přiřadit automaticky
|
||||||
|
unlock.errorMessage.wrongPassword = Chybné heslo
|
||||||
|
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Nepodporovaná verze trezoru. Byl vytvořen ve starším Cryptomator.
|
||||||
|
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Nepodporovaná verze trezoru. Byl vytvořen v novějším Cryptomator.
|
||||||
|
unlock.messageLabel.startServerFailed = Spuštění WebDAV serveru se nezdařílo.
|
||||||
|
# change_password.fxml
|
||||||
|
changePassword.label.oldPassword = Původní heslo
|
||||||
|
changePassword.label.newPassword = Nové heslo
|
||||||
|
changePassword.label.retypePassword = Zopakujte heslo
|
||||||
|
changePassword.label.downloadsPageLink = Všechny verze Cryptomator
|
||||||
|
changePassword.button.change = Změnit heslo
|
||||||
|
changePassword.errorMessage.wrongPassword = Chybné heslo
|
||||||
|
changePassword.errorMessage.decryptionFailed = Nepodařilo se rozšifrovat
|
||||||
|
# unlocked.fxml
|
||||||
|
unlocked.button.lock = Uzamknout trezor
|
||||||
|
unlocked.moreOptions.reveal = Odkrýt jednotku
|
||||||
|
unlocked.label.revealFailed = Vykonání příkazu se nezdařilo
|
||||||
|
unlocked.label.unmountFailed = Odpojení jednotky se nezdařilo
|
||||||
|
unlocked.label.statsEncrypted = zašifrováno
|
||||||
|
unlocked.label.statsDecrypted = rozšifrováno
|
||||||
|
unlocked.ioGraph.yAxis.label = Propustnost (MiB/s)
|
||||||
|
# settings.fxml
|
||||||
|
settings.version.label = Verze %s
|
||||||
|
settings.checkForUpdates.label = Zjistit případné aktualizace
|
||||||
|
settings.requiresRestartLabel = * Vyžaduje restart Cryptomator
|
||||||
|
# tray icon
|
||||||
|
tray.menu.open = Otevřít
|
||||||
|
tray.menu.quit = Ukončit
|
||||||
|
tray.infoMsg.title = Stále ještě spuštěné
|
||||||
|
tray.infoMsg.msg = Cryptomator je pořád ještě spuštěný. Ukončete ho přes ikonu v oznamovací oblasti.
|
||||||
|
tray.infoMsg.msg.osx = Cryptomator je pořád ještě spuštěný. Ukončete ho z ikony v liště nabídek.
|
||||||
|
initialize.messageLabel.passwordStrength.0 = Velmi slabé
|
||||||
|
initialize.messageLabel.passwordStrength.1 = Slabé
|
||||||
|
initialize.messageLabel.passwordStrength.2 = Dobré
|
||||||
|
initialize.messageLabel.passwordStrength.3 = Silné
|
||||||
|
# Easter egg for Czech users.
|
||||||
|
initialize.messageLabel.passwordStrength.4 = Velmi silné
|
||||||
|
initialize.label.doNotForget = DŮLEŽITÉ\: Pokud heslo ztratíte/zapomenete, nenávratně přijdete o přístup k datům\!
|
||||||
|
main.directoryList.remove.confirmation.title = Odebrat trezor
|
||||||
|
main.directoryList.remove.confirmation.header = Opravdu chcete tento trezor odebrat?
|
||||||
|
main.directoryList.remove.confirmation.content = Trezor bude pouze odebrán ze seznamu v Cryptomator. Končené odstranění provedete až smazáním jeho složky na souborovém systému.
|
||||||
|
upgrade.version3to4.msg = Tento trezor je třeba aktualizovat na novější formát.\nNázvy šifrovaných složek budou aktualizovány.\nNež budete pokračovat ověřte, že byla dokončena synchronizace.
|
||||||
|
upgrade.version3to4.err.io = Převod se nezdařil kvůli výjimce na vst./výst. Podrobnosti naleznete v souboru se záznamem událostí (log).
|
||||||
|
# upgrade.fxml
|
||||||
|
upgrade.confirmation.label = Ano, je ověřeno, že synchronizace byla dokončena
|
||||||
|
unlock.label.savePassword = Uložit heslo
|
||||||
|
unlock.errorMessage.unauthenticVersionMac = Nedaří se ověřit MAC funkci verze.
|
||||||
|
unlocked.label.mountFailed = Připojení jednotky se nezdařilo
|
||||||
|
unlock.savePassword.delete.confirmation.title = Smazat uložené heslo
|
||||||
|
unlock.savePassword.delete.confirmation.header = Opravdu chcete smazat uložené heslo pro tento trezor?
|
||||||
|
unlock.savePassword.delete.confirmation.content = Uložené heslo k tomuto trezoru bude okamžitě vymazáno ze systémové klíčenky. Pokud ho tam budete chtít znovu uložit, bude třeba trezor odemknout se zapnutou volbou „Uložit heslo“.
|
||||||
|
settings.debugMode.label = Ladící režim *
|
||||||
|
# Extension of what please? File, protocol, aplication extension for example? And bundle of what with what? Thanks :)
|
||||||
|
upgrade.version3dropBundleExtension.title = Přechod z verze 3 trezoru na novější (odebrat příp. .cryptomator a registraci bundle v macOS)
|
||||||
|
upgrade.version3to4.title = Aktualizace trezoru z verze 3 na 4
|
||||||
|
upgrade.version4to5.title = Aktualizace trezoru z verze 4 na 5
|
||||||
|
upgrade.version4to5.msg = Tento trezor je třeba aktualizovat na novější formát.\nZašifrované soubory budou aktualizovány.\nNež budete pokračovat ověřte, že synchronizace byla dokončena.\n\nPozn.\: Datum úpravy bude v rámci toho u všech dotčených souborů změněn na aktuální datum a čas.
|
||||||
|
upgrade.version4to5.err.io = Převod se nezdařil kvůli chybě na vstupu nebo výstupu. Podrobnosti naleznete v souboru se záznamem událostí (log).
|
||||||
|
unlock.label.revealAfterMount = Odkrýt jednotku
|
||||||
|
unlocked.lock.force.confirmation.title = Zamykání %1$s se nezdařilo
|
||||||
|
unlocked.lock.force.confirmation.header = Chcete vynutit uzamčení?
|
||||||
|
unlocked.lock.force.confirmation.content = Toto může být způsobeno tím, že ostatní aplikace stále ještě přistupují k souborům v trezoru nebo došlo k nějakému jinému problému.\n\nMůže se ovšem stát, že aplikace které stáje ještě pracují se soubory z trezoru to nemusí ustát a může dojít ke ztrátě ještě neuložených dat.
|
||||||
|
unlock.label.unlockAfterStartup = Automatické odemknutí při spuštění (experimentální)
|
||||||
|
unlock.errorMessage.unlockFailed = Odemknutí se nezdařilo. Podrobnosti naleznete v souboru se záznamem událostí (log).
|
||||||
|
upgrade.version5toX.title = Aktualizace verze trezoru
|
||||||
|
upgrade.version5toX.msg = Tento trezor je třeba aktualizovat na novější formát.\nNež budete pokračovat ověřte, že byla dokončena synchronizace.
|
||||||
|
main.createVault.nonEmptyDir.title = Vytvoření trezoru se nezdařilo
|
||||||
|
main.createVault.nonEmptyDir.header = Zvolená složka není prázdná
|
||||||
|
main.createVault.nonEmptyDir.content = Zvolená složka už obsahuje soubory (možná skryté). Trezor je možné vytvořit pouze v prázdné složce.
|
||||||
|
settings.webdav.port.label = Port WebDAV
|
||||||
|
settings.webdav.port.prompt = 0 \= zvolit automaticky
|
||||||
|
settings.webdav.port.apply = Použít
|
||||||
|
settings.webdav.prefGvfsScheme.label = WebDAV schéma
|
||||||
|
settings.volume.label = Způsob připojení (mount)
|
||||||
|
settings.volume.webdav = WebDAV
|
||||||
|
settings.volume.fuse = FUSE
|
||||||
|
unlock.successLabel.vaultCreated = Trezor byl úspěšně vytvořen.
|
||||||
|
unlock.successLabel.passwordChanged = Heslo bylo úspěšně změněno.
|
||||||
|
unlock.successLabel.upgraded = Cryptomator byl úspěšně povýšen na novou verzi.
|
||||||
|
unlock.label.useOwnMountPath = Použít vlastní přípojný bod
|
||||||
|
welcome.askForUpdateCheck.dialog.title = Zjišťování aktualizací
|
||||||
|
welcome.askForUpdateCheck.dialog.header = Zjišťovat automaticky nové verze?
|
||||||
|
welcome.askForUpdateCheck.dialog.content = Ke zjištění aktualizací, Cryptomator stáhne aktuální verzi z instalačních serverů a zobrazí nápovědu, když je k dispozici novější verze, než je nainstalovaná.\n\nDoporučujeme zapnout zjišťování aktualizací a zajistit tak, že vždy budete mít nejnovější verzi Cryptomator, se všemi opravami zabezpečení nainstalovanými. Pokud automatické zjišťování nezapnete, je možné ručně stahovat nejnovější verzi z https\://cryptomator.org/downloads/.\n\nToto nastavení je možné kdykoli změnit v nastavení aplikace.
|
||||||
|
settings.volume.dokany = Dokany
|
||||||
|
main.gracefulShutdown.dialog.title = Zamčení trezorů se nezdařilo
|
||||||
|
main.gracefulShutdown.dialog.header = Trezory v používání
|
||||||
|
main.gracefulShutdown.dialog.content = Jeden nebo více trezorů je stále v používání ostatními programy. Ukončete je aby se Cryptomator mohl správně vypnout a zkuste to znovu.\n\nPokud to nezabere, Cryptomator je možné vypnout vynuceně, ale to může vést ke ztrátě dat a není proto doporučeno.
|
||||||
|
main.gracefulShutdown.button.tryAgain = Zkusit znovu
|
||||||
|
main.gracefulShutdown.button.forceShutdown = Vynutit vypnutí
|
||||||
|
unlock.pendingMessage.unlocking = Odemykání trezoru…
|
||||||
|
unlock.failedDialog.title = Unlock failed
|
||||||
|
unlock.failedDialog.header = Unlock failed
|
||||||
|
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
|
||||||
|
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
|
||||||
|
unlock.label.useReadOnlyMode = Read-Only
|
||||||
|
unlock.label.chooseMountPath = Choose empty directory…
|
||||||
@@ -45,7 +45,6 @@ changePassword.errorMessage.decryptionFailed = Dekryptering fejlede
|
|||||||
# unlocked.fxml
|
# unlocked.fxml
|
||||||
unlocked.button.lock = Lås Vault
|
unlocked.button.lock = Lås Vault
|
||||||
unlocked.moreOptions.reveal = Vis drev
|
unlocked.moreOptions.reveal = Vis drev
|
||||||
unlocked.moreOptions.copyUrl = Kopier WebDAV URL
|
|
||||||
unlocked.label.revealFailed = Kommando fejlede
|
unlocked.label.revealFailed = Kommando fejlede
|
||||||
unlocked.label.unmountFailed = Afmontering af drev fejlede
|
unlocked.label.unmountFailed = Afmontering af drev fejlede
|
||||||
unlocked.label.statsEncrypted = Krypteret
|
unlocked.label.statsEncrypted = Krypteret
|
||||||
@@ -54,8 +53,6 @@ unlocked.ioGraph.yAxis.label = Throughput (MiB/s)
|
|||||||
# settings.fxml
|
# settings.fxml
|
||||||
settings.version.label = Version %s
|
settings.version.label = Version %s
|
||||||
settings.checkForUpdates.label = Tjek for opdateringer
|
settings.checkForUpdates.label = Tjek for opdateringer
|
||||||
settings.port.label = WebDAV Port
|
|
||||||
settings.port.prompt = 0 \= Vælg automatisk
|
|
||||||
settings.requiresRestartLabel = * Cryptomator skal genstartes
|
settings.requiresRestartLabel = * Cryptomator skal genstartes
|
||||||
# tray icon
|
# tray icon
|
||||||
tray.menu.open = Åbn
|
tray.menu.open = Åbn
|
||||||
@@ -74,10 +71,8 @@ main.directoryList.remove.confirmation.header = Er du sikker på at du vil slett
|
|||||||
main.directoryList.remove.confirmation.content = Valgte Vault vil kun blive slettet fra listen. For at slette denne permanent, skal du slette filerne fra dit filsystem.
|
main.directoryList.remove.confirmation.content = Valgte Vault vil kun blive slettet fra listen. For at slette denne permanent, skal du slette filerne fra dit filsystem.
|
||||||
upgrade.version3to4.msg = Denne Vault skal migreres til et nyere format.\nDe krypterede foldernavne vil blive opdateret.\nVent venligst til al synkronisering er gennemført, inden du fortsætter.
|
upgrade.version3to4.msg = Denne Vault skal migreres til et nyere format.\nDe krypterede foldernavne vil blive opdateret.\nVent venligst til al synkronisering er gennemført, inden du fortsætter.
|
||||||
upgrade.version3to4.err.io = Migrering fejlede pga. en I/O fejl. Se logfilen for yderligere detaljer.
|
upgrade.version3to4.err.io = Migrering fejlede pga. en I/O fejl. Se logfilen for yderligere detaljer.
|
||||||
settings.prefGvfsScheme.label = WebDAV Scheme
|
|
||||||
# upgrade.fxml
|
# upgrade.fxml
|
||||||
upgrade.confirmation.label = Ja, jeg har sikret mig at al synkronisering er gennemført.
|
upgrade.confirmation.label = Ja, jeg har sikret mig at al synkronisering er gennemført.
|
||||||
initialize.messageLabel.notEmpty = Vault er ikke tom
|
|
||||||
unlock.label.savePassword = Gem adgangskode
|
unlock.label.savePassword = Gem adgangskode
|
||||||
unlock.errorMessage.unauthenticVersionMac = Kunne ikke autentificere versions-MAC
|
unlock.errorMessage.unauthenticVersionMac = Kunne ikke autentificere versions-MAC
|
||||||
unlocked.label.mountFailed = Montering af drev fejlede
|
unlocked.label.mountFailed = Montering af drev fejlede
|
||||||
@@ -90,15 +85,41 @@ upgrade.version3to4.title = Vault Version 3 til 4 Opgradering
|
|||||||
upgrade.version4to5.title = Vault Version 4 til 5 Opgradering
|
upgrade.version4to5.title = Vault Version 4 til 5 Opgradering
|
||||||
upgrade.version4to5.msg = Denne Vault skal migreres til et nyere format.\nDe krypterede filer vil blive opdateret.\nVent venligst til al synkronisering er gennemført, inden du fortsætter.\n\nNote\: Denne proces vil påvirke ændringsdato og -tidspunkt for samtlige filer.
|
upgrade.version4to5.msg = Denne Vault skal migreres til et nyere format.\nDe krypterede filer vil blive opdateret.\nVent venligst til al synkronisering er gennemført, inden du fortsætter.\n\nNote\: Denne proces vil påvirke ændringsdato og -tidspunkt for samtlige filer.
|
||||||
upgrade.version4to5.err.io = Migrering fejlede pga. en I/O fejl. Se logfilen for yderligere detaljer.
|
upgrade.version4to5.err.io = Migrering fejlede pga. en I/O fejl. Se logfilen for yderligere detaljer.
|
||||||
settings.port.apply = Gem
|
|
||||||
unlock.label.mountAfterUnlock = Monter drev
|
|
||||||
unlock.label.revealAfterMount = Vis drev
|
unlock.label.revealAfterMount = Vis drev
|
||||||
unlocked.lock.force.confirmation.title = Locking of %1$s failed
|
unlocked.lock.force.confirmation.title = Locking of %1$s failed
|
||||||
unlocked.lock.force.confirmation.header = Do you want to force locking?
|
unlocked.lock.force.confirmation.header = Do you want to force locking?
|
||||||
unlocked.lock.force.confirmation.content = This may be because other programs are still accessing files in the vault or because some other problem occurred.\n\nPrograms still accessing the files may not work correctly and data not already written by those programs may be lost.
|
unlocked.lock.force.confirmation.content = This may be because other programs are still accessing files in the vault or because some other problem occurred.\n\nPrograms still accessing the files may not work correctly and data not already written by those programs may be lost.
|
||||||
unlock.label.unlockAfterStartup = Auto-Unlock on Start (Experimental)
|
unlock.label.unlockAfterStartup = Auto-Unlock on Start (Experimental)
|
||||||
unlock.errorMessage.unlockFailed = Unlock failed. See log file for details.
|
unlock.errorMessage.unlockFailed = Unlock failed. See log file for details.
|
||||||
unlocked.moreOptions.mount = Mount Drive
|
|
||||||
unlocked.moreOptions.unmount = Eject Drive
|
|
||||||
upgrade.version5toX.title = Vault Version Upgrade
|
upgrade.version5toX.title = Vault Version Upgrade
|
||||||
upgrade.version5toX.msg = This vault needs to be migrated to a newer format.\nPlease make sure synchronization has finished before proceeding.
|
upgrade.version5toX.msg = This vault needs to be migrated to a newer format.\nPlease make sure synchronization has finished before proceeding.
|
||||||
|
main.createVault.nonEmptyDir.title = Creating vault failed
|
||||||
|
main.createVault.nonEmptyDir.header = Chosen directory is not empty
|
||||||
|
main.createVault.nonEmptyDir.content = The selected directory already contains files (possibly hidden). A vault can only be created in an empty directory.
|
||||||
|
settings.webdav.port.label = WebDAV Port
|
||||||
|
settings.webdav.port.prompt = 0 \= Choose automatically
|
||||||
|
settings.webdav.port.apply = Apply
|
||||||
|
settings.webdav.prefGvfsScheme.label = WebDAV Scheme
|
||||||
|
settings.volume.label = Preferred Volume Type
|
||||||
|
settings.volume.webdav = WebDAV
|
||||||
|
settings.volume.fuse = FUSE
|
||||||
|
unlock.successLabel.vaultCreated = Vault was successfully created.
|
||||||
|
unlock.successLabel.passwordChanged = Password was successfully changed.
|
||||||
|
unlock.successLabel.upgraded = Cryptomator was successfully upgraded.
|
||||||
|
unlock.label.useOwnMountPath = Use Custom Mount Point
|
||||||
|
welcome.askForUpdateCheck.dialog.title = Update check
|
||||||
|
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
|
||||||
|
welcome.askForUpdateCheck.dialog.content = Recommended\: Enable the update check to always be sure you have the newest version of Cryptomator, with all security patches, installed.\n\nYou can change this from within the settings at any time.
|
||||||
|
settings.volume.dokany = Dokany
|
||||||
|
main.gracefulShutdown.dialog.title = Locking vault(s) failed
|
||||||
|
main.gracefulShutdown.dialog.header = Vault(s) in use
|
||||||
|
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
|
||||||
|
main.gracefulShutdown.button.tryAgain = Try again
|
||||||
|
main.gracefulShutdown.button.forceShutdown = Force shutdown
|
||||||
|
unlock.pendingMessage.unlocking = Unlocking vault...
|
||||||
|
unlock.failedDialog.title = Unlock failed
|
||||||
|
unlock.failedDialog.header = Unlock failed
|
||||||
|
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
|
||||||
|
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
|
||||||
|
unlock.label.useReadOnlyMode = Read-Only
|
||||||
|
unlock.label.chooseMountPath = Choose empty directory…
|
||||||
@@ -7,7 +7,7 @@ main.addDirectory.contextMenu.new = Tresor erstellen
|
|||||||
main.addDirectory.contextMenu.open = Tresor öffnen
|
main.addDirectory.contextMenu.open = Tresor öffnen
|
||||||
# welcome.fxml
|
# welcome.fxml
|
||||||
welcome.checkForUpdates.label.currentlyChecking = Prüfe auf Updates...
|
welcome.checkForUpdates.label.currentlyChecking = Prüfe auf Updates...
|
||||||
welcome.newVersionMessage = Version %1$s kann heruntergeladen werden.\nMomentane Version %2$s.
|
welcome.newVersionMessage = Version %1$s kann heruntergeladen werden.\nInstallierte Version %2$s.
|
||||||
# initialize.fxml
|
# initialize.fxml
|
||||||
initialize.label.password = Passwort
|
initialize.label.password = Passwort
|
||||||
initialize.label.retypePassword = Passwort bestätigen
|
initialize.label.retypePassword = Passwort bestätigen
|
||||||
@@ -45,7 +45,6 @@ changePassword.errorMessage.decryptionFailed = Entschlüsselung fehlgeschlagen
|
|||||||
# unlocked.fxml
|
# unlocked.fxml
|
||||||
unlocked.button.lock = Tresor sperren
|
unlocked.button.lock = Tresor sperren
|
||||||
unlocked.moreOptions.reveal = Laufwerk anzeigen
|
unlocked.moreOptions.reveal = Laufwerk anzeigen
|
||||||
unlocked.moreOptions.copyUrl = WebDAV-URL kopieren
|
|
||||||
unlocked.label.revealFailed = Befehl fehlgeschlagen
|
unlocked.label.revealFailed = Befehl fehlgeschlagen
|
||||||
unlocked.label.unmountFailed = Trennen des Laufwerks fehlgeschlagen
|
unlocked.label.unmountFailed = Trennen des Laufwerks fehlgeschlagen
|
||||||
unlocked.label.statsEncrypted = verschlüsselt
|
unlocked.label.statsEncrypted = verschlüsselt
|
||||||
@@ -54,8 +53,6 @@ unlocked.ioGraph.yAxis.label = Durchsatz (MiB/s)
|
|||||||
# settings.fxml
|
# settings.fxml
|
||||||
settings.version.label = Version %s
|
settings.version.label = Version %s
|
||||||
settings.checkForUpdates.label = Auf Updates prüfen
|
settings.checkForUpdates.label = Auf Updates prüfen
|
||||||
settings.port.label = WebDAV Port *
|
|
||||||
settings.port.prompt = 0 \= Automatisch wählen
|
|
||||||
settings.requiresRestartLabel = * benötigt Neustart von Cryptomator
|
settings.requiresRestartLabel = * benötigt Neustart von Cryptomator
|
||||||
# tray icon
|
# tray icon
|
||||||
tray.menu.open = Öffnen
|
tray.menu.open = Öffnen
|
||||||
@@ -74,10 +71,8 @@ main.directoryList.remove.confirmation.header = Möchten Sie diesen Tresor wirkl
|
|||||||
main.directoryList.remove.confirmation.content = Dieser Tresor wird nur aus der Liste entfernt. Um den Tresor unwiderruflich zu löschen, entfernen Sie bitte den Tresor aus Ihrem Dateisystem.
|
main.directoryList.remove.confirmation.content = Dieser Tresor wird nur aus der Liste entfernt. Um den Tresor unwiderruflich zu löschen, entfernen Sie bitte den Tresor aus Ihrem Dateisystem.
|
||||||
upgrade.version3to4.msg = Dieser Tresor muss auf ein neueres Format aktualisiert werden.\nVerschlüsselte Ordnernamen werden dabei aktualisiert.\nStellen Sie bitte sicher, dass derzeit keine Synchronisation stattfindet.
|
upgrade.version3to4.msg = Dieser Tresor muss auf ein neueres Format aktualisiert werden.\nVerschlüsselte Ordnernamen werden dabei aktualisiert.\nStellen Sie bitte sicher, dass derzeit keine Synchronisation stattfindet.
|
||||||
upgrade.version3to4.err.io = Migration aufgrund eines I/O-Fehlers fehlgeschlagen.\nWeitere Informationen in der Log-Datei.
|
upgrade.version3to4.err.io = Migration aufgrund eines I/O-Fehlers fehlgeschlagen.\nWeitere Informationen in der Log-Datei.
|
||||||
settings.prefGvfsScheme.label = WebDAV Schema
|
|
||||||
# upgrade.fxml
|
# upgrade.fxml
|
||||||
upgrade.confirmation.label = Ja, die Synchronisation ist abgeschlossen
|
upgrade.confirmation.label = Ja, die Synchronisation ist abgeschlossen
|
||||||
initialize.messageLabel.notEmpty = Tresor ist nicht leer
|
|
||||||
unlock.label.savePassword = Passwort speichern
|
unlock.label.savePassword = Passwort speichern
|
||||||
unlock.errorMessage.unauthenticVersionMac = Versions-MAC konnte nicht authentifiziert werden.
|
unlock.errorMessage.unauthenticVersionMac = Versions-MAC konnte nicht authentifiziert werden.
|
||||||
unlocked.label.mountFailed = Verbinden des Laufwerks fehlgeschlagen
|
unlocked.label.mountFailed = Verbinden des Laufwerks fehlgeschlagen
|
||||||
@@ -90,15 +85,41 @@ upgrade.version3to4.title = Upgrade Tresor-Version 3 zu 4
|
|||||||
upgrade.version4to5.title = Upgrade Tresor-Version 4 zu 5
|
upgrade.version4to5.title = Upgrade Tresor-Version 4 zu 5
|
||||||
upgrade.version4to5.msg = Dieser Tresor muss auf ein neueres Format aktualisiert werden.\nVerschlüsselte Dateien werden dabei aktualisiert.\nStellen Sie sicher, dass keine Synchronisation stattfindet, bevor Sie fortfahren.\n\nHinweis\: Beim Upgrade wird das Änderungsdatum aller Dateien auf das aktuelle Datum geändert.
|
upgrade.version4to5.msg = Dieser Tresor muss auf ein neueres Format aktualisiert werden.\nVerschlüsselte Dateien werden dabei aktualisiert.\nStellen Sie sicher, dass keine Synchronisation stattfindet, bevor Sie fortfahren.\n\nHinweis\: Beim Upgrade wird das Änderungsdatum aller Dateien auf das aktuelle Datum geändert.
|
||||||
upgrade.version4to5.err.io = Migration aufgrund eines I/O-Fehlers fehlgeschlagen.\nWeitere Informationen in der Log-Datei.
|
upgrade.version4to5.err.io = Migration aufgrund eines I/O-Fehlers fehlgeschlagen.\nWeitere Informationen in der Log-Datei.
|
||||||
settings.port.apply = Anwenden
|
|
||||||
unlock.label.mountAfterUnlock = Laufwerk verbinden
|
|
||||||
unlock.label.revealAfterMount = Laufwerk anzeigen
|
unlock.label.revealAfterMount = Laufwerk anzeigen
|
||||||
unlocked.lock.force.confirmation.title = Sperren von %1$s fehlgeschlagen
|
unlocked.lock.force.confirmation.title = Sperren von %1$s fehlgeschlagen
|
||||||
unlocked.lock.force.confirmation.header = Möchten Sie das Sperren erzwingen?
|
unlocked.lock.force.confirmation.header = Möchten Sie das Sperren erzwingen?
|
||||||
unlocked.lock.force.confirmation.content = Dies kann passieren, wenn andere Programme weiterhin auf Dateien im Tresor zugreifen. Oder es ist ein anderes Problem aufgetreten.\n\nProgramme, die weiterhin auf die Dateien zugreifen, könnten nicht mehr richtig funktionieren, oder Daten, die durch diese Programme noch nicht geschrieben wurden, könnten verloren gehen.
|
unlocked.lock.force.confirmation.content = Dies kann passieren, wenn andere Programme weiterhin auf Dateien im Tresor zugreifen. Oder es ist ein anderes Problem aufgetreten.\n\nProgramme, die weiterhin auf die Dateien zugreifen, könnten nicht mehr richtig funktionieren, oder Daten, die durch diese Programme noch nicht geschrieben wurden, könnten verloren gehen.
|
||||||
unlock.label.unlockAfterStartup = Automatisch entsperren beim Start (Experimentell)
|
unlock.label.unlockAfterStartup = Automatisch entsperren beim Start (Experimentell)
|
||||||
unlock.errorMessage.unlockFailed = Entsperren fehlgeschlagen. Siehe Log-Datei für Details.
|
unlock.errorMessage.unlockFailed = Entsperren fehlgeschlagen. Siehe Log-Datei für Details.
|
||||||
unlocked.moreOptions.mount = Laufwerk verbinden
|
|
||||||
unlocked.moreOptions.unmount = Laufwerk auswerfen
|
|
||||||
upgrade.version5toX.title = Upgrade Tresor-Version 5 zu X
|
upgrade.version5toX.title = Upgrade Tresor-Version 5 zu X
|
||||||
upgrade.version5toX.msg = Dieser Tresor muss auf ein neueres Format aktualisiert werden.\nStellen Sie sicher, dass keine Synchronisation stattfindet, bevor Sie fortfahren.
|
upgrade.version5toX.msg = Dieser Tresor muss auf ein neueres Format aktualisiert werden.\nStellen Sie sicher, dass keine Synchronisation stattfindet, bevor Sie fortfahren.
|
||||||
|
main.createVault.nonEmptyDir.title = Erstellung des Tresors fehlgeschlagen
|
||||||
|
main.createVault.nonEmptyDir.header = Ausgewählter Ordner ist nicht leer
|
||||||
|
main.createVault.nonEmptyDir.content = Der ausgewählte Ordner enthält bereits Dateien (möglicherweise unsichtbar). Ein Tresor kann nur innerhalb eines leeren Ordners erstellt werden.
|
||||||
|
settings.webdav.port.label = WebDAV-Port
|
||||||
|
settings.webdav.port.prompt = 0 \= automatische Auswahl
|
||||||
|
settings.webdav.port.apply = Anwenden
|
||||||
|
settings.webdav.prefGvfsScheme.label = WebDAV URL Schema
|
||||||
|
settings.volume.label = Laufwerkseinbindung *
|
||||||
|
settings.volume.webdav = WebDAV
|
||||||
|
settings.volume.fuse = FUSE
|
||||||
|
unlock.successLabel.vaultCreated = Der Tresor wurde erfolgreich erstellt.
|
||||||
|
unlock.successLabel.passwordChanged = Das Passwort wurde erfolgreich geändert.
|
||||||
|
unlock.successLabel.upgraded = Das Cryptomator Upgrade wurde erfolgreich abgeschlossen.
|
||||||
|
unlock.label.useOwnMountPath = Eigenes Laufwerksverzeichnis nutzen
|
||||||
|
welcome.askForUpdateCheck.dialog.title = Auf Updates prüfen
|
||||||
|
welcome.askForUpdateCheck.dialog.header = Eingebaute Update-Prüfung aktivieren?
|
||||||
|
welcome.askForUpdateCheck.dialog.content = Empfehlung\: Aktivieren Sie die Update-Prüfung, um sicherzustellen, dass Sie stets die neueste Cryptomator-Version mit allen Sicherheits-Patches verwenden.\n\nDiese Einstellung können Sie jederzeit im Einstellungs-Menü ändern.
|
||||||
|
settings.volume.dokany = Dokany
|
||||||
|
main.gracefulShutdown.dialog.title = Sperren des Tresors gescheitert
|
||||||
|
main.gracefulShutdown.dialog.header = Tresor in Gebrauch
|
||||||
|
main.gracefulShutdown.dialog.content = Ein oder mehrere Tresore werden noch von anderen Programmen genutzt. Bitte schliessen Sie die Programme um es Cryptomator zu ermöglichen richtig herunter zu fahren. Versuchen Sie es danach erneut.\n\nFalls dies nicht klappt, kann Cryptomator das Beenden erzwingen. Dies kann zu einem Datenverlust führen und ist nicht empfohlen.
|
||||||
|
main.gracefulShutdown.button.tryAgain = Versuchen Sie es erneut
|
||||||
|
main.gracefulShutdown.button.forceShutdown = Herunterfahren erzwingen
|
||||||
|
unlock.pendingMessage.unlocking = Entsperre Tresor...
|
||||||
|
unlock.failedDialog.title = Entsperren fehlgeschlagen
|
||||||
|
unlock.failedDialog.header = Entsperren fehlgeschlagen
|
||||||
|
unlock.failedDialog.content.mountPathNonExisting = Laufwerksverzeichnis existiert nicht.
|
||||||
|
unlock.failedDialog.content.mountPathNotEmpty = Laufwerksverzeichnis ist nicht leer.
|
||||||
|
unlock.label.useReadOnlyMode = Nur lesend
|
||||||
|
unlock.label.chooseMountPath = Leeren Ordner auswählen…
|
||||||
@@ -16,17 +16,27 @@ main.addDirectory.contextMenu.open=Open Existing Vault
|
|||||||
main.directoryList.remove.confirmation.title=Remove Vault
|
main.directoryList.remove.confirmation.title=Remove Vault
|
||||||
main.directoryList.remove.confirmation.header=Do you really want to remove this vault?
|
main.directoryList.remove.confirmation.header=Do you really want to remove this vault?
|
||||||
main.directoryList.remove.confirmation.content=The vault will only be removed from the list. To permanently delete it, please delete the vault from your filesystem.
|
main.directoryList.remove.confirmation.content=The vault will only be removed from the list. To permanently delete it, please delete the vault from your filesystem.
|
||||||
|
main.createVault.nonEmptyDir.title=Creating vault failed
|
||||||
|
main.createVault.nonEmptyDir.header=Chosen directory is not empty
|
||||||
|
main.createVault.nonEmptyDir.content=The selected directory already contains files (possibly hidden). A vault can only be created in an empty directory.
|
||||||
|
main.gracefulShutdown.dialog.title=Locking vault(s) failed
|
||||||
|
main.gracefulShutdown.dialog.header=Vault(s) in use
|
||||||
|
main.gracefulShutdown.dialog.content=One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
|
||||||
|
main.gracefulShutdown.button.tryAgain=Try again
|
||||||
|
main.gracefulShutdown.button.forceShutdown=Force shutdown
|
||||||
|
|
||||||
# welcome.fxml
|
# welcome.fxml
|
||||||
welcome.checkForUpdates.label.currentlyChecking=Checking for Updates...
|
welcome.checkForUpdates.label.currentlyChecking=Checking for Updates...
|
||||||
welcome.newVersionMessage=Version %1$s can be downloaded.\nThis is %2$s.
|
welcome.newVersionMessage=Version %1$s can be downloaded.\nThis is %2$s.
|
||||||
|
welcome.askForUpdateCheck.dialog.title=Update check
|
||||||
|
welcome.askForUpdateCheck.dialog.header=Enable the integrated update check?
|
||||||
|
welcome.askForUpdateCheck.dialog.content=Recommended: Enable the update check to always be sure you have the newest version of Cryptomator, with all security patches, installed.\n\nYou can change this from within the settings at any time.
|
||||||
|
|
||||||
# initialize.fxml
|
# initialize.fxml
|
||||||
initialize.label.password=Password
|
initialize.label.password=Password
|
||||||
initialize.label.retypePassword=Retype Password
|
initialize.label.retypePassword=Retype Password
|
||||||
initialize.button.ok=Create Vault
|
initialize.button.ok=Create Vault
|
||||||
initialize.messageLabel.alreadyInitialized=Vault already initialized
|
initialize.messageLabel.alreadyInitialized=Vault already initialized
|
||||||
initialize.messageLabel.notEmpty=Vault not empty
|
|
||||||
initialize.messageLabel.initializationFailed=Could not initialize vault. See log file for details.
|
initialize.messageLabel.initializationFailed=Could not initialize vault. See log file for details.
|
||||||
initialize.messageLabel.passwordStrength.0=Very weak
|
initialize.messageLabel.passwordStrength.0=Very weak
|
||||||
initialize.messageLabel.passwordStrength.1=Weak
|
initialize.messageLabel.passwordStrength.1=Weak
|
||||||
@@ -60,11 +70,13 @@ upgrade.version5toX.msg=This vault needs to be migrated to a newer format.\nPlea
|
|||||||
# unlock.fxml
|
# unlock.fxml
|
||||||
unlock.label.password=Password
|
unlock.label.password=Password
|
||||||
unlock.label.savePassword=Save Password
|
unlock.label.savePassword=Save Password
|
||||||
unlock.label.mountAfterUnlock=Mount Drive
|
|
||||||
unlock.label.mountName=Drive Name
|
unlock.label.mountName=Drive Name
|
||||||
unlock.label.unlockAfterStartup=Auto-Unlock on Start (Experimental)
|
unlock.label.unlockAfterStartup=Auto-Unlock on Start (Experimental)
|
||||||
unlock.label.revealAfterMount=Reveal Drive
|
unlock.label.revealAfterMount=Reveal Drive
|
||||||
|
unlock.label.useReadOnlyMode=Read-Only
|
||||||
unlock.label.winDriveLetter=Drive Letter
|
unlock.label.winDriveLetter=Drive Letter
|
||||||
|
unlock.label.useOwnMountPath=Use Custom Mount Point
|
||||||
|
unlock.label.chooseMountPath=Choose empty directory…
|
||||||
unlock.label.downloadsPageLink=All Cryptomator versions
|
unlock.label.downloadsPageLink=All Cryptomator versions
|
||||||
unlock.label.advancedHeading=Advanced Options
|
unlock.label.advancedHeading=Advanced Options
|
||||||
unlock.button.unlock=Unlock Vault
|
unlock.button.unlock=Unlock Vault
|
||||||
@@ -74,16 +86,26 @@ unlock.savePassword.delete.confirmation.title=Delete Saved Password
|
|||||||
unlock.savePassword.delete.confirmation.header=Do you really want to delete the saved password of this vault?
|
unlock.savePassword.delete.confirmation.header=Do you really want to delete the saved password of this vault?
|
||||||
unlock.savePassword.delete.confirmation.content=The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled.
|
unlock.savePassword.delete.confirmation.content=The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled.
|
||||||
unlock.choicebox.winDriveLetter.auto=Assign automatically
|
unlock.choicebox.winDriveLetter.auto=Assign automatically
|
||||||
|
unlock.pendingMessage.unlocking=Unlocking vault...
|
||||||
unlock.errorMessage.wrongPassword=Wrong password
|
unlock.errorMessage.wrongPassword=Wrong password
|
||||||
unlock.errorMessage.unlockFailed=Unlock failed. See log file for details.
|
unlock.errorMessage.unlockFailed=Unlock failed. See log file for details.
|
||||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
|
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
|
||||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
|
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
|
||||||
unlock.errorMessage.unauthenticVersionMac=Could not authenticate version MAC.
|
unlock.errorMessage.unauthenticVersionMac=Could not authenticate version MAC.
|
||||||
unlock.messageLabel.startServerFailed=Starting WebDAV server failed.
|
unlock.messageLabel.startServerFailed=Starting WebDAV server failed.
|
||||||
|
unlock.successLabel.vaultCreated=Vault was successfully created.
|
||||||
|
unlock.successLabel.passwordChanged=Password was successfully changed.
|
||||||
|
unlock.successLabel.upgraded=Cryptomator was successfully upgraded.
|
||||||
|
|
||||||
|
unlock.failedDialog.title=Unlock failed
|
||||||
|
unlock.failedDialog.header=Unlock failed
|
||||||
|
unlock.failedDialog.content.mountPathNonExisting=Mount point does not exist.
|
||||||
|
unlock.failedDialog.content.mountPathNotEmpty=Mount point is not empty.
|
||||||
|
|
||||||
|
|
||||||
# change_password.fxml
|
# change_password.fxml
|
||||||
changePassword.label.oldPassword=Old Password
|
changePassword.label.oldPassword=Old Password
|
||||||
changePassword.label.newPassword=New Nassword
|
changePassword.label.newPassword=New Password
|
||||||
changePassword.label.retypePassword=Retype Password
|
changePassword.label.retypePassword=Retype Password
|
||||||
changePassword.label.downloadsPageLink=All Cryptomator versions
|
changePassword.label.downloadsPageLink=All Cryptomator versions
|
||||||
changePassword.button.change=Change Password
|
changePassword.button.change=Change Password
|
||||||
@@ -92,10 +114,7 @@ changePassword.errorMessage.decryptionFailed=Decryption failed
|
|||||||
|
|
||||||
# unlocked.fxml
|
# unlocked.fxml
|
||||||
unlocked.button.lock=Lock Vault
|
unlocked.button.lock=Lock Vault
|
||||||
unlocked.moreOptions.mount=Mount Drive
|
|
||||||
unlocked.moreOptions.unmount=Eject Drive
|
|
||||||
unlocked.moreOptions.reveal=Reveal Drive
|
unlocked.moreOptions.reveal=Reveal Drive
|
||||||
unlocked.moreOptions.copyUrl=Copy WebDAV URL
|
|
||||||
unlocked.label.mountFailed=Connecting drive failed
|
unlocked.label.mountFailed=Connecting drive failed
|
||||||
unlocked.label.revealFailed=Command failed
|
unlocked.label.revealFailed=Command failed
|
||||||
unlocked.label.unmountFailed=Ejecting drive failed
|
unlocked.label.unmountFailed=Ejecting drive failed
|
||||||
@@ -109,12 +128,16 @@ unlocked.lock.force.confirmation.content=This may be because other programs are
|
|||||||
# settings.fxml
|
# settings.fxml
|
||||||
settings.version.label=Version %s
|
settings.version.label=Version %s
|
||||||
settings.checkForUpdates.label=Check for Updates
|
settings.checkForUpdates.label=Check for Updates
|
||||||
settings.port.label=WebDAV Port
|
settings.webdav.port.label=WebDAV Port
|
||||||
settings.port.prompt=0 = Choose automatically
|
settings.webdav.port.prompt=0 = Choose automatically
|
||||||
settings.port.apply=Apply
|
settings.webdav.port.apply=Apply
|
||||||
settings.prefGvfsScheme.label=WebDAV Scheme
|
settings.webdav.prefGvfsScheme.label=WebDAV Scheme
|
||||||
settings.debugMode.label=Debug Mode *
|
settings.debugMode.label=Debug Mode *
|
||||||
settings.requiresRestartLabel=* Cryptomator needs to restart
|
settings.requiresRestartLabel=* Cryptomator needs to restart
|
||||||
|
settings.volume.label=Preferred Volume Type
|
||||||
|
settings.volume.webdav=WebDAV
|
||||||
|
settings.volume.fuse=FUSE
|
||||||
|
settings.volume.dokany=Dokany
|
||||||
|
|
||||||
# tray icon
|
# tray icon
|
||||||
tray.menu.open=Open
|
tray.menu.open=Open
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user