mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 16:51:28 +00:00
Compare commits
332 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6aafa7bb5c | ||
|
|
43f2110f68 | ||
|
|
0b8f8e53af | ||
|
|
336d67195d | ||
|
|
6677079623 | ||
|
|
0974a57671 | ||
|
|
ab77673fed | ||
|
|
a70401596f | ||
|
|
ab198271a1 | ||
|
|
d62edcda73 | ||
|
|
8cba58075d | ||
|
|
426f36ce04 | ||
|
|
dd3c969f0f | ||
|
|
6a270ceccd | ||
|
|
a3474e05eb | ||
|
|
dd190b5a16 | ||
|
|
fe722629be | ||
|
|
f8e5d8aefb | ||
|
|
3e3a4ceefc | ||
|
|
a09edad165 | ||
|
|
129e9c63f8 | ||
|
|
9af58b8e6e | ||
|
|
1048ff5728 | ||
|
|
6adb591c9a | ||
|
|
d06720838e | ||
|
|
cf020e5b96 | ||
|
|
4bfd1e6433 | ||
|
|
deded33da8 | ||
|
|
be5fce0ee9 | ||
|
|
39f9da16f9 | ||
|
|
53b0d5cb9f | ||
|
|
b9a120b51b | ||
|
|
debcab47e2 | ||
|
|
8814372c68 | ||
|
|
98e5c3ff88 | ||
|
|
f1c332f455 | ||
|
|
79306ea498 | ||
|
|
d7dda7d249 | ||
|
|
1e80f4bba4 | ||
|
|
ffd3981f36 | ||
|
|
aa23635744 | ||
|
|
0317e7c21d | ||
|
|
ec5e8bba30 | ||
|
|
0caa9988d3 | ||
|
|
f16c3d5110 | ||
|
|
e1930505d1 | ||
|
|
757549919c | ||
|
|
0257802bb0 | ||
|
|
5cb4b403cd | ||
|
|
8831df9242 | ||
|
|
2229a56831 | ||
|
|
c3370a8388 | ||
|
|
1175a114ec | ||
|
|
3374dbf9a5 | ||
|
|
26aee9e42c | ||
|
|
ab82874013 | ||
|
|
39f1da105e | ||
|
|
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 | ||
|
|
86d8599d07 | ||
|
|
f760347d9d | ||
|
|
8fc647eb2b | ||
|
|
69b1bf5a26 | ||
|
|
6951edac96 | ||
|
|
76c84b34e9 | ||
|
|
1362720011 | ||
|
|
3f15352ebf | ||
|
|
fdedee49d3 | ||
|
|
b506493c13 | ||
|
|
bc09f28120 | ||
|
|
eba0935900 | ||
|
|
744225cf7a | ||
|
|
54f2667e45 | ||
|
|
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 |
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 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).
|
||||
|
||||
## 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?
|
||||
|
||||
- Open a new pull request with the patch.
|
||||
@@ -29,7 +18,7 @@
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
.project
|
||||
.classpath
|
||||
target/
|
||||
test-output/
|
||||
|
||||
# IntelliJ Settings Files #
|
||||
.idea/
|
||||
out/
|
||||
.idea_modules/
|
||||
*.iws
|
||||
# Maven #
|
||||
target/
|
||||
|
||||
# IntelliJ Settings Files (https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems) #
|
||||
.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>
|
||||
36
.idea/compiler.xml
generated
Normal file
36
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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>
|
||||
<bytecodeTargetLevel>
|
||||
<module name="buildkit" target="11" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
||||
11
.idea/encodings.xml
generated
Normal file
11
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with NO BOM">
|
||||
<file url="file://$PROJECT_DIR$/main" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/buildkit" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/commons" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/keychain" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/launcher" charset="UTF-8" />
|
||||
<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_11" default="false" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
9
.idea/modules.xml
generated
Normal file
9
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Desktop.iml" filepath="$PROJECT_DIR$/.idea/Desktop.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/main/buildkit/buildkit.iml" filepath="$PROJECT_DIR$/main/buildkit/buildkit.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
17
.idea/runConfigurations/Cryptomator_macOS.xml
generated
Normal file
17
.idea/runConfigurations/Cryptomator_macOS.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Cryptomator macOS" type="Application" factoryName="Application">
|
||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||
<module name="launcher" />
|
||||
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/Library/Application Support/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/Library/Logs/Cryptomator" -Dcryptomator.mountPointsDir="/Volumes/"" />
|
||||
<option name="WORKING_DIRECTORY" value="$MAVEN_REPOSITORY$" />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="org.cryptomator.launcher.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
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>
|
||||
19
.travis-deploy-release.tmpl.json
Normal file
19
.travis-deploy-release.tmpl.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"package": {
|
||||
"name": "buildkit",
|
||||
"repo": "cryptomator",
|
||||
"subject": "cryptomator"
|
||||
},
|
||||
"version": {
|
||||
"name": "$TRAVIS_TAG",
|
||||
"desc": "Cryptomator version $TRAVIS_TAG",
|
||||
"released": "$TODAY",
|
||||
"vcs_tag": "$TRAVIS_TAG",
|
||||
"gpgSign": true
|
||||
},
|
||||
"files":
|
||||
[
|
||||
{"includePattern": "main/buildkit/target/(buildkit-[a-z]+\\.zip)", "uploadPattern": "/$TRAVIS_TAG/$1"}
|
||||
],
|
||||
"publish": true
|
||||
}
|
||||
15
.travis-deploy-snapshot.json
Normal file
15
.travis-deploy-snapshot.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"package": {
|
||||
"name": "buildkit",
|
||||
"repo": "cryptomator",
|
||||
"subject": "cryptomator"
|
||||
},
|
||||
"version": {
|
||||
"name": "snapshot"
|
||||
},
|
||||
"files":
|
||||
[
|
||||
{"includePattern": "main/buildkit/target/(buildkit-[a-z]+\\.zip)", "uploadPattern": "/snapshot/$1", "matrixParams": {"override": 1}}
|
||||
],
|
||||
"publish": true
|
||||
}
|
||||
68
.travis.yml
68
.travis.yml
@@ -1,50 +1,54 @@
|
||||
language: java
|
||||
sudo: required
|
||||
dist: trusty
|
||||
sudo: false
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- openjdk11
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
env:
|
||||
global:
|
||||
- secure: "IfYURwZaDWuBDvyn47n0k1Zod/IQw1FF+CS5nnV08Q+NfC3vGGJMwV8m59XnbfwnWGxwvCaAbk4qP6s6+ijgZNKkvgfFMo3rfTok5zt43bIqgaFOANYV+OC/1c59gYD6ZUxhW5iNgMgU3qdsRtJuwSmfkVv/jKyLGfAbS4kN8BA=" # COVERITY_SCAN_TOKEN
|
||||
- secure: "lV9OwUbHMrMpLUH1CY+Z4puLDdFXytudyPlG1eGRsesdpuG6KM3uQVz6uAtf6lrU8DRbMM/T7ML+PmvQ4UoPPYLdLxESLLBat2qUPOIVBOhTSlCc7I0DmGy04CSvkeMy8dPaQC0ukgNiR7zwoNzfcpGRN/U9S8tziDruuHoZSrg=" # BINTRAY_API_KEY
|
||||
- secure: "HftEaabMmWn5GwKFKksUkOcelc3Mn7xazwAEy+4d4gL1+F8VhID/6DCK7nas+afUymWnxTano8Rv4Ci5MWryNkNkTH+FUPWmF3xWezc3hajSyS7RB92IZ8VPetl4Fo8UI1WwM5apDEaugalPxkIf8a7N+lpG5X/Gpumwzo3Be3w=" # BINTRAY_API_KEY
|
||||
- secure: "oWFgRTVP6lyTa7qVxlvkpm20MtVc3BtmsNXQJS6bfg2A0o/iCQMNx7OD59BaafCLGRKvCcJVESiC8FlSylVMS7CDSyYu0gg70NUiIuHp4NBM5inFWYCy/PdQsCTzr5uvNG+rMFQpMFRaCV0FrfM3tLondcVkhsHL68l93Xoexx4=" # CODACY_PROJECT_TOKEN
|
||||
- secure: "zJxgytA2Ks5Xzv+7kUaUq+EBFNQw9Qec63lcMJVuXVWczjL16nKW1EzzV515ag+OWL46z3lEPForDhufw0VtFnNmaX68jkO0mp01eLrHApc1llN2Y/U8GBXfNNazN4+Kom4H+z/AO+wJr8EsKMMUczCdQ3APgd9uVI0hzXw/Z3M=" # GITHUB_API_KEY
|
||||
addons:
|
||||
coverity_scan:
|
||||
project:
|
||||
name: "cryptomator/cryptomator"
|
||||
notification_email: sebastian.stenzel@cryptomator.org
|
||||
build_command: "mvn -fmain/pom.xml clean test -DskipTests"
|
||||
branch_pattern: release.*
|
||||
apt:
|
||||
packages:
|
||||
- haveged
|
||||
install:
|
||||
# "clean" needed until https://bugs.openjdk.java.net/browse/JDK-8067747 is resolved.
|
||||
- mvn -fmain/pom.xml clean package -DskipTests dependency:go-offline -Pcoverage
|
||||
- mvn -fmain/pom.xml clean package -DskipTests dependency:go-offline -Prelease
|
||||
- curl -o $HOME/.m2/settings.xml https://gist.githubusercontent.com/cryptobot/cf5fbd909c4782aaeeeb7c7f4a1a43da/raw/e60ee486e34ee0c79f89f947abe2c83b4290c6bb/settings.xml
|
||||
- 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
|
||||
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:
|
||||
- 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
|
||||
- export TODAY=`date +'%Y-%m-%d'`; envsubst '$TRAVIS_TAG $TODAY' < .travis-deploy-release.tmpl.json > .travis-deploy-release.json
|
||||
deploy:
|
||||
- provider: releases
|
||||
prerelease: false
|
||||
api_key:
|
||||
secure: "ZjE1j93v3qbPIe2YbmhS319aCbMdLQw0HuymmluTurxXsZtn9D4t2+eTr99vBVxGRuB5lzzGezPR5zjk5W7iHF7xhwrawXrFzr2rPJWzWFt0aM+Ry2njU1ROTGGXGTbv4anWeBlgMxLEInTAy/9ytOGNJlec83yc0THpOY2wxnk="
|
||||
file:
|
||||
- "main/uber-jar/target/Cryptomator-$TRAVIS_TAG.jar"
|
||||
- "main/ant-kit/target/antkit.tar.gz"
|
||||
- provider: bintray # SNAPSHOTS
|
||||
file: .travis-deploy-snapshot.json
|
||||
user: cryptobot
|
||||
key: $BINTRAY_API_KEY
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: cryptomator/cryptomator
|
||||
tags: true
|
||||
- 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"
|
||||
branch: develop
|
||||
- provider: bintray # RELEASES
|
||||
file: .travis-deploy-release.json
|
||||
user: cryptobot
|
||||
key: $BINTRAY_API_KEY
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: cryptomator/cryptomator
|
||||
tags: true
|
||||
- provider: script
|
||||
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"
|
||||
on:
|
||||
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://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)
|
||||
[](http://twitter.com/Cryptomator)
|
||||
[](https://poeditor.com/join/project/bHwbvJmx0E)
|
||||
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
||||
[](https://community.cryptomator.org)
|
||||
|
||||
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
|
||||
|
||||
- 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
|
||||
- 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
|
||||
@@ -21,6 +22,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
|
||||
- File names get encrypted
|
||||
- Folder structure gets obfuscated
|
||||
- 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
|
||||
|
||||
@@ -44,8 +46,7 @@ For more information on the security details visit [cryptomator.org](https://cry
|
||||
|
||||
### Dependencies
|
||||
|
||||
* Java 8 (min. 8u51, 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)
|
||||
* Java 10 (min. 10.0.1, we recommend to use the current version)
|
||||
* 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))
|
||||
|
||||
@@ -58,14 +59,6 @@ mvn clean install -Prelease
|
||||
|
||||
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
|
||||
|
||||
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,37 +0,0 @@
|
||||
<?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"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
|
||||
<id>tarball</id>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<formats>
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>target/libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/fixed-binaries</directory>
|
||||
<filtered>false</filtered>
|
||||
<outputDirectory>fixed-binaries</outputDirectory>
|
||||
<fileMode>755</fileMode>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/package</directory>
|
||||
<filtered>false</filtered>
|
||||
<outputDirectory>package</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target</directory>
|
||||
<includes>
|
||||
<include>build.xml</include>
|
||||
</includes>
|
||||
<filtered>false</filtered>
|
||||
<outputDirectory>.</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
@@ -1,99 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.3.1</version>
|
||||
</parent>
|
||||
<artifactId>ant-kit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator Ant Build Kit</name>
|
||||
<description>Builds a package that can be built with Ant locally</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>launcher</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- copy libraries to target/libs/: -->
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- copy resources to target/: -->
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||
<escapeString>\</escapeString>
|
||||
<encoding>UTF-8</encoding>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<excludes>
|
||||
<exclude>fixed-binaries/**</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
<includes>
|
||||
<include>fixed-binaries/**</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- create antkit.tar.gz: -->
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>antkit</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,74 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="Cryptomator" default="create-jar" basedir="." xmlns:fx="javafx:com.sun.javafx.tools.ant">
|
||||
<taskdef uri="javafx:com.sun.javafx.tools.ant" resource="com/sun/javafx/tools/ant/antlib.xml" classpath="\${java.class.path}:\${java.home}/../lib/ant-javafx.jar:." />
|
||||
|
||||
<!-- Define application to build -->
|
||||
<fx:application id="Cryptomator" name="Cryptomator" version="${project.version}" mainClass="org.cryptomator.launcher.Cryptomator" />
|
||||
|
||||
<!-- Create main application jar -->
|
||||
<target name="create-jar">
|
||||
<fx:jar destfile="antbuild/Cryptomator-${project.version}.jar">
|
||||
<fx:application refid="Cryptomator" />
|
||||
<fx:fileset dir="libs" includes="launcher-${project.version}.jar" />
|
||||
<fx:resources>
|
||||
<fx:fileset dir="libs" type="jar" includes="*.jar" excludes="launcher-${project.version}.jar" />
|
||||
</fx:resources>
|
||||
<fx:manifest>
|
||||
<fx:attribute name="Implementation-Vendor" value="cryptomator.org" />
|
||||
<fx:attribute name="Implementation-Title" value="Cryptomator"/>
|
||||
<fx:attribute name="Implementation-Version" value="${project.version}" />
|
||||
</fx:manifest>
|
||||
</fx:jar>
|
||||
</target>
|
||||
|
||||
<!-- Create Debian package -->
|
||||
<target name="deb" depends="create-jar">
|
||||
<fx:deploy nativeBundles="deb" 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: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:permissions elevated="false" />
|
||||
<fx:preferences install="true" />
|
||||
</fx:deploy>
|
||||
</target>
|
||||
|
||||
</project>
|
||||
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
|
||||
32
main/buildkit/assembly-linux.xml
Normal file
32
main/buildkit/assembly-linux.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>tarball</id>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
<include>version.txt</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/linux-libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
32
main/buildkit/assembly-mac.xml
Normal file
32
main/buildkit/assembly-mac.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>tarball</id>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
<include>version.txt</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/mac-libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
32
main/buildkit/assembly-win.xml
Normal file
32
main/buildkit/assembly-win.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>tarball</id>
|
||||
<includeBaseDirectory>false</includeBaseDirectory>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<fileSets>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
<include>version.txt</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/win-libs</directory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
||||
157
main/buildkit/pom.xml
Normal file
157
main/buildkit/pom.xml
Normal file
@@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.6</version>
|
||||
</parent>
|
||||
<artifactId>buildkit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator Build Kit</name>
|
||||
<description>Builds a package that can be built with Ant locally</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>launcher</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- generate version.txt -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
<includes>
|
||||
<include>version.txt</include>
|
||||
</includes>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- copy libraries to target/libs/: -->
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||
<excludeClassifiers>linux,mac,win</excludeClassifiers>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy-linux-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/linux-libs</outputDirectory>
|
||||
<includeGroupIds>org.openjfx</includeGroupIds>
|
||||
<classifier>linux</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy-mac-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/mac-libs</outputDirectory>
|
||||
<includeGroupIds>org.openjfx</includeGroupIds>
|
||||
<classifier>mac</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>copy-win-libs</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/win-libs</outputDirectory>
|
||||
<includeGroupIds>org.openjfx</includeGroupIds>
|
||||
<classifier>win</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- create buildkit.zip: -->
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>assemble-linux</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly-linux.xml</descriptor>
|
||||
</descriptors>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>buildkit-linux</finalName>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>assemble-mac</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly-mac.xml</descriptor>
|
||||
</descriptors>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>buildkit-mac</finalName>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>assemble-win</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<descriptors>
|
||||
<descriptor>assembly-win.xml</descriptor>
|
||||
</descriptors>
|
||||
<appendAssemblyId>false</appendAssemblyId>
|
||||
<finalName>buildkit-win</finalName>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
1
main/buildkit/src/main/resources/version.txt
Normal file
1
main/buildkit/src/main/resources/version.txt
Normal file
@@ -0,0 +1 @@
|
||||
${project.version}
|
||||
@@ -4,13 +4,19 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.3.1</version>
|
||||
<version>1.4.6</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator Commons</name>
|
||||
<description>Shared utilities</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- JavaFx -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-base</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Libs -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@Singleton
|
||||
public class Environment {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Environment.class);
|
||||
private static final String USER_HOME = System.getProperty("user.home");
|
||||
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
|
||||
private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME);
|
||||
private static final char PATH_LIST_SEP = ':';
|
||||
|
||||
@Inject
|
||||
public Environment() {
|
||||
LOG.debug("user.language: {}", System.getProperty("user.language"));
|
||||
LOG.debug("user.region: {}", System.getProperty("user.region"));
|
||||
LOG.debug("logback.configurationFile: {}", System.getProperty("logback.configurationFile"));
|
||||
LOG.debug("cryptomator.settingsPath: {}", System.getProperty("cryptomator.settingsPath"));
|
||||
LOG.debug("cryptomator.ipcPortPath: {}", System.getProperty("cryptomator.ipcPortPath"));
|
||||
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
|
||||
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
|
||||
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
||||
}
|
||||
|
||||
public boolean useCustomLogbackConfig() {
|
||||
return getPath("logback.configurationFile").map(Files::exists).orElse(false);
|
||||
}
|
||||
|
||||
public Stream<Path> getSettingsPath() {
|
||||
return getPaths("cryptomator.settingsPath");
|
||||
}
|
||||
|
||||
public Stream<Path> getIpcPortPath() {
|
||||
return getPaths("cryptomator.ipcPortPath");
|
||||
}
|
||||
|
||||
public Stream<Path> getKeychainPath() {
|
||||
return getPaths("cryptomator.keychainPath");
|
||||
}
|
||||
|
||||
public Optional<Path> getLogDir() {
|
||||
return getPath("cryptomator.logDir").map(this::replaceHomeDir);
|
||||
}
|
||||
|
||||
public Optional<Path> getMountPointsDir() {
|
||||
return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir);
|
||||
}
|
||||
|
||||
private Optional<Path> getPath(String propertyName) {
|
||||
String value = System.getProperty(propertyName);
|
||||
return Optional.ofNullable(value).map(Paths::get);
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
Stream<Path> getPaths(String propertyName) {
|
||||
Stream<String> rawSettingsPaths = getRawList(propertyName, PATH_LIST_SEP);
|
||||
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Paths::get).map(this::replaceHomeDir);
|
||||
}
|
||||
|
||||
private Path replaceHomeDir(Path path) {
|
||||
if (path.startsWith(RELATIVE_HOME_DIR)) {
|
||||
return ABSOLUTE_HOME_DIR.resolve(RELATIVE_HOME_DIR.relativize(path));
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
private Stream<String> getRawList(String propertyName, char separator) {
|
||||
String value = System.getProperty(propertyName);
|
||||
if (value == null) {
|
||||
return Stream.empty();
|
||||
} else {
|
||||
Iterable<String> iter = Splitter.on(separator).split(value);
|
||||
Spliterator<String> spliter = Spliterators.spliteratorUnknownSize(iter.iterator(), Spliterator.ORDERED | Spliterator.IMMUTABLE);
|
||||
return StreamSupport.stream(spliter, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface FxApplicationScoped {
|
||||
|
||||
}
|
||||
@@ -2,41 +2,41 @@
|
||||
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
||||
* All rights reserved.
|
||||
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
||||
*
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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.property.*;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Settings {
|
||||
|
||||
public static final int MIN_PORT = 1024;
|
||||
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_NUM_TRAY_NOTIFICATIONS = 3;
|
||||
public static final String DEFAULT_GVFS_SCHEME = "dav";
|
||||
public static final boolean DEFAULT_DEBUG_MODE = false;
|
||||
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
|
||||
|
||||
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 IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
|
||||
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
|
||||
private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME);
|
||||
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
||||
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
||||
|
||||
private Consumer<Settings> saveCmd;
|
||||
|
||||
/**
|
||||
@@ -44,11 +44,13 @@ public class Settings {
|
||||
*/
|
||||
Settings() {
|
||||
directories.addListener((ListChangeListener.Change<? extends VaultSettings> change) -> this.save());
|
||||
askedForUpdateCheck.addListener(this::somethingChanged);
|
||||
checkForUpdates.addListener(this::somethingChanged);
|
||||
port.addListener(this::somethingChanged);
|
||||
numTrayNotifications.addListener(this::somethingChanged);
|
||||
preferredGvfsScheme.addListener(this::somethingChanged);
|
||||
debugMode.addListener(this::somethingChanged);
|
||||
preferredVolumeImpl.addListener(this::somethingChanged);
|
||||
}
|
||||
|
||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||
@@ -71,6 +73,10 @@ public class Settings {
|
||||
return directories;
|
||||
}
|
||||
|
||||
public BooleanProperty askedForUpdateCheck() {
|
||||
return askedForUpdateCheck;
|
||||
}
|
||||
|
||||
public BooleanProperty checkForUpdates() {
|
||||
return checkForUpdates;
|
||||
}
|
||||
@@ -91,4 +97,8 @@ public class Settings {
|
||||
return debugMode;
|
||||
}
|
||||
|
||||
public ObjectProperty<VolumeImpl> preferredVolumeImpl() {
|
||||
return preferredVolumeImpl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,17 +5,16 @@
|
||||
*******************************************************************************/
|
||||
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.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
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> {
|
||||
|
||||
@@ -28,11 +27,13 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
out.beginObject();
|
||||
out.name("directories");
|
||||
writeVaultSettingsArray(out, value.getDirectories());
|
||||
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
|
||||
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
|
||||
out.name("port").value(value.port().get());
|
||||
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
||||
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get());
|
||||
out.name("debugMode").value(value.debugMode().get());
|
||||
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
@@ -52,27 +53,33 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
switch (name) {
|
||||
case "directories":
|
||||
settings.getDirectories().addAll(readVaultSettingsArray(in));
|
||||
break;
|
||||
case "checkForUpdatesEnabled":
|
||||
settings.checkForUpdates().set(in.nextBoolean());
|
||||
break;
|
||||
case "port":
|
||||
settings.port().set(in.nextInt());
|
||||
break;
|
||||
case "numTrayNotifications":
|
||||
settings.numTrayNotifications().set(in.nextInt());
|
||||
break;
|
||||
case "preferredGvfsScheme":
|
||||
settings.preferredGvfsScheme().set(in.nextString());
|
||||
break;
|
||||
case "debugMode":
|
||||
settings.debugMode().set(in.nextBoolean());
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
case "directories":
|
||||
settings.getDirectories().addAll(readVaultSettingsArray(in));
|
||||
break;
|
||||
case "askedForUpdateCheck":
|
||||
settings.askedForUpdateCheck().set(in.nextBoolean());
|
||||
break;
|
||||
case "checkForUpdatesEnabled":
|
||||
settings.checkForUpdates().set(in.nextBoolean());
|
||||
break;
|
||||
case "port":
|
||||
settings.port().set(in.nextInt());
|
||||
break;
|
||||
case "numTrayNotifications":
|
||||
settings.numTrayNotifications().set(in.nextInt());
|
||||
break;
|
||||
case "preferredGvfsScheme":
|
||||
settings.preferredGvfsScheme().set(in.nextString());
|
||||
break;
|
||||
case "debugMode":
|
||||
settings.debugMode().set(in.nextBoolean());
|
||||
break;
|
||||
case "preferredVolumeImpl":
|
||||
settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
@@ -80,6 +87,14 @@ public class SettingsJsonAdapter extends TypeAdapter<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 {
|
||||
List<VaultSettings> result = new ArrayList<>();
|
||||
in.beginArray();
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Optional;
|
||||
@@ -27,6 +28,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
@@ -34,6 +36,7 @@ import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.LazyInitializer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -45,92 +48,77 @@ import com.google.gson.GsonBuilder;
|
||||
public class SettingsProvider implements Provider<Settings> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
|
||||
private static final Path DEFAULT_SETTINGS_PATH;
|
||||
private static final long SAVE_DELAY_MS = 1000;
|
||||
|
||||
static {
|
||||
final FileSystem fs = FileSystems.getDefault();
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, "AppData/Roaming/Cryptomator/settings.json");
|
||||
} else if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, "Library/Application Support/Cryptomator/settings.json");
|
||||
} else {
|
||||
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, ".Cryptomator/settings.json");
|
||||
}
|
||||
}
|
||||
|
||||
private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
|
||||
private final AtomicReference<Settings> settings = new AtomicReference<>();
|
||||
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter();
|
||||
private final Environment env;
|
||||
private final Gson gson;
|
||||
|
||||
@Inject
|
||||
public SettingsProvider() {
|
||||
public SettingsProvider(Environment env) {
|
||||
this.env = env;
|
||||
this.gson = new GsonBuilder() //
|
||||
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
|
||||
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
|
||||
.create();
|
||||
}
|
||||
|
||||
private Path getSettingsPath() {
|
||||
final String settingsPathProperty = System.getProperty("cryptomator.settingsPath");
|
||||
return Optional.ofNullable(settingsPathProperty).filter(StringUtils::isNotBlank).map(this::replaceHomeDir).map(FileSystems.getDefault()::getPath).orElse(DEFAULT_SETTINGS_PATH);
|
||||
}
|
||||
|
||||
private String replaceHomeDir(String path) {
|
||||
if (path.startsWith("~/")) {
|
||||
return SystemUtils.USER_HOME + path.substring(1);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Settings get() {
|
||||
return LazyInitializer.initializeLazily(settings, this::load);
|
||||
}
|
||||
|
||||
private Settings load() {
|
||||
Settings settings;
|
||||
final Path settingsPath = getSettingsPath();
|
||||
try (InputStream in = Files.newInputStream(settingsPath, StandardOpenOption.READ); //
|
||||
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||
settings = gson.fromJson(reader, Settings.class);
|
||||
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElse(new Settings());
|
||||
settings.setSaveCmd(this::scheduleSave);
|
||||
return settings;
|
||||
}
|
||||
|
||||
private Stream<Settings> tryLoad(Path path) {
|
||||
LOG.debug("Attempting to load settings from {}", path);
|
||||
try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ); //
|
||||
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
|
||||
Settings settings = gson.fromJson(reader, Settings.class);
|
||||
if (settings == null) {
|
||||
throw new IOException("Unexpected EOF");
|
||||
}
|
||||
LOG.info("Settings loaded from " + settingsPath);
|
||||
LOG.info("Settings loaded from {}", path);
|
||||
return Stream.of(settings);
|
||||
} catch (NoSuchFileException e) {
|
||||
return Stream.empty();
|
||||
} catch (IOException e) {
|
||||
LOG.info("Failed to load settings, creating new one.");
|
||||
settings = new Settings();
|
||||
LOG.warn("Exception while loading settings from " + path, e);
|
||||
return Stream.empty();
|
||||
}
|
||||
settings.setSaveCmd(this::scheduleSave);
|
||||
return settings;
|
||||
}
|
||||
|
||||
private void scheduleSave(Settings settings) {
|
||||
if (settings == null) {
|
||||
return;
|
||||
}
|
||||
ScheduledFuture<?> saveCmd = saveScheduler.schedule(() -> {
|
||||
this.save(settings);
|
||||
}, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
ScheduledFuture<?> previousSaveCmd = scheduledSaveCmd.getAndSet(saveCmd);
|
||||
if (previousSaveCmd != null) {
|
||||
previousSaveCmd.cancel(false);
|
||||
}
|
||||
final Optional<Path> settingsPath = env.getSettingsPath().findFirst(); // alway save to preferred (first) path
|
||||
settingsPath.ifPresent(path -> {
|
||||
Runnable saveCommand = () -> this.save(settings, path);
|
||||
ScheduledFuture<?> scheduledTask = saveScheduler.schedule(saveCommand, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
ScheduledFuture<?> previouslyScheduledTask = scheduledSaveCmd.getAndSet(scheduledTask);
|
||||
if (previouslyScheduledTask != null) {
|
||||
previouslyScheduledTask.cancel(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void save(Settings settings) {
|
||||
private void save(Settings settings, Path settingsPath) {
|
||||
assert settings != null : "method should only be invoked by #scheduleSave, which checks for null";
|
||||
final Path settingsPath = getSettingsPath();
|
||||
LOG.debug("Attempting to save settings to {}", settingsPath);
|
||||
try {
|
||||
Files.createDirectories(settingsPath.getParent());
|
||||
try (OutputStream out = Files.newOutputStream(settingsPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
|
||||
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
|
||||
gson.toJson(settings, writer);
|
||||
LOG.info("Settings saved to " + settingsPath);
|
||||
LOG.info("Settings saved to {}", settingsPath);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to save settings.", e);
|
||||
|
||||
@@ -5,16 +5,7 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
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.UUID;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
@@ -22,20 +13,37 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
import java.nio.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 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_USES_INDIVIDUAL_MOUNTPATH = false;
|
||||
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
||||
|
||||
private final String id;
|
||||
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
|
||||
private final StringProperty mountName = new SimpleStringProperty();
|
||||
private final StringProperty winDriveLetter = new SimpleStringProperty();
|
||||
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 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) {
|
||||
this.id = Objects.requireNonNull(id);
|
||||
@@ -44,7 +52,7 @@ public class VaultSettings {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -115,14 +123,30 @@ public class VaultSettings {
|
||||
return unlockAfterStartup;
|
||||
}
|
||||
|
||||
public BooleanProperty mountAfterUnlock() {
|
||||
return mountAfterUnlock;
|
||||
}
|
||||
|
||||
public BooleanProperty 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 */
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
class VaultSettingsJsonAdapter {
|
||||
|
||||
@@ -25,8 +24,10 @@ class VaultSettingsJsonAdapter {
|
||||
out.name("mountName").value(value.mountName().get());
|
||||
out.name("winDriveLetter").value(value.winDriveLetter().get());
|
||||
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
|
||||
out.name("mountAfterUnlock").value(value.mountAfterUnlock().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();
|
||||
}
|
||||
|
||||
@@ -34,39 +35,47 @@ class VaultSettingsJsonAdapter {
|
||||
String id = null;
|
||||
String path = null;
|
||||
String mountName = null;
|
||||
String individualMountPath = null;
|
||||
String winDriveLetter = null;
|
||||
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
||||
boolean mountAfterUnlock = VaultSettings.DEFAULT_MOUNT_AFTER_UNLOCK;
|
||||
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
|
||||
boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
||||
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
switch (name) {
|
||||
case "id":
|
||||
id = in.nextString();
|
||||
break;
|
||||
case "path":
|
||||
path = in.nextString();
|
||||
break;
|
||||
case "mountName":
|
||||
mountName = in.nextString();
|
||||
break;
|
||||
case "winDriveLetter":
|
||||
winDriveLetter = in.nextString();
|
||||
break;
|
||||
case "unlockAfterStartup":
|
||||
unlockAfterStartup = in.nextBoolean();
|
||||
break;
|
||||
case "mountAfterUnlock":
|
||||
mountAfterUnlock = in.nextBoolean();
|
||||
break;
|
||||
case "revealAfterMount":
|
||||
revealAfterMount = in.nextBoolean();
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
case "id":
|
||||
id = in.nextString();
|
||||
break;
|
||||
case "path":
|
||||
path = in.nextString();
|
||||
break;
|
||||
case "mountName":
|
||||
mountName = in.nextString();
|
||||
break;
|
||||
case "winDriveLetter":
|
||||
winDriveLetter = in.nextString();
|
||||
break;
|
||||
case "unlockAfterStartup":
|
||||
unlockAfterStartup = in.nextBoolean();
|
||||
break;
|
||||
case "revealAfterMount":
|
||||
revealAfterMount = in.nextBoolean();
|
||||
break;
|
||||
case "usesIndividualMountPath":
|
||||
usesIndividualMountPath = in.nextBoolean();
|
||||
break;
|
||||
case "individualMountPath":
|
||||
individualMountPath = in.nextString();
|
||||
break;
|
||||
case "usesReadOnlyMode":
|
||||
usesReadOnlyMode = in.nextBoolean();
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
@@ -76,8 +85,10 @@ class VaultSettingsJsonAdapter {
|
||||
vaultSettings.path().set(Paths.get(path));
|
||||
vaultSettings.winDriveLetter().set(winDriveLetter);
|
||||
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
|
||||
vaultSettings.mountAfterUnlock().set(mountAfterUnlock);
|
||||
vaultSettings.revealAfterMount().set(revealAfterMount);
|
||||
vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath);
|
||||
vaultSettings.individualMountPath().set(individualMountPath);
|
||||
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@DisplayName("Environment Variables Test")
|
||||
class EnvironmentTest {
|
||||
|
||||
private Environment env;
|
||||
|
||||
@BeforeAll
|
||||
static void init() {
|
||||
System.setProperty("user.home", "/home/testuser");
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void initEach() {
|
||||
env = new Environment();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("cryptomator.settingsPath=~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json")
|
||||
public void testSettingsPath() {
|
||||
System.setProperty("cryptomator.settingsPath", "~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json");
|
||||
|
||||
List<Path> result = env.getSettingsPath().collect(Collectors.toList());
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"),
|
||||
Paths.get("/home/testuser/.Cryptomator/settings.json")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("cryptomator.ipcPortPath=~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin")
|
||||
public void testIpcPortPath() {
|
||||
System.setProperty("cryptomator.ipcPortPath", "~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin");
|
||||
|
||||
List<Path> result = env.getIpcPortPath().collect(Collectors.toList());
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"),
|
||||
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("cryptomator.keychainPath=~/AppData/Roaming/Cryptomator/keychain.json")
|
||||
public void testKeychainPath() {
|
||||
System.setProperty("cryptomator.keychainPath", "~/AppData/Roaming/Cryptomator/keychain.json");
|
||||
|
||||
List<Path> result = env.getKeychainPath().collect(Collectors.toList());
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/AppData/Roaming/Cryptomator/keychain.json")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("cryptomator.logDir=/foo/bar")
|
||||
public void testAbsoluteLogDir() {
|
||||
System.setProperty("cryptomator.logDir", "/foo/bar");
|
||||
|
||||
Optional<Path> logDir = env.getLogDir();
|
||||
|
||||
Assertions.assertTrue(logDir.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("cryptomator.logDir=~/foo/bar")
|
||||
public void testRelativeLogDir() {
|
||||
System.setProperty("cryptomator.logDir", "~/foo/bar");
|
||||
|
||||
Optional<Path> logDir = env.getLogDir();
|
||||
|
||||
Assertions.assertTrue(logDir.isPresent());
|
||||
Assertions.assertEquals(Paths.get("/home/testuser/foo/bar"), logDir.get());
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("Path Lists")
|
||||
class SettingsPath {
|
||||
|
||||
@Test
|
||||
@DisplayName("test.path.property=")
|
||||
public void testEmptyList() {
|
||||
System.setProperty("test.path.property", "");
|
||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
||||
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("test.path.property=/foo/bar/test")
|
||||
public void testSingleAbsolutePath() {
|
||||
System.setProperty("test.path.property", "/foo/bar/test");
|
||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
||||
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/foo/bar/test")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("test.path.property=~/test")
|
||||
public void testSingleHomeRelativePath() {
|
||||
System.setProperty("test.path.property", "~/test");
|
||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
||||
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/home/testuser/test")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("test.path.property=~/test:~/test2:/foo/bar/test")
|
||||
public void testMultiplePaths() {
|
||||
System.setProperty("test.path.property", "~/test:~/test2:/foo/bar/test");
|
||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
||||
|
||||
MatcherAssert.assertThat(result, Matchers.hasSize(3));
|
||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"),
|
||||
Paths.get("/home/testuser/test2"),
|
||||
Paths.get("/foo/bar/test")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,10 +8,10 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common;
|
||||
|
||||
import java.util.Comparator;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import java.util.Comparator;
|
||||
|
||||
public class SemVerComparatorTest {
|
||||
|
||||
@@ -21,38 +21,38 @@ public class SemVerComparatorTest {
|
||||
|
||||
@Test
|
||||
public void compareEqualVersions() {
|
||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4")));
|
||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha")));
|
||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4+20170101", "1.23.4+20171231")));
|
||||
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha+20170101", "1.23.4-alpha+20171231")));
|
||||
Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4")));
|
||||
Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha")));
|
||||
Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4+20170101", "1.23.4+20171231")));
|
||||
Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha+20170101", "1.23.4-alpha+20171231")));
|
||||
}
|
||||
|
||||
// newer versions in first argument
|
||||
|
||||
@Test
|
||||
public void compareHigherToLowerVersions() {
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.5", "1.23.4")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.24.4", "1.23.4")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-SNAPSHOT")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-56.78")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-beta", "1.23.4-alpha")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-alpha.1", "1.23.4-alpha")));
|
||||
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-56.79", "1.23.4-56.78")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.5", "1.23.4")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.24.4", "1.23.4")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-SNAPSHOT")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-56.78")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-beta", "1.23.4-alpha")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-alpha.1", "1.23.4-alpha")));
|
||||
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-56.79", "1.23.4-56.78")));
|
||||
}
|
||||
|
||||
// newer versions in second argument
|
||||
|
||||
@Test
|
||||
public void compareLowerToHigherVersions() {
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.5")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.24.4")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23", "1.23.4")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-SNAPSHOT", "1.23.4")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-beta")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha.1")));
|
||||
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4-56.79")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.5")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.24.4")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23", "1.23.4")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-SNAPSHOT", "1.23.4")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-beta")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha.1")));
|
||||
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4-56.79")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import java.io.IOException;
|
||||
|
||||
public class SettingsJsonAdapterTest {
|
||||
|
||||
@@ -21,17 +21,17 @@ public class SettingsJsonAdapterTest {
|
||||
String json = "{\"directories\": [" + vault1Json + "," + vault2Json + "]," //
|
||||
+ "\"checkForUpdatesEnabled\": true,"//
|
||||
+ "\"port\": 8080,"//
|
||||
+ "\"useIpv6\": true,"//
|
||||
+ "\"numTrayNotifications\": 42}";
|
||||
+ "\"numTrayNotifications\": 42,"//
|
||||
+ "\"preferredVolumeImpl\": \"FUSE\"}";
|
||||
|
||||
Settings settings = adapter.fromJson(json);
|
||||
|
||||
Assert.assertTrue(settings.checkForUpdates().get());
|
||||
Assert.assertEquals(2, settings.getDirectories().size());
|
||||
Assert.assertEquals(8080, settings.port().get());
|
||||
// Assert.assertTrue(settings.useIpv6().get()); temporarily ignored
|
||||
Assert.assertEquals(42, settings.numTrayNotifications().get());
|
||||
Assert.assertEquals("dav", settings.preferredGvfsScheme().get());
|
||||
Assertions.assertTrue(settings.checkForUpdates().get());
|
||||
Assertions.assertEquals(2, settings.getDirectories().size());
|
||||
Assertions.assertEquals(8080, settings.port().get());
|
||||
Assertions.assertEquals(42, settings.numTrayNotifications().get());
|
||||
Assertions.assertEquals("dav", settings.preferredGvfsScheme().get());
|
||||
Assertions.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class SettingsTest {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -5,29 +5,29 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
|
||||
public class VaultSettingsJsonAdapterTest {
|
||||
|
||||
private final VaultSettingsJsonAdapter adapter = new VaultSettingsJsonAdapter();
|
||||
|
||||
@Test
|
||||
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));
|
||||
|
||||
VaultSettings vaultSettings = adapter.read(jsonReader);
|
||||
Assert.assertEquals("foo", vaultSettings.getId());
|
||||
Assert.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
||||
Assert.assertEquals("test", vaultSettings.mountName().get());
|
||||
Assert.assertEquals("X", vaultSettings.winDriveLetter().get());
|
||||
Assertions.assertEquals("foo", vaultSettings.getId());
|
||||
Assertions.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
||||
Assertions.assertEquals("test", vaultSettings.mountName().get());
|
||||
Assertions.assertEquals("X", vaultSettings.winDriveLetter().get());
|
||||
Assertions.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class VaultSettingsTest {
|
||||
|
||||
|
||||
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.1</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>
|
||||
@@ -4,12 +4,17 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.3.1</version>
|
||||
<version>1.4.6</version>
|
||||
</parent>
|
||||
<artifactId>keychain</artifactId>
|
||||
<name>System Keychain Access</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
|
||||
@@ -8,17 +8,27 @@ package org.cryptomator.keychain;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.cryptomator.jni.JniModule;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
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 {
|
||||
|
||||
@Provides
|
||||
Optional<MacFunctions> provideOptionalMacFunctions() {
|
||||
return JniFunctions.macFunctions();
|
||||
}
|
||||
|
||||
@Provides
|
||||
Optional<WinFunctions> provideOptionalWinFunctions() {
|
||||
return JniFunctions.winFunctions();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ElementsIntoSet
|
||||
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain) {
|
||||
|
||||
@@ -15,35 +15,35 @@ import org.cryptomator.jni.MacKeychainAccess;
|
||||
|
||||
class MacSystemKeychainAccess implements KeychainAccessStrategy {
|
||||
|
||||
private final MacKeychainAccess keychain;
|
||||
private final Optional<MacFunctions> macFunctions;
|
||||
|
||||
@Inject
|
||||
public MacSystemKeychainAccess(Optional<MacFunctions> macFunctions) {
|
||||
if (macFunctions.isPresent()) {
|
||||
this.keychain = macFunctions.get().keychainAccess();
|
||||
} else {
|
||||
this.keychain = null;
|
||||
}
|
||||
this.macFunctions = macFunctions;
|
||||
}
|
||||
|
||||
private MacKeychainAccess keychain() {
|
||||
return macFunctions.orElseThrow(IllegalStateException::new).keychainAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePassphrase(String key, CharSequence passphrase) {
|
||||
keychain.storePassword(key, passphrase);
|
||||
keychain().storePassword(key, passphrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] loadPassphrase(String key) {
|
||||
return keychain.loadPassword(key);
|
||||
return keychain().loadPassword(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return SystemUtils.IS_OS_MAC_OSX && keychain != null;
|
||||
return SystemUtils.IS_OS_MAC_OSX && macFunctions.isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePassphrase(String key) {
|
||||
keychain.deletePassword(key);
|
||||
keychain().deletePassword(key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,38 +5,6 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import 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.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
@@ -49,6 +17,38 @@ import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.jni.WinDataProtection;
|
||||
import org.cryptomator.jni.WinFunctions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
|
||||
@@ -57,29 +57,18 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
.registerTypeHierarchyAdapter(byte[].class, new ByteArrayJsonAdapter()) //
|
||||
.disableHtmlEscaping().create();
|
||||
|
||||
private final WinDataProtection dataProtection;
|
||||
private final Path keychainPath;
|
||||
private final Optional<WinFunctions> winFunctions;
|
||||
private final List<Path> keychainPaths;
|
||||
private Map<String, KeychainEntry> keychainEntries;
|
||||
|
||||
@Inject
|
||||
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions) {
|
||||
if (winFunctions.isPresent()) {
|
||||
this.dataProtection = winFunctions.get().dataProtection();
|
||||
} else {
|
||||
this.dataProtection = null;
|
||||
}
|
||||
String keychainPathProperty = System.getProperty("cryptomator.keychainPath");
|
||||
if (dataProtection != null && keychainPathProperty == null) {
|
||||
LOG.warn("Windows DataProtection module loaded, but no cryptomator.keychainPath property found.");
|
||||
}
|
||||
if (keychainPathProperty != null) {
|
||||
if (keychainPathProperty.startsWith("~/")) {
|
||||
keychainPathProperty = SystemUtils.USER_HOME + keychainPathProperty.substring(1);
|
||||
}
|
||||
this.keychainPath = FileSystems.getDefault().getPath(keychainPathProperty);
|
||||
} else {
|
||||
this.keychainPath = null;
|
||||
}
|
||||
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions, Environment environment) {
|
||||
this.winFunctions = winFunctions;
|
||||
this.keychainPaths = environment.getKeychainPath().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private WinDataProtection dataProtection() {
|
||||
return winFunctions.orElseThrow(IllegalStateException::new).dataProtection();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,7 +79,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
buf.get(cleartext);
|
||||
KeychainEntry entry = new KeychainEntry();
|
||||
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(cleartext, (byte) 0x00);
|
||||
keychainEntries.put(key, entry);
|
||||
@@ -104,7 +93,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] cleartext = dataProtection.unprotect(entry.ciphertext, entry.salt);
|
||||
byte[] cleartext = dataProtection().unprotect(entry.ciphertext, entry.salt);
|
||||
if (cleartext == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -125,7 +114,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return SystemUtils.IS_OS_WINDOWS && dataProtection != null && keychainPath != null;
|
||||
return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && !keychainPaths.isEmpty();
|
||||
}
|
||||
|
||||
private byte[] generateSalt() {
|
||||
@@ -139,30 +128,44 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
|
||||
private void loadKeychainEntriesIfNeeded() {
|
||||
if (keychainEntries == null) {
|
||||
loadKeychainEntries();
|
||||
}
|
||||
assert keychainEntries != null;
|
||||
}
|
||||
|
||||
private void loadKeychainEntries() {
|
||||
Type type = new TypeToken<Map<String, KeychainEntry>>() {
|
||||
}.getType();
|
||||
try (InputStream in = Files.newInputStream(keychainPath, StandardOpenOption.READ); //
|
||||
Reader reader = new InputStreamReader(in, UTF_8)) {
|
||||
keychainEntries = GSON.fromJson(reader, type);
|
||||
} catch (JsonParseException | NoSuchFileException e) {
|
||||
LOG.info("Creating new keychain at path {}", keychainPath);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
|
||||
for (Path keychainPath : keychainPaths) {
|
||||
Optional<Map<String, KeychainEntry>> keychain = loadKeychainEntries(keychainPath);
|
||||
if (keychain.isPresent()) {
|
||||
keychainEntries = keychain.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (keychainEntries == null) {
|
||||
LOG.info("Unable to load existing keychain file, creating new keychain.");
|
||||
keychainEntries = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Map<String, KeychainEntry>> loadKeychainEntries(Path keychainPath) {
|
||||
LOG.debug("Attempting to load keychain from {}", keychainPath);
|
||||
Type type = new TypeToken<Map<String, KeychainEntry>>() {
|
||||
}.getType();
|
||||
try (InputStream in = Files.newInputStream(keychainPath, StandardOpenOption.READ); //
|
||||
Reader reader = new InputStreamReader(in, UTF_8)) {
|
||||
return Optional.of(GSON.fromJson(reader, type));
|
||||
} catch (NoSuchFileException | JsonParseException e) {
|
||||
return Optional.empty();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveKeychainEntries() {
|
||||
if (keychainPaths.isEmpty()) {
|
||||
throw new IllegalStateException("Can't save keychain if no keychain path is specified.");
|
||||
}
|
||||
saveKeychainEntries(keychainPaths.get(0));
|
||||
}
|
||||
|
||||
private void saveKeychainEntries(Path keychainPath) {
|
||||
try (OutputStream out = Files.newOutputStream(keychainPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
|
||||
Writer writer = new OutputStreamWriter(out, UTF_8)) {
|
||||
Writer writer = new OutputStreamWriter(out, UTF_8)) {
|
||||
GSON.toJson(keychainEntries, writer);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
|
||||
@@ -170,6 +173,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
}
|
||||
|
||||
private static class KeychainEntry {
|
||||
|
||||
@SerializedName("ciphertext")
|
||||
byte[] ciphertext;
|
||||
@SerializedName("salt")
|
||||
|
||||
@@ -5,18 +5,20 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import java.util.Optional;
|
||||
|
||||
public class KeychainModuleTest {
|
||||
|
||||
@Test
|
||||
public void testGetKeychain() {
|
||||
Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().jniModule(new TestJniModule()).keychainModule(new TestKeychainModule()).build().keychainAccess();
|
||||
Assert.assertTrue(keychainAccess.isPresent());
|
||||
Assert.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
|
||||
Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess();
|
||||
Assertions.assertTrue(keychainAccess.isPresent());
|
||||
Assertions.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
|
||||
keychainAccess.get().storePassphrase("test", "asd");
|
||||
Assertions.assertArrayEquals("asd".toCharArray(), keychainAccess.get().loadPassphrase("test"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,11 +5,10 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import java.util.Optional;
|
||||
import dagger.Component;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = KeychainModule.class)
|
||||
|
||||
@@ -5,61 +5,54 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.jni.WinDataProtection;
|
||||
import org.cryptomator.jni.WinFunctions;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class WindowsProtectedKeychainAccessTest {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private Path tmpFile;
|
||||
private Path keychainPath;
|
||||
private WindowsProtectedKeychainAccess keychain;
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException, ReflectiveOperationException {
|
||||
tmpFile = Files.createTempFile("unit-tests", ".tmp");
|
||||
System.setProperty("cryptomator.keychainPath", tmpFile.toAbsolutePath().normalize().toString());
|
||||
@BeforeEach
|
||||
public void setup(@TempDir Path tempDir) throws IOException {
|
||||
keychainPath = tempDir.resolve("keychainfile.tmp");
|
||||
Environment env = Mockito.mock(Environment.class);
|
||||
Mockito.when(env.getKeychainPath()).thenReturn(Stream.of(keychainPath));
|
||||
WinFunctions winFunctions = Mockito.mock(WinFunctions.class);
|
||||
WinDataProtection winDataProtection = Mockito.mock(WinDataProtection.class);
|
||||
Mockito.when(winFunctions.dataProtection()).thenReturn(winDataProtection);
|
||||
Answer<byte[]> answerReturningFirstArg = invocation -> ((byte[]) invocation.getArgument(0)).clone();
|
||||
Mockito.when(winDataProtection.protect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
|
||||
Mockito.when(winDataProtection.unprotect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
|
||||
keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions));
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() throws IOException {
|
||||
Files.deleteIfExists(tmpFile);
|
||||
keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions), env);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreAndLoad() {
|
||||
public void testStoreAndLoad() throws IOException {
|
||||
String storedPw1 = "topSecret";
|
||||
String storedPw2 = "bottomSecret";
|
||||
keychain.storePassphrase("myPassword", storedPw1);
|
||||
keychain.storePassphrase("myOtherPassword", storedPw2);
|
||||
String loadedPw1 = new String(keychain.loadPassphrase("myPassword"));
|
||||
String loadedPw2 = new String(keychain.loadPassphrase("myOtherPassword"));
|
||||
Assert.assertEquals(storedPw1, loadedPw1);
|
||||
Assert.assertEquals(storedPw2, loadedPw2);
|
||||
Assertions.assertEquals(storedPw1, loadedPw1);
|
||||
Assertions.assertEquals(storedPw2, loadedPw2);
|
||||
keychain.deletePassphrase("myPassword");
|
||||
Assert.assertNull(keychain.loadPassphrase("myPassword"));
|
||||
Assert.assertNull(keychain.loadPassphrase("nonExistingPassword"));
|
||||
Assertions.assertNull(keychain.loadPassphrase("myPassword"));
|
||||
Assertions.assertNotNull(keychain.loadPassphrase("myOtherPassword"));
|
||||
Assertions.assertNull(keychain.loadPassphrase("nonExistingPassword"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.3.1</version>
|
||||
<version>1.4.6</version>
|
||||
</parent>
|
||||
<artifactId>launcher</artifactId>
|
||||
<name>Cryptomator Launcher</name>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ApplicationVersion {
|
||||
|
||||
public static String orElse(String other) {
|
||||
return get().orElse(other);
|
||||
}
|
||||
|
||||
public static Optional<String> get() {
|
||||
return Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,55 +5,127 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.logging.DebugMode;
|
||||
import org.cryptomator.logging.LoggerConfiguration;
|
||||
import org.cryptomator.ui.controllers.MainController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@Singleton
|
||||
public class Cryptomator {
|
||||
|
||||
// DaggerCryptomatorComponent gets generated by Dagger.
|
||||
// Run Maven and include target/generated-sources/annotations in your IDE.
|
||||
private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.create();
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
|
||||
static final BlockingQueue<Path> FILE_OPEN_REQUESTS = new ArrayBlockingQueue<>(10);
|
||||
|
||||
public static void main(String[] args) {
|
||||
LOG.info("Starting Cryptomator {} on {} {} ({})", ApplicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||
private final LoggerConfiguration logConfig;
|
||||
private final DebugMode debugMode;
|
||||
private final IpcFactory ipcFactory;
|
||||
private final Optional<String> applicationVersion;
|
||||
|
||||
FileOpenRequestHandler fileOpenRequestHandler = new FileOpenRequestHandler(FILE_OPEN_REQUESTS);
|
||||
try (InterProcessCommunicator communicator = InterProcessCommunicator.start(new IpcProtocolImpl(fileOpenRequestHandler))) {
|
||||
if (communicator.isServer()) {
|
||||
fileOpenRequestHandler.handleLaunchArgs(args);
|
||||
CleanShutdownPerformer.registerShutdownHook();
|
||||
Application.launch(MainApplication.class, args);
|
||||
} else {
|
||||
communicator.handleLaunchArgs(args);
|
||||
LOG.info("Found running application instance. Shutting down.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to initiate inter-process communication.", e);
|
||||
}
|
||||
System.exit(0); // end remaining non-daemon threads.
|
||||
@Inject
|
||||
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion) {
|
||||
this.logConfig = logConfig;
|
||||
this.debugMode = debugMode;
|
||||
this.ipcFactory = ipcFactory;
|
||||
this.applicationVersion = applicationVersion;
|
||||
}
|
||||
|
||||
private static class IpcProtocolImpl implements InterProcessCommunicationProtocol {
|
||||
public static void main(String[] args) {
|
||||
int exitCode = CRYPTOMATOR_COMPONENT.application().run(args);
|
||||
System.exit(exitCode); // end remaining non-daemon threads.
|
||||
}
|
||||
|
||||
private final FileOpenRequestHandler fileOpenRequestHandler;
|
||||
/**
|
||||
* Main entry point of the application launcher.
|
||||
* @param args The arguments passed to this program via {@link #main(String[])}.
|
||||
* @return Nonzero exit code in case of an error.
|
||||
*/
|
||||
private int run(String[] args) {
|
||||
logConfig.init();
|
||||
LOG.info("Starting Cryptomator {} on {} {} ({})", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||
|
||||
// TODO: inject?
|
||||
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler) {
|
||||
this.fileOpenRequestHandler = fileOpenRequestHandler;
|
||||
if (sendArgsToRunningInstance(args)) {
|
||||
LOG.info("Found running application instance. Shutting down...");
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
runGuiApplication();
|
||||
LOG.info("Shutting down...");
|
||||
return 0;
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Terminating due to error", e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args.
|
||||
* If no external process could be reached, the args will be handled by the loopback IPC endpoint.
|
||||
*
|
||||
* @param args Arguments to send to the instance (if possible)
|
||||
* @return <code>true</code> if a different process could be reached, <code>false</code> otherwise.
|
||||
*/
|
||||
private boolean sendArgsToRunningInstance(String[] args) {
|
||||
try (IpcFactory.IpcEndpoint endpoint = ipcFactory.create()) {
|
||||
endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
|
||||
return endpoint.isConnectedToRemote();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to initiate inter-process communication.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the JavaFX application and waits until shutdown is requested.
|
||||
*/
|
||||
private void runGuiApplication() {
|
||||
debugMode.initialize();
|
||||
CleanShutdownPerformer.registerShutdownHook();
|
||||
Application.launch(MainApp.class);
|
||||
// Platform.startup(() -> {
|
||||
// assert Platform.isFxApplicationThread();
|
||||
// FxApplication app = CRYPTOMATOR_COMPONENT.fxApplicationComponent().application();
|
||||
// app.start();
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
// We need a separate FX Application class, until we can use the module system. See https://stackoverflow.com/q/54756176/4014509
|
||||
public static class MainApp extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
LOG.info("JavaFX application started.");
|
||||
primaryStage.setMinWidth(652.0);
|
||||
primaryStage.setMinHeight(440.0);
|
||||
|
||||
FxApplicationComponent fxApplicationComponent = CRYPTOMATOR_COMPONENT.fxApplicationComponent() //
|
||||
.fxApplication(this) //
|
||||
.mainWindow(primaryStage) //
|
||||
.build();
|
||||
|
||||
MainController mainCtrl = fxApplicationComponent.fxmlLoader().load("/fxml/main.fxml");
|
||||
mainCtrl.initStage(primaryStage);
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
LOG.info("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
|
||||
fileOpenRequestHandler.handleLaunchArgs(args);
|
||||
public void stop() {
|
||||
LOG.info("JavaFX application stopped.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import dagger.Component;
|
||||
import org.cryptomator.common.CommonsModule;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.logging.DebugMode;
|
||||
import org.cryptomator.logging.LoggerModule;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class})
|
||||
public interface CryptomatorComponent {
|
||||
|
||||
Cryptomator application();
|
||||
|
||||
FxApplicationComponent.Builder fxApplicationComponent();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.SettingsProvider;
|
||||
import org.cryptomator.ui.model.AppLaunchEvent;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@Module
|
||||
class CryptomatorModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Settings provideSettings(SettingsProvider settingsProvider) {
|
||||
return settingsProvider.get();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("launchEventQueue")
|
||||
static BlockingQueue<AppLaunchEvent> provideFileOpenRequests() {
|
||||
return new ArrayBlockingQueue<>(10);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("applicationVersion")
|
||||
static Optional<String> provideApplicationVersion() {
|
||||
return Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,29 +6,48 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.awt.desktop.OpenFilesEvent;
|
||||
import java.awt.desktop.QuitStrategy;
|
||||
import java.io.File;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.cryptomator.ui.util.EawtApplicationWrapper;
|
||||
import org.cryptomator.ui.model.AppLaunchEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
class FileOpenRequestHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FileOpenRequestHandler.class);
|
||||
private final BlockingQueue<Path> fileOpenRequests;
|
||||
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
|
||||
|
||||
public FileOpenRequestHandler(BlockingQueue<Path> fileOpenRequests) {
|
||||
this.fileOpenRequests = fileOpenRequests;
|
||||
EawtApplicationWrapper.getApplication().ifPresent(app -> {
|
||||
app.setOpenFileHandler(files -> {
|
||||
files.stream().map(File::toPath).forEach(fileOpenRequests::add);
|
||||
});
|
||||
});
|
||||
@Inject
|
||||
public FileOpenRequestHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
|
||||
this.launchEventQueue = launchEventQueue;
|
||||
try {
|
||||
Desktop.getDesktop().setOpenFileHandler(this::openFiles);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
LOG.info("Unable to setOpenFileHandler, probably not supported on this OS.");
|
||||
}
|
||||
}
|
||||
|
||||
private void openFiles(final OpenFilesEvent evt) {
|
||||
Stream<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath);
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen);
|
||||
tryToEnqueueFileOpenRequest(launchEvent);
|
||||
}
|
||||
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
@@ -37,19 +56,22 @@ class FileOpenRequestHandler {
|
||||
|
||||
// visible for testing
|
||||
void handleLaunchArgs(FileSystem fs, String[] args) {
|
||||
for (String arg : args) {
|
||||
Stream<Path> pathsToOpen = Arrays.stream(args).map(str -> {
|
||||
try {
|
||||
Path path = fs.getPath(arg);
|
||||
tryToEnqueueFileOpenRequest(path);
|
||||
return fs.getPath(str);
|
||||
} catch (InvalidPathException e) {
|
||||
LOG.trace("{} not a valid path", arg);
|
||||
LOG.trace("Argument not a valid path: {}", str);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}).filter(Objects::nonNull);
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen);
|
||||
tryToEnqueueFileOpenRequest(launchEvent);
|
||||
}
|
||||
|
||||
private void tryToEnqueueFileOpenRequest(Path path) {
|
||||
if (!fileOpenRequests.offer(path)) {
|
||||
LOG.warn("{} could not be enqueued for opening.", path);
|
||||
|
||||
private void tryToEnqueueFileOpenRequest(AppLaunchEvent launchEvent) {
|
||||
if (!launchEventQueue.offer(launchEvent)) {
|
||||
LOG.warn("Could not enqueue application launch event.", launchEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,19 +5,33 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Subcomponent;
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.logging.DebugMode;
|
||||
import org.cryptomator.ui.controllers.ViewControllerLoader;
|
||||
|
||||
import dagger.Component;
|
||||
import javax.inject.Named;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = LauncherModule.class)
|
||||
interface LauncherComponent {
|
||||
@FxApplicationScoped
|
||||
@Subcomponent(modules = FxApplicationModule.class)
|
||||
interface FxApplicationComponent {
|
||||
|
||||
ViewControllerLoader fxmlLoader();
|
||||
|
||||
DebugMode debugMode();
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
|
||||
@BindsInstance
|
||||
Builder fxApplication(Application application);
|
||||
|
||||
@BindsInstance
|
||||
Builder mainWindow(@Named("mainWindow") Stage mainWindow);
|
||||
|
||||
FxApplicationComponent build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.ui.UiModule;
|
||||
|
||||
import javax.inject.Named;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Module(includes = {UiModule.class})
|
||||
class FxApplicationModule {
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
@Named("shutdownTaskScheduler")
|
||||
Consumer<Runnable> provideShutdownTaskScheduler() {
|
||||
return CleanShutdownPerformer::scheduleShutdownTask;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.rmi.ConnectException;
|
||||
import java.rmi.NotBoundException;
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.RemoteException;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
import java.rmi.registry.Registry;
|
||||
import java.rmi.server.RMIClientSocketFactory;
|
||||
import java.rmi.server.RMIServerSocketFactory;
|
||||
import java.rmi.server.RMISocketFactory;
|
||||
import java.rmi.server.UnicastRemoteObject;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.io.MoreFiles;
|
||||
|
||||
/**
|
||||
* First running application on a machine opens a server socket. Further processes will connect as clients.
|
||||
*/
|
||||
abstract class InterProcessCommunicator implements InterProcessCommunicationProtocol, Closeable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InterProcessCommunicator.class);
|
||||
private static final String RMI_NAME = "Cryptomator";
|
||||
|
||||
public abstract boolean isServer();
|
||||
|
||||
/**
|
||||
* @param endpoint The server-side communication endpoint.
|
||||
* @return Either a client or a server communicator.
|
||||
* @throws IOException In case of communication errors.
|
||||
*/
|
||||
public static InterProcessCommunicator start(InterProcessCommunicationProtocol endpoint) throws IOException {
|
||||
return start(getIpcPortPath(), endpoint);
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
static InterProcessCommunicator start(Path portFilePath, InterProcessCommunicationProtocol endpoint) throws IOException {
|
||||
System.setProperty("java.rmi.server.hostname", "localhost");
|
||||
// try to connect to existing server:
|
||||
int port = readPort(portFilePath);
|
||||
LOG.debug("Connecting to running process on TCP port {}...", port);
|
||||
try {
|
||||
ClientCommunicator client = new ClientCommunicator(port);
|
||||
LOG.trace("Connected to running process.");
|
||||
return client;
|
||||
} catch (ConnectException | NotBoundException e) {
|
||||
LOG.debug("Did not find running process.");
|
||||
// continue
|
||||
}
|
||||
|
||||
// spawn a new server:
|
||||
LOG.trace("Spawning new server...");
|
||||
ServerCommunicator server = new ServerCommunicator(endpoint);
|
||||
writePort(portFilePath, server.getPort());
|
||||
LOG.debug("Server listening on port {}.", server.getPort());
|
||||
return server;
|
||||
}
|
||||
|
||||
private static Path getIpcPortPath() {
|
||||
final String settingsPathProperty = System.getProperty("cryptomator.ipcPortPath");
|
||||
if (settingsPathProperty == null) {
|
||||
LOG.warn("System property cryptomator.ipcPortPath not set.");
|
||||
return Paths.get("ipcPort.tmp");
|
||||
} else {
|
||||
return Paths.get(replaceHomeDir(settingsPathProperty));
|
||||
}
|
||||
}
|
||||
|
||||
private static String replaceHomeDir(String path) {
|
||||
if (path.startsWith("~/")) {
|
||||
return SystemUtils.USER_HOME + path.substring(1);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClientCommunicator extends InterProcessCommunicator {
|
||||
|
||||
private final IpcProtocolRemote remote;
|
||||
|
||||
private ClientCommunicator(int port) throws ConnectException, NotBoundException, RemoteException {
|
||||
if (port == 0) {
|
||||
throw new ConnectException("Can not connect to port 0.");
|
||||
}
|
||||
Registry registry = LocateRegistry.getRegistry("localhost", port);
|
||||
this.remote = (IpcProtocolRemote) registry.lookup(RMI_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
try {
|
||||
remote.handleLaunchArgs(args);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ServerCommunicator extends InterProcessCommunicator {
|
||||
|
||||
private final ServerSocket socket;
|
||||
private final Registry registry;
|
||||
private final IpcProtocolRemoteImpl remote;
|
||||
|
||||
private ServerCommunicator(InterProcessCommunicationProtocol delegate) throws IOException {
|
||||
this.socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost"));
|
||||
RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory();
|
||||
SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket);
|
||||
this.registry = LocateRegistry.createRegistry(0, csf, ssf);
|
||||
this.remote = new IpcProtocolRemoteImpl(delegate);
|
||||
UnicastRemoteObject.exportObject(remote, 0);
|
||||
registry.rebind(RMI_NAME, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
throw new UnsupportedOperationException("Server doesn't invoke methods.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isServer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getPort() {
|
||||
return socket.getLocalPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
registry.unbind(RMI_NAME);
|
||||
UnicastRemoteObject.unexportObject(remote, true);
|
||||
socket.close();
|
||||
LOG.debug("Server shut down.");
|
||||
} catch (NotBoundException | IOException e) {
|
||||
LOG.warn("Failed to close IPC Server.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static interface IpcProtocolRemote extends Remote {
|
||||
void handleLaunchArgs(String[] args) throws RemoteException;
|
||||
}
|
||||
|
||||
private static class IpcProtocolRemoteImpl implements IpcProtocolRemote {
|
||||
|
||||
private final InterProcessCommunicationProtocol delegate;
|
||||
|
||||
protected IpcProtocolRemoteImpl(InterProcessCommunicationProtocol delegate) throws RemoteException {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
delegate.handleLaunchArgs(args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns the same pre-constructed server socket.
|
||||
*/
|
||||
private static class SingletonServerSocketFactory implements RMIServerSocketFactory {
|
||||
|
||||
private final ServerSocket socket;
|
||||
|
||||
public SingletonServerSocketFactory(ServerSocket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ServerSocket createServerSocket(int port) throws IOException {
|
||||
if (port != 0) {
|
||||
throw new IllegalArgumentException("This factory doesn't support specific ports.");
|
||||
}
|
||||
return this.socket;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static int readPort(Path path) throws IOException {
|
||||
if (Files.notExists(path)) {
|
||||
return 0;
|
||||
}
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||
try (ReadableByteChannel ch = Files.newByteChannel(path, StandardOpenOption.READ)) {
|
||||
if (ch.read(buf) == Integer.BYTES) {
|
||||
buf.flip();
|
||||
return buf.getInt();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void writePort(Path path, int port) throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||
buf.putInt(port);
|
||||
buf.flip();
|
||||
MoreFiles.createParentDirectories(path);
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import com.google.common.io.MoreFiles;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.rmi.NotBoundException;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
import java.rmi.registry.Registry;
|
||||
import java.rmi.server.RMIClientSocketFactory;
|
||||
import java.rmi.server.RMIServerSocketFactory;
|
||||
import java.rmi.server.RMISocketFactory;
|
||||
import java.rmi.server.UnicastRemoteObject;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* First running application on a machine opens a server socket. Further processes will connect as clients.
|
||||
*/
|
||||
@Singleton
|
||||
class IpcFactory {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(IpcFactory.class);
|
||||
private static final String RMI_NAME = "Cryptomator";
|
||||
|
||||
private final List<Path> portFilePaths;
|
||||
private final IpcProtocolImpl ipcHandler;
|
||||
|
||||
@Inject
|
||||
public IpcFactory(Environment env, IpcProtocolImpl ipcHandler) {
|
||||
this.portFilePaths = env.getIpcPortPath().collect(Collectors.toUnmodifiableList());
|
||||
this.ipcHandler = ipcHandler;
|
||||
}
|
||||
|
||||
public IpcEndpoint create() {
|
||||
if (portFilePaths.isEmpty()) {
|
||||
LOG.warn("No IPC port file path specified.");
|
||||
return new SelfEndpoint(ipcHandler);
|
||||
} else {
|
||||
System.setProperty("java.rmi.server.hostname", "localhost");
|
||||
return attemptClientConnection().or(this::createServerEndpoint).orElseGet(() -> new SelfEndpoint(ipcHandler));
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<IpcEndpoint> attemptClientConnection() {
|
||||
for (Path portFilePath : portFilePaths) {
|
||||
try {
|
||||
int port = readPort(portFilePath);
|
||||
LOG.debug("[Client] Connecting to port {}...", port);
|
||||
Registry registry = LocateRegistry.getRegistry("localhost", port, new ClientSocketFactory());
|
||||
IpcProtocol remoteInterface = (IpcProtocol) registry.lookup(RMI_NAME);
|
||||
return Optional.of(new ClientEndpoint(remoteInterface));
|
||||
} catch (NotBoundException | IOException e) {
|
||||
LOG.debug("[Client] Failed to connect.");
|
||||
// continue with next portFilePath...
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private int readPort(Path portFilePath) throws IOException {
|
||||
try (ReadableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.READ)) {
|
||||
LOG.debug("[Client] Reading IPC port from {}", portFilePath);
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||
if (ch.read(buf) == Integer.BYTES) {
|
||||
buf.flip();
|
||||
return buf.getInt();
|
||||
} else {
|
||||
throw new IOException("Invalid IPC port file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<IpcEndpoint> createServerEndpoint() {
|
||||
assert !portFilePaths.isEmpty();
|
||||
Path portFilePath = portFilePaths.get(0);
|
||||
try {
|
||||
ServerSocket socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost"));
|
||||
RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory();
|
||||
SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket);
|
||||
Registry registry = LocateRegistry.createRegistry(0, csf, ssf);
|
||||
UnicastRemoteObject.exportObject(ipcHandler, 0);
|
||||
registry.rebind(RMI_NAME, ipcHandler);
|
||||
writePort(portFilePath, socket.getLocalPort());
|
||||
return Optional.of(new ServerEndpoint(ipcHandler, socket, registry, portFilePath));
|
||||
} catch (IOException e) {
|
||||
LOG.warn("[Server] Failed to create IPC server.", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void writePort(Path portFilePath, int port) throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||
buf.putInt(port);
|
||||
buf.flip();
|
||||
MoreFiles.createParentDirectories(portFilePath);
|
||||
try (WritableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
if (ch.write(buf) != Integer.BYTES) {
|
||||
throw new IOException("Did not write expected number of bytes.");
|
||||
}
|
||||
}
|
||||
LOG.debug("[Server] Wrote IPC port {} to {}", port, portFilePath);
|
||||
}
|
||||
|
||||
interface IpcEndpoint extends Closeable {
|
||||
|
||||
boolean isConnectedToRemote();
|
||||
|
||||
IpcProtocol getRemote();
|
||||
|
||||
}
|
||||
|
||||
static class SelfEndpoint implements IpcEndpoint {
|
||||
|
||||
protected final IpcProtocol remoteObject;
|
||||
|
||||
SelfEndpoint(IpcProtocol remoteObject) {
|
||||
this.remoteObject = remoteObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnectedToRemote() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IpcProtocol getRemote() {
|
||||
return remoteObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
static class ClientEndpoint implements IpcEndpoint {
|
||||
|
||||
private final IpcProtocol remoteInterface;
|
||||
|
||||
public ClientEndpoint(IpcProtocol remoteInterface) {
|
||||
this.remoteInterface = remoteInterface;
|
||||
}
|
||||
|
||||
public IpcProtocol getRemote() {
|
||||
return remoteInterface;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnectedToRemote() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ServerEndpoint extends SelfEndpoint {
|
||||
|
||||
private final ServerSocket socket;
|
||||
private final Registry registry;
|
||||
private final Path portFilePath;
|
||||
|
||||
private ServerEndpoint(IpcProtocol remoteObject, ServerSocket socket, Registry registry, Path portFilePath) {
|
||||
super(remoteObject);
|
||||
this.socket = socket;
|
||||
this.registry = registry;
|
||||
this.portFilePath = portFilePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
registry.unbind(RMI_NAME);
|
||||
UnicastRemoteObject.unexportObject(remoteObject, true);
|
||||
socket.close();
|
||||
Files.deleteIfExists(portFilePath);
|
||||
LOG.debug("[Server] Shut down");
|
||||
} catch (NotBoundException | IOException e) {
|
||||
LOG.warn("[Server] Error shutting down:", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns the same pre-constructed server socket.
|
||||
*/
|
||||
private static class SingletonServerSocketFactory implements RMIServerSocketFactory {
|
||||
|
||||
private final ServerSocket socket;
|
||||
|
||||
public SingletonServerSocketFactory(ServerSocket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ServerSocket createServerSocket(int port) throws IOException {
|
||||
if (port != 0) {
|
||||
throw new IllegalArgumentException("This factory doesn't support specific ports.");
|
||||
}
|
||||
return this.socket;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates client sockets with short timeouts.
|
||||
*/
|
||||
private static class ClientSocketFactory implements RMIClientSocketFactory {
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
return new SocketWithFixedTimeout(host, port, 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class SocketWithFixedTimeout extends Socket {
|
||||
|
||||
public SocketWithFixedTimeout(String host, int port, int timeoutInMs) throws UnknownHostException, IOException {
|
||||
super(host, port);
|
||||
super.setSoTimeout(timeoutInMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setSoTimeout(int timeout) throws SocketException {
|
||||
// do nothing, timeout is fixed
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,11 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
public interface InterProcessCommunicationProtocol {
|
||||
void handleLaunchArgs(String[] args);
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.RemoteException;
|
||||
|
||||
interface IpcProtocol extends Remote {
|
||||
|
||||
void handleLaunchArgs(String[] args) throws RemoteException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Singleton
|
||||
class IpcProtocolImpl implements IpcProtocol {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(IpcProtocolImpl.class);
|
||||
|
||||
private final FileOpenRequestHandler fileOpenRequestHandler;
|
||||
|
||||
@Inject
|
||||
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler) {
|
||||
this.fileOpenRequestHandler = fileOpenRequestHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
LOG.info("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
|
||||
fileOpenRequestHandler.handleLaunchArgs(args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.ui.UiModule;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@Module(includes = {UiModule.class})
|
||||
class LauncherModule {
|
||||
|
||||
private final Application application;
|
||||
private final Stage mainWindow;
|
||||
|
||||
public LauncherModule(Application application, Stage mainWindow) {
|
||||
this.application = application;
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Application provideApplication() {
|
||||
return application;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("applicationVersion")
|
||||
Optional<String> provideApplicationVersion() {
|
||||
return ApplicationVersion.get();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("mainWindow")
|
||||
Stage provideMainWindow() {
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("fileOpenRequests")
|
||||
BlockingQueue<Path> provideFileOpenRequests() {
|
||||
return Cryptomator.FILE_OPEN_REQUESTS;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("shutdownTaskScheduler")
|
||||
Consumer<Runnable> provideShutdownTaskScheduler() {
|
||||
return CleanShutdownPerformer::scheduleShutdownTask;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.cryptomator.ui.controllers.MainController;
|
||||
import org.slf4j.Logger;
|
||||
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 {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainApplication.class);
|
||||
private Stage primaryStage;
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
LOG.info("JavaFX application started.");
|
||||
this.primaryStage = primaryStage;
|
||||
setupFXMLClassLoader();
|
||||
|
||||
LauncherModule launcherModule = new LauncherModule(this, primaryStage);
|
||||
LauncherComponent launcherComponent = DaggerLauncherComponent.builder() //
|
||||
.launcherModule(launcherModule) //
|
||||
.build();
|
||||
|
||||
launcherComponent.debugMode().initialize();
|
||||
|
||||
MainController mainCtrl = launcherComponent.fxmlLoader().load("/fxml/main.fxml");
|
||||
mainCtrl.initStage(primaryStage);
|
||||
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
assert primaryStage != null;
|
||||
primaryStage.hide();
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,77 +5,55 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.logging;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.slf4j.ILoggerFactory;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class DebugMode {
|
||||
|
||||
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(DebugMode.class);
|
||||
|
||||
private static final Collection<LoggerUpgrade> LOGGER_UPGRADES = asList( //
|
||||
loggerUpgrade(org.slf4j.Logger.ROOT_LOGGER_NAME, Level.INFO), //
|
||||
loggerUpgrade("org.cryptomator", Level.TRACE), //
|
||||
loggerUpgrade("org.eclipse.jetty.server.HttpChannel", Level.DEBUG) //
|
||||
);
|
||||
|
||||
private final Settings settings;
|
||||
private final LoggerContext context;
|
||||
|
||||
@Inject
|
||||
public DebugMode(Settings settings) {
|
||||
public DebugMode(Settings settings, LoggerContext context) {
|
||||
this.settings = settings;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
if (settings.debugMode().get()) {
|
||||
enable();
|
||||
LOG.debug("Debug mode initialized");
|
||||
}
|
||||
setLogLevels(settings.debugMode().get());
|
||||
settings.debugMode().addListener(this::logLevelChanged);
|
||||
}
|
||||
|
||||
private void enable() {
|
||||
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
||||
if (loggerFactory instanceof LoggerContext) {
|
||||
LoggerContext context = (LoggerContext) loggerFactory;
|
||||
LOGGER_UPGRADES.forEach(loggerUpgrade -> loggerUpgrade.execute(context));
|
||||
private void logLevelChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observable, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) {
|
||||
setLogLevels(newValue);
|
||||
}
|
||||
|
||||
private void setLogLevels(boolean debugMode) {
|
||||
if (debugMode) {
|
||||
setLogLevels(LoggerModule.DEBUG_LOG_LEVELS);
|
||||
LOG.debug("Debug mode enabled");
|
||||
} else {
|
||||
LOG.warn("SLF4J not bound to Logback.");
|
||||
LOG.debug("Debug mode disabled");
|
||||
setLogLevels(LoggerModule.DEFAULT_LOG_LEVELS);
|
||||
}
|
||||
}
|
||||
|
||||
private static LoggerUpgrade loggerUpgrade(String loggerName, Level minLevel) {
|
||||
return new LoggerUpgrade(loggerName, minLevel);
|
||||
}
|
||||
|
||||
private static class LoggerUpgrade {
|
||||
|
||||
private final Level level;
|
||||
private final String loggerName;
|
||||
|
||||
public LoggerUpgrade(String loggerName, Level minLevel) {
|
||||
this.loggerName = loggerName;
|
||||
this.level = minLevel;
|
||||
private void setLogLevels(Map<String, Level> logLevels) {
|
||||
for (Map.Entry<String, Level> loglevel : logLevels.entrySet()) {
|
||||
Logger logger = context.getLogger(loglevel.getKey());
|
||||
logger.setLevel(loglevel.getValue());
|
||||
}
|
||||
|
||||
public void execute(LoggerContext context) {
|
||||
Logger logger = context.getLogger(loggerName);
|
||||
if (logger != null && logger.getEffectiveLevel().isGreaterOrEqual(level)) {
|
||||
logger.setLevel(level);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.cryptomator.logging;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
import ch.qos.logback.core.hook.DelayingShutdownHook;
|
||||
import ch.qos.logback.core.util.Duration;
|
||||
import org.cryptomator.common.Environment;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class LoggerConfiguration {
|
||||
|
||||
private static final double SHUTDOWN_DELAY_MS = 100;
|
||||
|
||||
private final LoggerContext context;
|
||||
private final Environment environment;
|
||||
private final Appender<ILoggingEvent> stdout;
|
||||
private final Appender<ILoggingEvent> upgrade;
|
||||
private final Appender<ILoggingEvent> file;
|
||||
|
||||
@Inject
|
||||
LoggerConfiguration(LoggerContext context, //
|
||||
Environment environment, //
|
||||
@Named("stdoutAppender") Appender<ILoggingEvent> stdout, //
|
||||
@Named("upgradeAppender") Appender<ILoggingEvent> upgrade, //
|
||||
@Named("fileAppender") Appender<ILoggingEvent> file) {
|
||||
this.context = context;
|
||||
this.environment = environment;
|
||||
this.stdout = stdout;
|
||||
this.upgrade = upgrade;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
if (environment.useCustomLogbackConfig()) {
|
||||
Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
root.info("Using external logback configuration file.");
|
||||
} else {
|
||||
context.reset();
|
||||
|
||||
// configure loggers:
|
||||
for (Map.Entry<String, Level> loglevel : LoggerModule.DEFAULT_LOG_LEVELS.entrySet()) {
|
||||
Logger logger = context.getLogger(loglevel.getKey());
|
||||
logger.setLevel(loglevel.getValue());
|
||||
logger.setAdditive(false);
|
||||
logger.addAppender(stdout);
|
||||
logger.addAppender(file);
|
||||
}
|
||||
|
||||
// configure upgrade logger:
|
||||
Logger upgrades = context.getLogger("org.cryptomator.ui.model.upgrade");
|
||||
upgrades.setLevel(Level.DEBUG);
|
||||
upgrades.addAppender(stdout);
|
||||
upgrades.addAppender(upgrade);
|
||||
upgrades.setAdditive(false);
|
||||
|
||||
// add shutdown hook
|
||||
DelayingShutdownHook shutdownHook = new DelayingShutdownHook();
|
||||
shutdownHook.setContext(context);
|
||||
shutdownHook.setDelay(Duration.buildByMilliseconds(SHUTDOWN_DELAY_MS));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package org.cryptomator.logging;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
import ch.qos.logback.core.ConsoleAppender;
|
||||
import ch.qos.logback.core.FileAppender;
|
||||
import ch.qos.logback.core.helpers.NOPAppender;
|
||||
import ch.qos.logback.core.hook.DelayingShutdownHook;
|
||||
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
|
||||
import ch.qos.logback.core.rolling.RollingFileAppender;
|
||||
import ch.qos.logback.core.util.Duration;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.slf4j.ILoggerFactory;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
@Module
|
||||
public class LoggerModule {
|
||||
|
||||
private static final String UPGRADE_FILENAME = "upgrade.log";
|
||||
private static final String LOGFILE_NAME = "cryptomator0.log";
|
||||
private static final String LOGFILE_ROLLING_PATTERN = "cryptomator%i.log";
|
||||
private static final int LOGFILE_ROLLING_MIN = 1;
|
||||
private static final int LOGFILE_ROLLING_MAX = 9;
|
||||
private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
|
||||
static final Map<String, Level> DEFAULT_LOG_LEVELS = Map.of( //
|
||||
Logger.ROOT_LOGGER_NAME, Level.INFO, //
|
||||
"org.cryptomator", Level.INFO //
|
||||
);
|
||||
static final Map<String, Level> DEBUG_LOG_LEVELS = Map.of( //
|
||||
Logger.ROOT_LOGGER_NAME, Level.INFO, //
|
||||
"org.cryptomator", Level.TRACE //
|
||||
);
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static LoggerContext provideLoggerContext() {
|
||||
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
|
||||
if (loggerFactory instanceof LoggerContext) {
|
||||
return (LoggerContext) loggerFactory;
|
||||
} else {
|
||||
throw new IllegalStateException("SLF4J not bound to Logback.");
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static PatternLayoutEncoder provideLayoutEncoder(LoggerContext context) {
|
||||
PatternLayoutEncoder ple = new PatternLayoutEncoder();
|
||||
ple.setPattern(LOG_PATTERN);
|
||||
ple.setContext(context);
|
||||
ple.start();
|
||||
return ple;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("stdoutAppender")
|
||||
static Appender<ILoggingEvent> provideStdoutAppender(LoggerContext context, PatternLayoutEncoder encoder) {
|
||||
ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>();
|
||||
appender.setContext(context);
|
||||
appender.setEncoder(encoder);
|
||||
appender.start();
|
||||
return appender;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("fileAppender")
|
||||
static Appender<ILoggingEvent> provideFileAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
|
||||
if (environment.getLogDir().isPresent()) {
|
||||
Path logDir = environment.getLogDir().get();
|
||||
RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
|
||||
appender.setContext(context);
|
||||
appender.setFile(logDir.resolve(LOGFILE_NAME).toString());
|
||||
appender.setEncoder(encoder);
|
||||
LaunchBasedTriggeringPolicy triggeringPolicy = new LaunchBasedTriggeringPolicy();
|
||||
triggeringPolicy.setContext(context);
|
||||
triggeringPolicy.start();
|
||||
appender.setTriggeringPolicy(triggeringPolicy);
|
||||
FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
|
||||
rollingPolicy.setContext(context);
|
||||
rollingPolicy.setFileNamePattern(logDir.resolve(LOGFILE_ROLLING_PATTERN).toString());
|
||||
rollingPolicy.setMinIndex(LOGFILE_ROLLING_MIN);
|
||||
rollingPolicy.setMaxIndex(LOGFILE_ROLLING_MAX);
|
||||
rollingPolicy.setParent(appender);
|
||||
rollingPolicy.start();
|
||||
appender.setRollingPolicy(rollingPolicy);
|
||||
appender.start();
|
||||
return appender;
|
||||
} else {
|
||||
NOPAppender appender = new NOPAppender<>();
|
||||
appender.setContext(context);
|
||||
return appender;
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("upgradeAppender")
|
||||
static Appender<ILoggingEvent> provideUpgradeAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
|
||||
if (environment.getLogDir().isPresent()) {
|
||||
FileAppender<ILoggingEvent> appender = new FileAppender<>();
|
||||
appender.setFile(environment.getLogDir().get().resolve(UPGRADE_FILENAME).toString());
|
||||
appender.setContext(context);
|
||||
appender.setEncoder(encoder);
|
||||
appender.start();
|
||||
return appender;
|
||||
} else {
|
||||
NOPAppender appender = new NOPAppender<>();
|
||||
appender.setContext(context);
|
||||
return appender;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -5,73 +5,69 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.cryptomator.ui.model.AppLaunchEvent;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.spi.FileSystemProvider;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class FileOpenRequestHandlerTest {
|
||||
|
||||
@Test
|
||||
public void testOpenArgsWithCorrectPaths() throws IOException {
|
||||
Path p1 = Mockito.mock(Path.class);
|
||||
Path p2 = Mockito.mock(Path.class);
|
||||
FileSystem fs = Mockito.mock(FileSystem.class);
|
||||
FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
|
||||
BasicFileAttributes attrs = Mockito.mock(BasicFileAttributes.class);
|
||||
Mockito.when(p1.getFileSystem()).thenReturn(fs);
|
||||
Mockito.when(p2.getFileSystem()).thenReturn(fs);
|
||||
Mockito.when(fs.provider()).thenReturn(provider);
|
||||
Mockito.when(fs.getPath(Mockito.anyString())).thenReturn(p1, p2);
|
||||
Mockito.when(provider.readAttributes(Mockito.any(), Mockito.eq(BasicFileAttributes.class))).thenReturn(attrs);
|
||||
Mockito.when(attrs.isRegularFile()).thenReturn(true);
|
||||
private FileOpenRequestHandler inTest;
|
||||
private BlockingQueue<AppLaunchEvent> queue;
|
||||
|
||||
BlockingQueue<Path> queue = new ArrayBlockingQueue<>(10);
|
||||
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
|
||||
handler.handleLaunchArgs(fs, new String[] {"foo", "bar"});
|
||||
|
||||
Assert.assertEquals(p1, queue.poll());
|
||||
Assert.assertEquals(p2, queue.poll());
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
queue = new ArrayBlockingQueue<>(1);
|
||||
inTest = new FileOpenRequestHandler(queue);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("./cryptomator.exe foo bar")
|
||||
public void testOpenArgsWithCorrectPaths() throws IOException {
|
||||
inTest.handleLaunchArgs(new String[]{"foo", "bar"});
|
||||
|
||||
AppLaunchEvent evt = queue.poll();
|
||||
Assertions.assertNotNull(evt);
|
||||
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
|
||||
MatcherAssert.assertThat(paths, CoreMatchers.hasItems(Paths.get("foo"), Paths.get("bar")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("./cryptomator.exe foo (with 'foo' being an invalid path)")
|
||||
public void testOpenArgsWithIncorrectPaths() throws IOException {
|
||||
FileSystem fs = Mockito.mock(FileSystem.class);
|
||||
Mockito.when(fs.getPath(Mockito.anyString())).thenThrow(new InvalidPathException("foo", "foo is not a path"));
|
||||
Mockito.when(fs.getPath("foo")).thenThrow(new InvalidPathException("foo", "foo is not a path"));
|
||||
inTest.handleLaunchArgs(fs, new String[]{"foo"});
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
BlockingQueue<Path> queue = Mockito.mock(BlockingQueue.class);
|
||||
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
|
||||
handler.handleLaunchArgs(fs, new String[] {"foo"});
|
||||
|
||||
Mockito.verifyNoMoreInteractions(queue);
|
||||
AppLaunchEvent evt = queue.poll();
|
||||
Assertions.assertNotNull(evt);
|
||||
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
|
||||
Assertions.assertTrue(paths.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("./cryptomator.exe foo (with full event queue)")
|
||||
public void testOpenArgsWithFullQueue() throws IOException {
|
||||
Path p = Mockito.mock(Path.class);
|
||||
FileSystem fs = Mockito.mock(FileSystem.class);
|
||||
FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
|
||||
BasicFileAttributes attrs = Mockito.mock(BasicFileAttributes.class);
|
||||
Mockito.when(p.getFileSystem()).thenReturn(fs);
|
||||
Mockito.when(fs.provider()).thenReturn(provider);
|
||||
Mockito.when(fs.getPath(Mockito.anyString())).thenReturn(p);
|
||||
Mockito.when(provider.readAttributes(Mockito.eq(p), Mockito.eq(BasicFileAttributes.class))).thenReturn(attrs);
|
||||
Mockito.when(attrs.isRegularFile()).thenReturn(true);
|
||||
queue.add(new AppLaunchEvent(Stream.empty()));
|
||||
Assumptions.assumeTrue(queue.remainingCapacity() == 0);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
BlockingQueue<Path> queue = Mockito.mock(BlockingQueue.class);
|
||||
Mockito.when(queue.offer(Mockito.any())).thenReturn(false);
|
||||
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
|
||||
handler.handleLaunchArgs(fs, new String[] {"foo"});
|
||||
inTest.handleLaunchArgs(new String[]{"foo"});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.spi.FileSystemProvider;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class InterProcessCommunicatorTest {
|
||||
|
||||
Path portFilePath = Mockito.mock(Path.class);
|
||||
Path portFileParentPath = Mockito.mock(Path.class);
|
||||
BasicFileAttributes portFileParentPathAttrs = Mockito.mock(BasicFileAttributes.class);
|
||||
FileSystem fs = Mockito.mock(FileSystem.class);
|
||||
FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
|
||||
SeekableByteChannel portFileChannel = Mockito.mock(SeekableByteChannel.class);
|
||||
AtomicInteger port = new AtomicInteger(-1);
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
Mockito.when(portFilePath.getFileSystem()).thenReturn(fs);
|
||||
Mockito.when(portFilePath.toAbsolutePath()).thenReturn(portFilePath);
|
||||
Mockito.when(portFilePath.normalize()).thenReturn(portFilePath);
|
||||
Mockito.when(portFilePath.getParent()).thenReturn(portFileParentPath);
|
||||
Mockito.when(portFileParentPath.getFileSystem()).thenReturn(fs);
|
||||
Mockito.when(fs.provider()).thenReturn(provider);
|
||||
Mockito.when(provider.readAttributes(portFileParentPath, BasicFileAttributes.class)).thenReturn(portFileParentPathAttrs);
|
||||
Mockito.when(portFileParentPathAttrs.isDirectory()).thenReturn(false, true); // Guava's MoreFiles will check if dir exists before attempting to create them.
|
||||
Mockito.when(provider.newByteChannel(Mockito.eq(portFilePath), Mockito.any(), Mockito.any())).thenReturn(portFileChannel);
|
||||
Mockito.when(portFileChannel.read(Mockito.any())).then(invocation -> {
|
||||
ByteBuffer buf = invocation.getArgument(0);
|
||||
buf.putInt(port.get());
|
||||
return Integer.BYTES;
|
||||
});
|
||||
Mockito.when(portFileChannel.write(Mockito.any())).then(invocation -> {
|
||||
ByteBuffer buf = invocation.getArgument(0);
|
||||
port.set(buf.getInt());
|
||||
return Integer.BYTES;
|
||||
});
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testStartWithDummyPort1() throws IOException {
|
||||
port.set(0);
|
||||
InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
|
||||
try (InterProcessCommunicator result = InterProcessCommunicator.start(portFilePath, protocol)) {
|
||||
Assert.assertTrue(result.isServer());
|
||||
Mockito.verify(provider).createDirectory(portFileParentPath);
|
||||
Mockito.verifyZeroInteractions(protocol);
|
||||
result.handleLaunchArgs(new String[] {"foo"});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartWithDummyPort2() throws IOException {
|
||||
Mockito.doThrow(new NoSuchFileException("port file")).when(provider).checkAccess(portFilePath);
|
||||
|
||||
InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
|
||||
try (InterProcessCommunicator result = InterProcessCommunicator.start(portFilePath, protocol)) {
|
||||
Assert.assertTrue(result.isServer());
|
||||
Mockito.verify(provider).createDirectory(portFileParentPath);
|
||||
Mockito.verifyZeroInteractions(protocol);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterProcessCommunication() throws IOException, InterruptedException {
|
||||
port.set(-1);
|
||||
InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
|
||||
try (InterProcessCommunicator result1 = InterProcessCommunicator.start(portFilePath, protocol)) {
|
||||
Assert.assertTrue(result1.isServer());
|
||||
Mockito.verify(provider, Mockito.times(1)).createDirectory(portFileParentPath);
|
||||
Mockito.verifyZeroInteractions(protocol);
|
||||
|
||||
try (InterProcessCommunicator result2 = InterProcessCommunicator.start(portFilePath, null)) {
|
||||
Assert.assertFalse(result2.isServer());
|
||||
Mockito.verify(provider, Mockito.times(1)).createDirectory(portFileParentPath);
|
||||
Assert.assertNotSame(result1, result2);
|
||||
|
||||
result2.handleLaunchArgs(new String[] {"foo"});
|
||||
Mockito.verify(protocol).handleLaunchArgs(new String[] {"foo"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.launcher.IpcFactory.IpcEndpoint;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class IpcFactoryTest {
|
||||
|
||||
private Environment environment = Mockito.mock(Environment.class);
|
||||
private IpcProtocolImpl protocolHandler = Mockito.mock(IpcProtocolImpl.class);
|
||||
|
||||
@Test
|
||||
@DisplayName("Wihout IPC port files")
|
||||
public void testNoIpcWithoutPortFile() throws IOException {
|
||||
IpcFactory inTest = new IpcFactory(environment, protocolHandler);
|
||||
|
||||
Mockito.when(environment.getIpcPortPath()).thenReturn(Stream.empty());
|
||||
try (IpcEndpoint endpoint1 = inTest.create()) {
|
||||
Assertions.assertEquals(IpcFactory.SelfEndpoint.class, endpoint1.getClass());
|
||||
Assertions.assertFalse(endpoint1.isConnectedToRemote());
|
||||
Assertions.assertSame(protocolHandler, endpoint1.getRemote());
|
||||
try (IpcEndpoint endpoint2 = inTest.create()) {
|
||||
Assertions.assertEquals(IpcFactory.SelfEndpoint.class, endpoint2.getClass());
|
||||
Assertions.assertNotSame(endpoint1, endpoint2);
|
||||
Assertions.assertFalse(endpoint2.isConnectedToRemote());
|
||||
Assertions.assertSame(protocolHandler, endpoint2.getRemote());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Start server and client with port shared via file")
|
||||
public void testInterProcessCommunication(@TempDir Path tmpDir) throws IOException {
|
||||
Path portFile = tmpDir.resolve("testPortFile");
|
||||
Mockito.when(environment.getIpcPortPath()).thenReturn(Stream.of(portFile));
|
||||
IpcFactory inTest = new IpcFactory(environment, protocolHandler);
|
||||
|
||||
Assertions.assertFalse(Files.exists(portFile));
|
||||
try (IpcEndpoint endpoint1 = inTest.create()) {
|
||||
Assertions.assertEquals(IpcFactory.ServerEndpoint.class, endpoint1.getClass());
|
||||
Assertions.assertFalse(endpoint1.isConnectedToRemote());
|
||||
Assertions.assertTrue(Files.exists(portFile));
|
||||
Assertions.assertSame(protocolHandler, endpoint1.getRemote());
|
||||
Mockito.verifyZeroInteractions(protocolHandler);
|
||||
try (IpcEndpoint endpoint2 = inTest.create()) {
|
||||
Assertions.assertEquals(IpcFactory.ClientEndpoint.class, endpoint2.getClass());
|
||||
Assertions.assertNotSame(endpoint1, endpoint2);
|
||||
Assertions.assertTrue(endpoint2.isConnectedToRemote());
|
||||
Assertions.assertNotSame(protocolHandler, endpoint2.getRemote());
|
||||
Mockito.verifyZeroInteractions(protocolHandler);
|
||||
endpoint2.getRemote().handleLaunchArgs(new String[] {"foo"});
|
||||
Mockito.verify(protocolHandler).handleLaunchArgs(new String[] {"foo"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,12 +5,12 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.logging;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class LaunchBasedTriggeringPolicyTest {
|
||||
|
||||
@Test
|
||||
@@ -21,15 +21,15 @@ public class LaunchBasedTriggeringPolicyTest {
|
||||
|
||||
// 1st invocation
|
||||
boolean triggered = policy.isTriggeringEvent(activeFile, event);
|
||||
Assert.assertTrue(triggered);
|
||||
Assertions.assertTrue(triggered);
|
||||
|
||||
// 2nd invocation
|
||||
triggered = policy.isTriggeringEvent(activeFile, event);
|
||||
Assert.assertFalse(triggered);
|
||||
Assertions.assertFalse(triggered);
|
||||
|
||||
// 3rd invocation
|
||||
triggered = policy.isTriggeringEvent(activeFile, event);
|
||||
Assert.assertFalse(triggered);
|
||||
Assertions.assertFalse(triggered);
|
||||
|
||||
Mockito.verifyZeroInteractions(activeFile);
|
||||
Mockito.verifyZeroInteractions(event);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE xml>
|
||||
<!-- log config used during unit tests and starts from IDE. For production please specify -Dlogback.configurationFile=/path/to/config -->
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.cryptomator" level="DEBUG" />
|
||||
<logger name="org.eclipse.jetty" level="INFO" />
|
||||
<logger name="org.eclipse.jetty.server.Server" level="DEBUG" />
|
||||
<logger name="org.apache" level="INFO" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
152
main/pom.xml
152
main/pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.3.1</version>
|
||||
<version>1.4.6</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
@@ -24,28 +24,30 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<!-- dependency versions -->
|
||||
<cryptomator.cryptolib.version>1.1.5</cryptomator.cryptolib.version>
|
||||
<cryptomator.cryptofs.version>1.4.1</cryptomator.cryptofs.version>
|
||||
<cryptomator.webdav.version>0.6.2</cryptomator.webdav.version>
|
||||
<cryptomator.jni.version>1.0.2</cryptomator.jni.version>
|
||||
|
||||
<commons-io.version>2.5</commons-io.version>
|
||||
<commons-lang3.version>3.6</commons-lang3.version>
|
||||
<httpclient.version>4.5.3</httpclient.version>
|
||||
|
||||
<cryptomator.cryptolib.version>1.2.1</cryptomator.cryptolib.version>
|
||||
<cryptomator.cryptofs.version>1.7.0</cryptomator.cryptofs.version>
|
||||
<cryptomator.jni.version>2.0.0</cryptomator.jni.version>
|
||||
<cryptomator.fuse.version>1.1.1</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.1.3</cryptomator.dokany.version>
|
||||
<cryptomator.webdav.version>1.0.9</cryptomator.webdav.version>
|
||||
|
||||
<javafx.version>11.0.2</javafx.version>
|
||||
|
||||
<commons-io.version>2.6</commons-io.version>
|
||||
<commons-lang3.version>3.8.1</commons-lang3.version>
|
||||
|
||||
<easybind.version>1.0.3</easybind.version>
|
||||
|
||||
<guava.version>22.0</guava.version>
|
||||
<dagger.version>2.11</dagger.version>
|
||||
<gson.version>2.8.1</gson.version>
|
||||
|
||||
|
||||
<guava.version>27.0-jre</guava.version>
|
||||
<dagger.version>2.20</dagger.version>
|
||||
<gson.version>2.8.5</gson.version>
|
||||
|
||||
<slf4j.version>1.7.25</slf4j.version>
|
||||
<logback.version>1.2.2</logback.version>
|
||||
|
||||
<junit.version>4.12</junit.version>
|
||||
<junit.hierarchicalrunner.version>4.12.1</junit.hierarchicalrunner.version>
|
||||
<mockito.version>2.7.21</mockito.version>
|
||||
<hamcrest.version>1.3</hamcrest.version> <!-- keep in sync with version required by JUnit -->
|
||||
<logback.version>1.2.3</logback.version>
|
||||
|
||||
<junit.jupiter.version>5.4.0</junit.jupiter.version>
|
||||
<mockito.version>2.24.0</mockito.version>
|
||||
<hamcrest.version>1.3</hamcrest.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
@@ -59,6 +61,10 @@
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jcenter</id>
|
||||
<url>http://jcenter.bintray.com</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -96,6 +102,16 @@
|
||||
<artifactId>cryptofs</artifactId>
|
||||
<version>${cryptomator.cryptofs.version}</version>
|
||||
</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>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>webdav-nio-adapter</artifactId>
|
||||
@@ -107,6 +123,23 @@
|
||||
<version>${cryptomator.jni.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JavaFX -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-base</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
@@ -140,11 +173,6 @@
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>${httpclient.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
@@ -172,20 +200,10 @@
|
||||
|
||||
<!-- JUnit / Mockito / Hamcrest -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.bechte.junit</groupId>
|
||||
<artifactId>junit-hierarchicalcontextrunner</artifactId>
|
||||
<version>${junit.hierarchicalrunner.version}</version>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
@@ -197,6 +215,12 @@
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<version>${hamcrest.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-swing</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -207,9 +231,8 @@
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
@@ -221,11 +244,6 @@
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.bechte.junit</groupId>
|
||||
<artifactId>junit-hierarchicalcontextrunner</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<modules>
|
||||
@@ -239,15 +257,11 @@
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<modules>
|
||||
<module>uber-jar</module>
|
||||
<module>ant-kit</module>
|
||||
<module>buildkit</module>
|
||||
</modules>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>coverage</id>
|
||||
<modules>
|
||||
<module>jacoco-report</module>
|
||||
</modules>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
@@ -264,7 +278,7 @@
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-libs</id>
|
||||
@@ -278,10 +292,22 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<!-- adds Implementation-Version which can be read during runtime -->
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.7.9</version>
|
||||
<version>0.8.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
@@ -289,6 +315,12 @@
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<excludes>
|
||||
@@ -302,10 +334,9 @@
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.6.1</version>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<release>11</release>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>com.google.dagger</groupId>
|
||||
@@ -315,6 +346,11 @@
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.3.1</version>
|
||||
</parent>
|
||||
<artifactId>uber-jar</artifactId>
|
||||
<name>Single über jar with all dependencies</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>launcher</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<finalName>Cryptomator-${project.version}</finalName>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<manifestEntries>
|
||||
<Main-Class>org.cryptomator.launcher.Cryptomator</Main-Class>
|
||||
<Implementation-Version>${project.version}</Implementation-Version>
|
||||
</manifestEntries>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE xml>
|
||||
<!-- log config used during unit tests and starts from IDE. For production please specify -Dlogback.configurationFile=/path/to/config -->
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.cryptomator" level="DEBUG" />
|
||||
<logger name="org.eclipse.jetty" level="INFO" />
|
||||
<logger name="org.eclipse.jetty.server.Server" level="INFO" />
|
||||
<logger name="org.apache" level="INFO" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -4,39 +4,58 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.3.1</version>
|
||||
<version>1.4.6</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>keychain</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptofs</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>webdav-nio-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>jni</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>keychain</artifactId>
|
||||
<artifactId>fuse-nio-adapter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>dokany-nio-adapter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>webdav-nio-adapter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- CryptoLib -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptolib</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JavaFx -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
@@ -62,10 +81,6 @@
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DI -->
|
||||
<dependency>
|
||||
@@ -86,5 +101,18 @@
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing -->
|
||||
<dependency>
|
||||
<groupId>com.google.jimfs</groupId>
|
||||
<artifactId>jimfs</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-swing</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -9,6 +9,24 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.jni.JniException;
|
||||
import org.cryptomator.jni.MacApplicationUiState;
|
||||
import org.cryptomator.jni.MacFunctions;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
import javax.swing.SwingUtilities;
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Image;
|
||||
import java.awt.MenuItem;
|
||||
@@ -24,27 +42,7 @@ import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.jni.JniException;
|
||||
import org.cryptomator.jni.MacApplicationUiState;
|
||||
import org.cryptomator.jni.MacFunctions;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
public class ExitUtil {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ExitUtil.class);
|
||||
@@ -53,6 +51,7 @@ public class ExitUtil {
|
||||
private final Localization localization;
|
||||
private final Settings settings;
|
||||
private final Optional<MacFunctions> macFunctions;
|
||||
private TrayIcon trayIcon;
|
||||
|
||||
@Inject
|
||||
public ExitUtil(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, Optional<MacFunctions> macFunctions) {
|
||||
@@ -82,7 +81,7 @@ public class ExitUtil {
|
||||
}
|
||||
|
||||
private void initTrayIconExitHandler(Runnable exitCommand) {
|
||||
final TrayIcon trayIcon = createTrayIcon(exitCommand);
|
||||
trayIcon = createTrayIcon(exitCommand);
|
||||
try {
|
||||
// double clicking tray icon should open Cryptomator
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
@@ -118,14 +117,7 @@ public class ExitUtil {
|
||||
exitItem.addActionListener(e -> exitCommand.run());
|
||||
popup.add(exitItem);
|
||||
|
||||
final Image image;
|
||||
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"));
|
||||
}
|
||||
final Image image = getAppropriateTrayIconImage(true);
|
||||
|
||||
return new TrayIcon(image, localization.getString("app.name"), popup);
|
||||
}
|
||||
@@ -202,4 +194,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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,48 +8,62 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.beans.binding.Binding;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.CommonsModule;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.SettingsProvider;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.cryptomator.jni.JniModule;
|
||||
import org.cryptomator.keychain.KeychainModule;
|
||||
import org.cryptomator.ui.controllers.ViewControllerModule;
|
||||
import org.cryptomator.ui.model.VaultComponent;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javax.inject.Named;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class, JniModule.class}, subcomponents = {VaultComponent.class})
|
||||
@Module(includes = {ViewControllerModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class})
|
||||
public class UiModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Settings provideSettings(SettingsProvider settingsProvider) {
|
||||
return settingsProvider.get();
|
||||
}
|
||||
private static final int NUM_SCHEDULER_THREADS = 4;
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
||||
ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
@FxApplicationScoped
|
||||
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
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
||||
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);
|
||||
return executorService;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
Binding<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
|
||||
return EasyBind.map(settings.port(), (Number port) -> {
|
||||
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
|
||||
@@ -58,7 +72,7 @@ public class UiModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@FxApplicationScoped
|
||||
WebDavServer provideWebDavServer(Binding<InetSocketAddress> serverSocketAddressBinding) {
|
||||
WebDavServer server = WebDavServer.create();
|
||||
// no need to unsubscribe eventually, because server is a singleton
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
@@ -48,7 +49,7 @@ public class ChangePasswordController implements ViewController {
|
||||
private final Application app;
|
||||
private final PasswordStrengthUtil strengthRater;
|
||||
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 Vault vault;
|
||||
|
||||
@@ -100,11 +101,9 @@ public class ChangePasswordController implements ViewController {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
BooleanBinding oldPasswordIsEmpty = oldPasswordField.textProperty().isEmpty();
|
||||
BooleanBinding newPasswordIsEmpty = newPasswordField.textProperty().isEmpty();
|
||||
BooleanBinding passwordsDiffer = newPasswordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
|
||||
changePasswordButton.disableProperty().bind(oldPasswordIsEmpty.or(newPasswordIsEmpty.or(passwordsDiffer)));
|
||||
passwordStrength.bind(EasyBind.map(newPasswordField.textProperty(), strengthRater::computeRate));
|
||||
oldPasswordField.textProperty().addListener(this::passwordsChanged);
|
||||
newPasswordField.textProperty().addListener(this::passwordsChanged);
|
||||
retypePasswordField.textProperty().addListener(this::passwordsChanged);
|
||||
|
||||
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
|
||||
@@ -114,11 +113,24 @@ public class ChangePasswordController implements ViewController {
|
||||
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
|
||||
public Parent getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focus() {
|
||||
oldPasswordField.requestFocus();
|
||||
}
|
||||
|
||||
void setVault(Vault vault) {
|
||||
this.vault = Objects.requireNonNull(vault);
|
||||
// trigger "default" change to refresh key bindings:
|
||||
|
||||
@@ -10,13 +10,15 @@
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryNotEmptyException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.value.ObservableIntegerValue;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
@@ -43,7 +45,7 @@ public class InitializeController implements ViewController {
|
||||
|
||||
private final Localization localization;
|
||||
private final PasswordStrengthUtil strengthRater;
|
||||
private ObservableValue<Integer> passwordStrength; // 0-4
|
||||
private IntegerProperty passwordStrength = new SimpleIntegerProperty(-1); // strengths: 0-4
|
||||
private Optional<InitializationListener> listener = Optional.empty();
|
||||
private Vault vault;
|
||||
|
||||
@@ -88,10 +90,8 @@ public class InitializeController implements ViewController {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
BooleanBinding passwordIsEmpty = passwordField.textProperty().isEmpty();
|
||||
BooleanBinding passwordsDiffer = passwordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
|
||||
okButton.disableProperty().bind(passwordIsEmpty.or(passwordsDiffer));
|
||||
passwordStrength = EasyBind.map(passwordField.textProperty(), strengthRater::computeRate);
|
||||
passwordField.textProperty().addListener(this::passwordsChanged);
|
||||
retypePasswordField.textProperty().addListener(this::passwordsChanged);
|
||||
|
||||
passwordStrengthLevel0.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(0), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel1.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(1), strengthRater::getBackgroundWithStrengthColor));
|
||||
@@ -101,11 +101,23 @@ public class InitializeController implements ViewController {
|
||||
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
|
||||
public Parent getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focus() {
|
||||
passwordField.requestFocus();
|
||||
}
|
||||
|
||||
void setVault(Vault vault) {
|
||||
this.vault = Objects.requireNonNull(vault);
|
||||
// trigger "default" change to refresh key bindings:
|
||||
@@ -125,8 +137,6 @@ public class InitializeController implements ViewController {
|
||||
listener.ifPresent(this::invokeListenerLater);
|
||||
} catch (FileAlreadyExistsException ex) {
|
||||
messageLabel.setText(localization.getString("initialize.messageLabel.alreadyInitialized"));
|
||||
} catch (DirectoryNotEmptyException ex) {
|
||||
messageLabel.setText(localization.getString("initialize.messageLabel.notEmpty"));
|
||||
} catch (IOException ex) {
|
||||
LOG.error("I/O Exception", ex);
|
||||
messageLabel.setText(localization.getString("initialize.messageLabel.initializationFailed"));
|
||||
|
||||
@@ -2,48 +2,13 @@
|
||||
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
||||
* All rights reserved.
|
||||
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
||||
*
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
* Jean-Noël Charon - confirmation dialog on vault removal
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
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.Platform;
|
||||
import javafx.beans.binding.Binding;
|
||||
@@ -63,19 +28,62 @@ import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Cell;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
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.Pane;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.ui.ExitUtil;
|
||||
import org.cryptomator.ui.controls.DirectoryListCell;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.AppLaunchEvent;
|
||||
import org.cryptomator.ui.model.AutoUnlocker;
|
||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.upgrade.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.Tasks;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.Subscription;
|
||||
import org.fxmisc.easybind.monadic.MonadicBinding;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.awt.Desktop;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.cryptomator.ui.util.DialogBuilderUtil.buildErrorDialog;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class MainController implements ViewController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
|
||||
@@ -86,7 +94,7 @@ public class MainController implements ViewController {
|
||||
private final ExitUtil exitUtil;
|
||||
private final Localization localization;
|
||||
private final ExecutorService executorService;
|
||||
private final BlockingQueue<Path> fileOpenRequests;
|
||||
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
|
||||
private final VaultFactory vaultFactoy;
|
||||
private final ViewControllerLoader viewControllerLoader;
|
||||
private final ObjectProperty<ViewController> activeController = new SimpleObjectProperty<>();
|
||||
@@ -103,11 +111,11 @@ public class MainController implements ViewController {
|
||||
private Subscription subs = Subscription.EMPTY;
|
||||
|
||||
@Inject
|
||||
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("fileOpenRequests") BlockingQueue<Path> fileOpenRequests, ExitUtil exitUtil, Localization localization,
|
||||
VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker) {
|
||||
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExitUtil exitUtil, Localization localization,
|
||||
VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.executorService = executorService;
|
||||
this.fileOpenRequests = fileOpenRequests;
|
||||
this.launchEventQueue = launchEventQueue;
|
||||
this.exitUtil = exitUtil;
|
||||
this.localization = localization;
|
||||
this.vaultFactoy = vaultFactoy;
|
||||
@@ -119,14 +127,17 @@ public class MainController implements ViewController {
|
||||
this.upgradeStrategyForSelectedVault = EasyBind.monadic(selectedVault).map(upgradeStrategies::getUpgradeStrategy);
|
||||
this.areAllVaultsLocked = Bindings.isEmpty(FXCollections.observableList(vaults, Vault::observables).filtered(Vault.NOT_LOCKED));
|
||||
|
||||
EasyBind.subscribe(areAllVaultsLocked, exitUtil::updateTrayIcon);
|
||||
EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit);
|
||||
autoUnlocker.unlockAllSilently();
|
||||
|
||||
EawtApplicationWrapper.getApplication().ifPresent(app -> {
|
||||
app.setPreferencesHandler(() -> {
|
||||
try {
|
||||
Desktop.getDesktop().setPreferencesHandler(e -> {
|
||||
Platform.runLater(this::toggleShowSettings);
|
||||
});
|
||||
});
|
||||
} catch (UnsupportedOperationException e) {
|
||||
LOG.info("Unable to setPreferencesHandler, probably not supported on this OS.");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -162,7 +173,10 @@ public class MainController implements ViewController {
|
||||
@Override
|
||||
public void initialize() {
|
||||
vaultList.setItems(vaults);
|
||||
vaultList.getSelectionModel().clearSelection();
|
||||
vaultList.setOnKeyReleased(this::didPressKeyOnList);
|
||||
vaultList.setCellFactory(this::createDirecoryListCell);
|
||||
root.setOnKeyReleased(this::didPressKeyOnRoot);
|
||||
activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
|
||||
selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
|
||||
removeVaultButton.disableProperty().bind(canEditSelectedVault.not());
|
||||
@@ -183,6 +197,7 @@ public class MainController implements ViewController {
|
||||
public void initStage(Stage stage) {
|
||||
stage.setScene(new Scene(getRoot()));
|
||||
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.setResizable(false);
|
||||
loadFont("/css/ionicons.ttf");
|
||||
@@ -192,18 +207,38 @@ public class MainController implements ViewController {
|
||||
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());
|
||||
} 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());
|
||||
} 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());
|
||||
}
|
||||
exitUtil.initExitHandler(this::gracefulShutdown);
|
||||
exitUtil.initExitHandler(() -> Platform.runLater(this::gracefulShutdown));
|
||||
listenToFileOpenRequests(stage);
|
||||
}
|
||||
|
||||
private void gracefulShutdown() {
|
||||
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) {
|
||||
@@ -215,27 +250,19 @@ public class MainController implements ViewController {
|
||||
}
|
||||
|
||||
private void listenToFileOpenRequests(Stage stage) {
|
||||
executorService.submit(() -> {
|
||||
while (!Thread.interrupted()) {
|
||||
try {
|
||||
final Path path = fileOpenRequests.take();
|
||||
Platform.runLater(() -> {
|
||||
addVault(path, true);
|
||||
stage.setIconified(false);
|
||||
stage.show();
|
||||
stage.toFront();
|
||||
stage.requestFocus();
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
});
|
||||
Tasks.create(launchEventQueue::take).onSuccess(event -> {
|
||||
stage.setIconified(false);
|
||||
stage.show();
|
||||
stage.toFront();
|
||||
stage.requestFocus();
|
||||
event.getPathsToOpen().forEach(path -> addVault(path, true));
|
||||
}).schedulePeriodically(executorService, Duration.ZERO, Duration.ZERO);
|
||||
}
|
||||
|
||||
private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
|
||||
final DirectoryListCell cell = new DirectoryListCell();
|
||||
cell.setVaultContextMenu(vaultListCellContextMenu);
|
||||
cell.setOnMouseClicked(this::didClickOnListCell);
|
||||
return cell;
|
||||
}
|
||||
|
||||
@@ -261,7 +288,18 @@ public class MainController implements ViewController {
|
||||
}
|
||||
try {
|
||||
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);
|
||||
}
|
||||
addVault(vaultDir, true);
|
||||
@@ -270,6 +308,10 @@ public class MainController implements ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNotHidden(Path file) {
|
||||
return !file.getFileName().toString().startsWith(".");
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickAddExistingVaults(ActionEvent event) {
|
||||
final FileChooser fileChooser = new FileChooser();
|
||||
@@ -284,14 +326,14 @@ public class MainController implements ViewController {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public void addVault(final Path path, boolean select) {
|
||||
final Path vaultPath;
|
||||
if (path != null && Files.isDirectory(path)) {
|
||||
vaultPath = path;
|
||||
} else if (path != null && Files.isRegularFile(path)) {
|
||||
} else if (path != null && Files.isReadable(path)) {
|
||||
vaultPath = path.getParent();
|
||||
} else {
|
||||
LOG.warn("Ignoring attempt to add vault with invalid path: {}", path);
|
||||
@@ -309,6 +351,7 @@ public class MainController implements ViewController {
|
||||
}
|
||||
if (select) {
|
||||
vaultList.getSelectionModel().select(vault);
|
||||
activeController.get().focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +368,8 @@ public class MainController implements ViewController {
|
||||
vaults.remove(selectedVault.get());
|
||||
if (vaults.isEmpty()) {
|
||||
activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
|
||||
} else {
|
||||
activeController.get().focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,18 +408,54 @@ public class MainController implements ViewController {
|
||||
return;
|
||||
}
|
||||
if (newValue.getState() != Vault.State.LOCKED) {
|
||||
this.showUnlockedView(newValue);
|
||||
this.showUnlockedView(newValue, false);
|
||||
} else if (!newValue.doesVaultDirectoryExist()) {
|
||||
this.showNotFoundView();
|
||||
} else if (newValue.isValidVaultDirectory() && upgradeStrategyForSelectedVault.isPresent()) {
|
||||
this.showUpgradeView();
|
||||
} else if (newValue.isValidVaultDirectory()) {
|
||||
this.showUnlockView();
|
||||
this.showUnlockView(UnlockController.State.UNLOCKING);
|
||||
} else {
|
||||
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
|
||||
// ****************************************
|
||||
@@ -407,7 +488,8 @@ public class MainController implements ViewController {
|
||||
}
|
||||
|
||||
public void didInitialize() {
|
||||
showUnlockView();
|
||||
showUnlockView(UnlockController.State.INITIALIZED);
|
||||
activeController.get().focus();
|
||||
}
|
||||
|
||||
private void showUpgradeView() {
|
||||
@@ -418,34 +500,39 @@ public class MainController implements ViewController {
|
||||
}
|
||||
|
||||
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");
|
||||
ctrl.setVault(selectedVault.get());
|
||||
ctrl.setVault(selectedVault.get(), state);
|
||||
ctrl.setListener(this::didUnlock);
|
||||
activeController.set(ctrl);
|
||||
}
|
||||
|
||||
public void didUnlock(Vault vault) {
|
||||
if (vault.equals(selectedVault.getValue())) {
|
||||
this.showUnlockedView(vault);
|
||||
this.showUnlockedView(vault, vault.getVaultSettings().revealAfterMount().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void showUnlockedView(Vault vault) {
|
||||
final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> {
|
||||
return viewControllerLoader.load("/fxml/unlocked.fxml");
|
||||
});
|
||||
private void showUnlockedView(Vault vault, boolean reveal) {
|
||||
final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> viewControllerLoader.load("/fxml/unlocked.fxml"));
|
||||
ctrl.setVault(vault);
|
||||
ctrl.setListener(this::didLock);
|
||||
if (reveal) {
|
||||
ctrl.revealVault(vault);
|
||||
}
|
||||
activeController.set(ctrl);
|
||||
}
|
||||
|
||||
public void didLock(UnlockedController ctrl) {
|
||||
unlockedVaults.remove(ctrl.getVault());
|
||||
showUnlockView();
|
||||
if (ctrl.getVault().getId() == selectedVault.get().getId()) {
|
||||
showUnlockView(UnlockController.State.UNLOCKING);
|
||||
}
|
||||
activeController.get().focus();
|
||||
}
|
||||
|
||||
private void showChangePasswordView() {
|
||||
@@ -453,10 +540,12 @@ public class MainController implements ViewController {
|
||||
ctrl.setVault(selectedVault.get());
|
||||
ctrl.setListener(this::didChangePassword);
|
||||
activeController.set(ctrl);
|
||||
Platform.runLater(ctrl::focus);
|
||||
}
|
||||
|
||||
public void didChangePassword() {
|
||||
showUnlockView();
|
||||
showUnlockView(UnlockController.State.PASSWORD_CHANGED);
|
||||
activeController.get().focus();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
|
||||
@Singleton
|
||||
import javax.inject.Inject;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class NotFoundController implements ViewController {
|
||||
|
||||
@Inject
|
||||
|
||||
@@ -2,28 +2,19 @@
|
||||
* Copyright (c) 2014, 2017 Sebastian Stenzel
|
||||
* All rights reserved.
|
||||
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
||||
*
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import 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.Strings;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
@@ -32,8 +23,19 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.StringConverter;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.Volume;
|
||||
|
||||
@Singleton
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.util.Optional;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class SettingsController implements ViewController {
|
||||
|
||||
private static final CharMatcher DIGITS_MATCHER = CharMatcher.inRange('0', '9');
|
||||
@@ -47,11 +49,17 @@ public class SettingsController implements ViewController {
|
||||
this.localization = localization;
|
||||
this.settings = settings;
|
||||
this.applicationVersion = applicationVersion;
|
||||
this.webdavSettings = new Group();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private CheckBox checkForUpdatesCheckbox;
|
||||
|
||||
private Group webdavSettings;
|
||||
|
||||
@FXML
|
||||
private Label portFieldLabel;
|
||||
|
||||
@FXML
|
||||
private TextField portField;
|
||||
|
||||
@@ -67,6 +75,9 @@ public class SettingsController implements ViewController {
|
||||
@FXML
|
||||
private ChoiceBox<String> prefGvfsScheme;
|
||||
|
||||
@FXML
|
||||
private ChoiceBox<VolumeImpl> volume;
|
||||
|
||||
@FXML
|
||||
private CheckBox debugModeCheckbox;
|
||||
|
||||
@@ -75,22 +86,41 @@ public class SettingsController implements ViewController {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion.orElse("SNAPSHOT")));
|
||||
checkForUpdatesCheckbox.setDisable(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.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents);
|
||||
changePortButton.visibleProperty().bind(settings.port().asString().isNotEqualTo(portField.textProperty()));
|
||||
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("webdav");
|
||||
prefGvfsScheme.setValue(settings.preferredGvfsScheme().get());
|
||||
prefGvfsSchemeLabel.setVisible(SystemUtils.IS_OS_LINUX);
|
||||
prefGvfsScheme.setVisible(SystemUtils.IS_OS_LINUX);
|
||||
|
||||
debugModeCheckbox.setSelected(settings.debugMode().get());
|
||||
|
||||
settings.checkForUpdates().bind(checkForUpdatesCheckbox.selectedProperty());
|
||||
settings.preferredGvfsScheme().bind(prefGvfsScheme.valueProperty());
|
||||
settings.preferredVolumeImpl().bind(volume.valueProperty());
|
||||
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() {
|
||||
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
|
||||
* All rights reserved.
|
||||
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
||||
*
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import 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.Strings;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -54,8 +27,42 @@ import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.Stage;
|
||||
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 {
|
||||
|
||||
@@ -67,22 +74,26 @@ public class UnlockController implements ViewController {
|
||||
.precomputed();
|
||||
|
||||
private final Application app;
|
||||
private final Stage mainWindow;
|
||||
private final Localization localization;
|
||||
private final AsyncTaskService asyncTaskService;
|
||||
private final WindowsDriveLetters driveLetters;
|
||||
private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange;
|
||||
private final Optional<KeychainAccess> keychainAccess;
|
||||
private final Settings settings;
|
||||
private final ExecutorService executor;
|
||||
private Vault vault;
|
||||
private Optional<UnlockListener> listener = Optional.empty();
|
||||
private Subscription vaultSubs = Subscription.EMPTY;
|
||||
|
||||
@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.mainWindow = mainWindow;
|
||||
this.localization = localization;
|
||||
this.asyncTaskService = asyncTaskService;
|
||||
this.driveLetters = driveLetters;
|
||||
this.keychainAccess = keychainAccess;
|
||||
this.settings = settings;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -95,10 +106,10 @@ public class UnlockController implements ViewController {
|
||||
private Button unlockButton;
|
||||
|
||||
@FXML
|
||||
private CheckBox savePassword;
|
||||
private Text messageText;
|
||||
|
||||
@FXML
|
||||
private CheckBox mountAfterUnlock;
|
||||
private CheckBox savePassword;
|
||||
|
||||
@FXML
|
||||
private TextField mountName;
|
||||
@@ -112,11 +123,20 @@ public class UnlockController implements ViewController {
|
||||
@FXML
|
||||
private ChoiceBox<Character> winDriveLetter;
|
||||
|
||||
@FXML
|
||||
private CheckBox useCustomMountPoint;
|
||||
|
||||
@FXML
|
||||
private HBox customMountPoint;
|
||||
|
||||
@FXML
|
||||
private Label customMountPointLabel;
|
||||
|
||||
@FXML
|
||||
private ProgressIndicator progressIndicator;
|
||||
|
||||
@FXML
|
||||
private Text messageText;
|
||||
private Text progressText;
|
||||
|
||||
@FXML
|
||||
private Hyperlink downloadsPageLink;
|
||||
@@ -130,19 +150,23 @@ public class UnlockController implements ViewController {
|
||||
@FXML
|
||||
private CheckBox unlockAfterStartup;
|
||||
|
||||
@FXML
|
||||
private CheckBox useReadOnlyMode;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
|
||||
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.textProperty().addListener(this::mountNameDidChange);
|
||||
savePassword.setDisable(!keychainAccess.isPresent());
|
||||
unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
|
||||
} else {
|
||||
|
||||
customMountPoint.visibleProperty().bind(useCustomMountPoint.selectedProperty());
|
||||
customMountPoint.managedProperty().bind(useCustomMountPoint.selectedProperty());
|
||||
winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
|
||||
|
||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
||||
winDriveLetterLabel.setVisible(false);
|
||||
winDriveLetterLabel.setManaged(false);
|
||||
winDriveLetter.setVisible(false);
|
||||
@@ -150,12 +174,18 @@ public class UnlockController implements ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Parent getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
void setVault(Vault vault) {
|
||||
@Override
|
||||
public void focus() {
|
||||
passwordField.requestFocus();
|
||||
}
|
||||
|
||||
void setVault(Vault vault, State state) {
|
||||
vaultSubs.unsubscribe();
|
||||
vaultSubs = Subscription.EMPTY;
|
||||
|
||||
@@ -170,6 +200,8 @@ public class UnlockController implements ViewController {
|
||||
advancedOptions.setVisible(false);
|
||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
||||
progressIndicator.setVisible(false);
|
||||
progressText.setText(null);
|
||||
state.successMessage().map(localization::getString).ifPresent(messageText::setText);
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
winDriveLetter.valueProperty().removeListener(driveLetterChangeListener);
|
||||
winDriveLetter.getItems().clear();
|
||||
@@ -177,32 +209,77 @@ public class UnlockController implements ViewController {
|
||||
winDriveLetter.getItems().addAll(driveLetters.getAvailableDriveLetters());
|
||||
winDriveLetter.getItems().sort(new WinDriveLetterComparator());
|
||||
winDriveLetter.valueProperty().addListener(driveLetterChangeListener);
|
||||
}
|
||||
downloadsPageLink.setVisible(false);
|
||||
messageText.setText(null);
|
||||
mountName.setText(vault.getMountName());
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
chooseSelectedDriveLetter();
|
||||
}
|
||||
downloadsPageLink.setVisible(false);
|
||||
mountName.setText(vault.getMountName());
|
||||
savePassword.setSelected(false);
|
||||
// auto-fill pw from keychain:
|
||||
if (keychainAccess.isPresent()) {
|
||||
char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
|
||||
if (storedPw != null) {
|
||||
savePassword.setSelected(true);
|
||||
passwordField.setText(new String(storedPw));
|
||||
passwordField.setPassword(storedPw);
|
||||
passwordField.selectRange(storedPw.length, storedPw.length);
|
||||
Arrays.fill(storedPw, ' ');
|
||||
}
|
||||
}
|
||||
VaultSettings settings = vault.getVaultSettings();
|
||||
unlockAfterStartup.setSelected(savePassword.isSelected() && settings.unlockAfterStartup().get());
|
||||
mountAfterUnlock.setSelected(settings.mountAfterUnlock().get());
|
||||
revealAfterMount.setSelected(settings.revealAfterMount().get());
|
||||
VaultSettings vaultSettings = vault.getVaultSettings();
|
||||
unlockAfterStartup.setSelected(savePassword.isSelected() && vaultSettings.unlockAfterStartup().get());
|
||||
revealAfterMount.setSelected(vaultSettings.revealAfterMount().get());
|
||||
|
||||
vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), settings.unlockAfterStartup()::set));
|
||||
vaultSubs = vaultSubs.and(EasyBind.subscribe(mountAfterUnlock.selectedProperty(), settings.mountAfterUnlock()::set));
|
||||
vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), settings.revealAfterMount()::set));
|
||||
// WEBDAV-dependent controls:
|
||||
if (VolumeImpl.WEBDAV.equals(settings.preferredVolumeImpl().get())) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
@@ -220,6 +297,7 @@ public class UnlockController implements ViewController {
|
||||
|
||||
@FXML
|
||||
private void didClickAdvancedOptionsButton(ActionEvent event) {
|
||||
messageText.setText(null);
|
||||
advancedOptions.setVisible(!advancedOptions.isVisible());
|
||||
if (advancedOptions.isVisible()) {
|
||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.hide"));
|
||||
@@ -243,6 +321,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.
|
||||
*/
|
||||
@@ -253,7 +340,7 @@ public class UnlockController implements ViewController {
|
||||
if (letter == null) {
|
||||
return localization.getString("unlock.choicebox.winDriveLetter.auto");
|
||||
} else {
|
||||
return Character.toString(letter) + ":";
|
||||
return letter + ":";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,10 +429,13 @@ public class UnlockController implements ViewController {
|
||||
@FXML
|
||||
private void didClickUnlockButton(ActionEvent event) {
|
||||
advancedOptions.setDisable(true);
|
||||
advancedOptions.setVisible(false);
|
||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
||||
progressIndicator.setVisible(true);
|
||||
|
||||
CharSequence password = passwordField.getCharacters();
|
||||
asyncTaskService.asyncTaskOf(() -> {
|
||||
Tasks.create(() -> {
|
||||
progressText.setText(localization.getString("unlock.pendingMessage.unlocking"));
|
||||
vault.unlock(password);
|
||||
if (keychainAccess.isPresent() && savePassword.isSelected()) {
|
||||
keychainAccess.get().storePassphrase(vault.getId(), password);
|
||||
@@ -369,10 +459,17 @@ public class UnlockController implements ViewController {
|
||||
} else if (e.getDetectedVersion() == Integer.MAX_VALUE) {
|
||||
messageText.setText(localization.getString("unlock.errorMessage.unauthenticVersionMac"));
|
||||
}
|
||||
}).onError(ServerLifecycleException.class, e -> {
|
||||
LOG.error("Unlock failed for technical reasons.", e);
|
||||
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
|
||||
}).onError(IOException.class, e -> {
|
||||
}).onError(NotDirectoryException.class, e -> {
|
||||
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
|
||||
advancedOptions.setVisible(true);
|
||||
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);
|
||||
messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
|
||||
}).andFinally(() -> {
|
||||
@@ -381,7 +478,16 @@ public class UnlockController implements ViewController {
|
||||
}
|
||||
advancedOptions.setDisable(false);
|
||||
progressIndicator.setVisible(false);
|
||||
}).run();
|
||||
progressText.setText(null);
|
||||
}).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 */
|
||||
@@ -392,7 +498,28 @@ public class UnlockController implements ViewController {
|
||||
|
||||
@FunctionalInterface
|
||||
interface UnlockListener {
|
||||
|
||||
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;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.cryptomator.frontend.webdav.ServerLifecycleException;
|
||||
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 java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.ObjectExpression;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
@@ -46,13 +30,19 @@ import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.PopupWindow.AnchorLocation;
|
||||
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 {
|
||||
|
||||
@@ -62,9 +52,8 @@ public class UnlockedController implements ViewController {
|
||||
private static final double IO_SAMPLING_INTERVAL = 0.25;
|
||||
|
||||
private final Localization localization;
|
||||
private final AsyncTaskService asyncTaskService;
|
||||
private final ExecutorService executor;
|
||||
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 Timeline ioAnimation;
|
||||
|
||||
@@ -83,30 +72,17 @@ public class UnlockedController implements ViewController {
|
||||
@FXML
|
||||
private ContextMenu moreOptionsMenu;
|
||||
|
||||
@FXML
|
||||
private MenuItem mountVaultMenuItem;
|
||||
|
||||
@FXML
|
||||
private MenuItem unmountVaultMenuItem;
|
||||
|
||||
@FXML
|
||||
private MenuItem revealVaultMenuItem;
|
||||
|
||||
@FXML
|
||||
private VBox root;
|
||||
|
||||
@Inject
|
||||
public UnlockedController(Localization localization, AsyncTaskService asyncTaskService) {
|
||||
public UnlockedController(Localization localization, ExecutorService executor) {
|
||||
this.localization = localization;
|
||||
this.asyncTaskService = asyncTaskService;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
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(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected);
|
||||
}
|
||||
@@ -121,10 +97,6 @@ public class UnlockedController implements ViewController {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newVault.getState() == Vault.State.UNLOCKED && newVault.getVaultSettings().mountAfterUnlock().get()) {
|
||||
mountVault(newVault);
|
||||
}
|
||||
|
||||
// (re)start throughput statistics:
|
||||
stopIoSampling();
|
||||
startIoSampling();
|
||||
@@ -132,75 +104,22 @@ public class UnlockedController implements ViewController {
|
||||
|
||||
@FXML
|
||||
private void didClickLockVault(ActionEvent event) {
|
||||
regularUnmountVault(this::lockVault);
|
||||
regularLockVault(this::lockVaultSucceeded);
|
||||
}
|
||||
|
||||
private void lockVault() {
|
||||
try {
|
||||
vault.get().lock();
|
||||
} catch (ServerLifecycleException | IOException e) {
|
||||
LOG.error("Lock failed", e);
|
||||
}
|
||||
private void lockVaultSucceeded() {
|
||||
listener.ifPresent(listener -> listener.didLock(this));
|
||||
}
|
||||
|
||||
@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
|
||||
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();
|
||||
private void regularLockVault(Runnable onSuccess) {
|
||||
Tasks.create(() -> {
|
||||
vault.get().lock(false);
|
||||
}).onSuccess(() -> {
|
||||
LOG.trace("Regular unmount succeeded.");
|
||||
onSuccess.run();
|
||||
}).onError(Exception.class, e -> {
|
||||
onRegularUnmountVaultFailed(e, onSuccess);
|
||||
}).run();
|
||||
}
|
||||
|
||||
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();
|
||||
}).runOnce(executor);
|
||||
}
|
||||
|
||||
private void onRegularUnmountVaultFailed(Exception e, Runnable onSuccess) {
|
||||
@@ -214,7 +133,7 @@ public class UnlockedController implements ViewController {
|
||||
|
||||
Optional<ButtonType> choice = confirmDialog.showAndWait();
|
||||
if (ButtonType.YES.equals(choice.get())) {
|
||||
forcedUnmountVault(onSuccess);
|
||||
forcedLockVault(onSuccess);
|
||||
} else {
|
||||
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
|
||||
private void didClickRevealVault(ActionEvent event) {
|
||||
revealVault(vault.get());
|
||||
}
|
||||
|
||||
private void revealVault(Vault vault) {
|
||||
asyncTaskService.asyncTaskOf(() -> {
|
||||
void revealVault(Vault vault) {
|
||||
Tasks.create(() -> {
|
||||
vault.reveal();
|
||||
}).onSuccess(() -> {
|
||||
LOG.trace("Reveal succeeded.");
|
||||
messageLabel.setText(null);
|
||||
}).onError(CommandFailedException.class, e -> {
|
||||
}).onError(Exception.class, e -> {
|
||||
LOG.error("Reveal failed.", e);
|
||||
messageLabel.setText(localization.getString("unlocked.label.revealFailed"));
|
||||
}).run();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickCopyUrl(ActionEvent event) {
|
||||
ClipboardContent clipboardContent = new ClipboardContent();
|
||||
clipboardContent.putUrl(vault.get().getWebDavUrl());
|
||||
clipboardContent.putString(vault.get().getWebDavUrl());
|
||||
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||
}).runOnce(executor);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
|
||||
@@ -5,19 +5,10 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
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 java.util.concurrent.ExecutorService;
|
||||
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
@@ -30,19 +21,26 @@ import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategy;
|
||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategy.UpgradeFailedException;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.util.Tasks;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
public class UpgradeController implements ViewController {
|
||||
|
||||
private final ObjectProperty<UpgradeStrategy> strategy = new SimpleObjectProperty<>();
|
||||
private final UpgradeStrategies strategies;
|
||||
private final AsyncTaskService asyncTaskService;
|
||||
private final ExecutorService executor;
|
||||
private Optional<UpgradeListener> listener = Optional.empty();
|
||||
private Vault vault;
|
||||
|
||||
@Inject
|
||||
public UpgradeController(Localization localization, UpgradeStrategies strategies, AsyncTaskService asyncTaskService) {
|
||||
public UpgradeController(UpgradeStrategies strategies, ExecutorService executor) {
|
||||
this.strategies = strategies;
|
||||
this.asyncTaskService = asyncTaskService;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -84,6 +82,11 @@ public class UpgradeController implements ViewController {
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focus() {
|
||||
passwordField.requestFocus();
|
||||
}
|
||||
|
||||
void setVault(Vault vault) {
|
||||
this.vault = Objects.requireNonNull(vault);
|
||||
errorLabel.setText(null);
|
||||
@@ -117,8 +120,8 @@ public class UpgradeController implements ViewController {
|
||||
private void upgrade(UpgradeStrategy instruction) {
|
||||
passwordField.setDisable(true);
|
||||
progressIndicator.setVisible(true);
|
||||
asyncTaskService //
|
||||
.asyncTaskOf(() -> {
|
||||
Tasks //
|
||||
.create(() -> {
|
||||
if (!instruction.isApplicable(vault)) {
|
||||
throw new IllegalStateException("No ugprade needed for " + vault.getPath());
|
||||
}
|
||||
@@ -132,7 +135,7 @@ public class UpgradeController implements ViewController {
|
||||
progressIndicator.setVisible(false);
|
||||
passwordField.setDisable(false);
|
||||
passwordField.swipe();
|
||||
}).run();
|
||||
}).runOnce(executor);
|
||||
}
|
||||
|
||||
private void showNextUpgrade() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user