mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-16 01:31:28 +00:00
Compare commits
531 Commits
1.4.12
...
1.5.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88f83c5cdb | ||
|
|
c8059bef78 | ||
|
|
638b731cf8 | ||
|
|
b2b6e304ed | ||
|
|
0748709f40 | ||
|
|
d8ee446d37 | ||
|
|
9932185ccc | ||
|
|
77afd4688a | ||
|
|
9a5ef3f6ff | ||
|
|
c99f23a4c7 | ||
|
|
274c3886f5 | ||
|
|
95db29158c | ||
|
|
872680a737 | ||
|
|
0ba12f6301 | ||
|
|
248df9da51 | ||
|
|
702408d488 | ||
|
|
dd1506f17a | ||
|
|
7fb5c741ad | ||
|
|
aa61ab2b6e | ||
|
|
7a1e20d732 | ||
|
|
c1a5e187b6 | ||
|
|
57d3f788e6 | ||
|
|
ca73c3ad90 | ||
|
|
aec367dcc7 | ||
|
|
56e7c13cb1 | ||
|
|
3b4f384bfd | ||
|
|
d1a20da7e0 | ||
|
|
4b3533f717 | ||
|
|
6535adef44 | ||
|
|
f69cde1469 | ||
|
|
59643b762f | ||
|
|
ec69c1411b | ||
|
|
ad28d4510d | ||
|
|
111e500928 | ||
|
|
b5cb129ff0 | ||
|
|
8a1586e5e8 | ||
|
|
e1959211de | ||
|
|
53494fa141 | ||
|
|
7fdab3a2ab | ||
|
|
cb749c2fba | ||
|
|
453efe9998 | ||
|
|
2948b78cbe | ||
|
|
5a9f993df8 | ||
|
|
4936cc76d0 | ||
|
|
4a15467ff5 | ||
|
|
bb09b32885 | ||
|
|
4c9372747c | ||
|
|
1c04c22617 | ||
|
|
675146d20c | ||
|
|
56ee6af9de | ||
|
|
926a0b3717 | ||
|
|
eae28ce76d | ||
|
|
99c34539e6 | ||
|
|
0dfafdd874 | ||
|
|
a8cb40831e | ||
|
|
3b1bed9345 | ||
|
|
f18b81bdb7 | ||
|
|
4a61fe372e | ||
|
|
0bd0543d10 | ||
|
|
06a3a04840 | ||
|
|
3eb379b1e9 | ||
|
|
52e7707f81 | ||
|
|
28d2424cd5 | ||
|
|
187e9f17fc | ||
|
|
b9776a1017 | ||
|
|
3fbf28eea9 | ||
|
|
99e7ec7dc2 | ||
|
|
a9b4512ce6 | ||
|
|
feb9ee238d | ||
|
|
748f7ca889 | ||
|
|
87a469f264 | ||
|
|
59e0175d65 | ||
|
|
7595f5317d | ||
|
|
3ba4e87f1d | ||
|
|
0a7aec26cc | ||
|
|
d70b7e12ef | ||
|
|
54d2591391 | ||
|
|
4f70695ceb | ||
|
|
8f0a151018 | ||
|
|
cd64460e62 | ||
|
|
db3c0622c6 | ||
|
|
ac0092b178 | ||
|
|
fab32e9e93 | ||
|
|
3d1129b0f3 | ||
|
|
89b2ff74f3 | ||
|
|
a43e8fc461 | ||
|
|
98d3fddfcc | ||
|
|
ff36fff091 | ||
|
|
7d02108c8b | ||
|
|
593f37d5fe | ||
|
|
7abe9c627b | ||
|
|
a0923c3c9b | ||
|
|
439042ab95 | ||
|
|
113e505c52 | ||
|
|
f01e0ae194 | ||
|
|
5540cef257 | ||
|
|
ab16ee493f | ||
|
|
b1a1d1029c | ||
|
|
a46b4aa768 | ||
|
|
245fe4b525 | ||
|
|
c5a9926652 | ||
|
|
7032862a65 | ||
|
|
2012229c46 | ||
|
|
a6672ddbfc | ||
|
|
8dd2147638 | ||
|
|
7a29a1b680 | ||
|
|
f4983f7862 | ||
|
|
488bd1087a | ||
|
|
d557136295 | ||
|
|
87f4b639a0 | ||
|
|
dbb9379a2e | ||
|
|
fa86ae68ea | ||
|
|
f82fddc8fe | ||
|
|
e6f0b321cb | ||
|
|
41fef58450 | ||
|
|
268026629d | ||
|
|
b95fa1868a | ||
|
|
d2b7376e37 | ||
|
|
8d6eac63e2 | ||
|
|
b8fc2dcb64 | ||
|
|
671f934934 | ||
|
|
58ff6554fc | ||
|
|
cad6b221d9 | ||
|
|
c02d5a9794 | ||
|
|
aac71277e3 | ||
|
|
aa61be7bf5 | ||
|
|
3a4f796c79 | ||
|
|
f070cdc12d | ||
|
|
e14911d4d5 | ||
|
|
aa666ad025 | ||
|
|
cd55c2666f | ||
|
|
0dc6032209 | ||
|
|
f7630c28d6 | ||
|
|
08d9beb6b8 | ||
|
|
5808239416 | ||
|
|
e2f400340b | ||
|
|
5854b24c44 | ||
|
|
7adaf44fb3 | ||
|
|
0bd74056a0 | ||
|
|
bbeea7a2ee | ||
|
|
e3548737f1 | ||
|
|
75d7656824 | ||
|
|
c292f18915 | ||
|
|
9636e7c700 | ||
|
|
eb8f7840cc | ||
|
|
180d79f49e | ||
|
|
29cb1c96b1 | ||
|
|
f61432fc52 | ||
|
|
8e7dbf4640 | ||
|
|
d2528faf3a | ||
|
|
c1a3fe66ef | ||
|
|
ecb4c114a2 | ||
|
|
1ecaf5ae6e | ||
|
|
e88b7f00b3 | ||
|
|
91aaabc7fb | ||
|
|
7414c29593 | ||
|
|
79ae5b7bff | ||
|
|
486a3a07f5 | ||
|
|
02d44eb0e7 | ||
|
|
3aff7bd956 | ||
|
|
cd6a6cc55a | ||
|
|
d0a30a1779 | ||
|
|
a5fb30d7c7 | ||
|
|
ba4e813a35 | ||
|
|
f6d4caee07 | ||
|
|
d3a00e726f | ||
|
|
d71c0695f4 | ||
|
|
394c91c50c | ||
|
|
71c35eaf4c | ||
|
|
70790d0e6a | ||
|
|
8fbc00a417 | ||
|
|
080bf7f540 | ||
|
|
9d988294f3 | ||
|
|
a712013507 | ||
|
|
0aab22975a | ||
|
|
fde6555e4d | ||
|
|
748e76e187 | ||
|
|
55a627f718 | ||
|
|
a19cbff12f | ||
|
|
d5e098a6cd | ||
|
|
3f9fc28e3c | ||
|
|
0bf4081d71 | ||
|
|
de3e392aa3 | ||
|
|
44a5f51c93 | ||
|
|
9c8ea3c2e5 | ||
|
|
4208d1e036 | ||
|
|
154198fd5c | ||
|
|
0ad0d79a54 | ||
|
|
966538e47b | ||
|
|
181a1e7248 | ||
|
|
1a13e03a08 | ||
|
|
0fa0568f24 | ||
|
|
d7e76bee6d | ||
|
|
77dcf4da62 | ||
|
|
6eb0384fa6 | ||
|
|
5af5a6c5b0 | ||
|
|
0e4c833be0 | ||
|
|
a2e4a2c78a | ||
|
|
f4ab2b590f | ||
|
|
80b3d7ab6b | ||
|
|
b23fbd4507 | ||
|
|
5b56ff6d55 | ||
|
|
143b691c61 | ||
|
|
88e4c88a87 | ||
|
|
c9e06ad2d2 | ||
|
|
bd814f729d | ||
|
|
8c8b50b83a | ||
|
|
e9f4790b40 | ||
|
|
ac1149b873 | ||
|
|
51b6f828ed | ||
|
|
b97a99d4e2 | ||
|
|
6ed661aa13 | ||
|
|
f3d46ed767 | ||
|
|
3bde55c4df | ||
|
|
dd591ec258 | ||
|
|
4ce035b554 | ||
|
|
cdc451fdca | ||
|
|
0680d16708 | ||
|
|
1f1792522e | ||
|
|
db53a7a3dc | ||
|
|
9362196d4c | ||
|
|
527d387640 | ||
|
|
5f9dda4bbb | ||
|
|
6a991f3ab5 | ||
|
|
4c023f792c | ||
|
|
6b5f7d37ca | ||
|
|
87158b1e7a | ||
|
|
0b132b7d10 | ||
|
|
cd4cb70896 | ||
|
|
633470b0d6 | ||
|
|
1930090044 | ||
|
|
ccefb3613e | ||
|
|
9e79350b9e | ||
|
|
9092c2325a | ||
|
|
5cd3a6dbaf | ||
|
|
9934a4ea3a | ||
|
|
3468a88268 | ||
|
|
4d916cd5cc | ||
|
|
24f13e2afe | ||
|
|
ad29c009b2 | ||
|
|
d87955adb6 | ||
|
|
07aa71bb9d | ||
|
|
ea3ef8b60f | ||
|
|
a33df54ed3 | ||
|
|
3107f16768 | ||
|
|
230436be8a | ||
|
|
7fefeee145 | ||
|
|
db836a9dc3 | ||
|
|
ba22f0ed3c | ||
|
|
a731b1b569 | ||
|
|
8bb3530928 | ||
|
|
2567ca50e7 | ||
|
|
8d00b926c4 | ||
|
|
7636d6874b | ||
|
|
66c098c746 | ||
|
|
7534bb3c7e | ||
|
|
200fc1a563 | ||
|
|
c961eb0ca0 | ||
|
|
d2086d100e | ||
|
|
2a33705cc6 | ||
|
|
4b48f75aed | ||
|
|
2bbc3e5834 | ||
|
|
9e14b5e70f | ||
|
|
161a4cd511 | ||
|
|
2cf97b5f77 | ||
|
|
0d6df6e6c1 | ||
|
|
6a4edbf73d | ||
|
|
d6ffb890e3 | ||
|
|
5b83456e6d | ||
|
|
3ad6784961 | ||
|
|
3fa1ed1928 | ||
|
|
d27466d49e | ||
|
|
d9677fe7c4 | ||
|
|
fa7421b1b0 | ||
|
|
602bccae2a | ||
|
|
0acd3b427f | ||
|
|
f35f04851e | ||
|
|
cb4717b770 | ||
|
|
68e69790cf | ||
|
|
2bd93469bb | ||
|
|
6fc37d48ff | ||
|
|
df8fbe6440 | ||
|
|
9eee7883c5 | ||
|
|
7de08f52df | ||
|
|
b4bf5415bc | ||
|
|
c8e22fe2e3 | ||
|
|
af89cee619 | ||
|
|
216f3620e1 | ||
|
|
ae97e42d11 | ||
|
|
c6ad677e2b | ||
|
|
e1a1f0bbdb | ||
|
|
efaf5a1553 | ||
|
|
9c104beeba | ||
|
|
31bad3191d | ||
|
|
1089497c08 | ||
|
|
73bc2d06a7 | ||
|
|
040d913693 | ||
|
|
52a488f967 | ||
|
|
7511a676d4 | ||
|
|
6b7964f5d9 | ||
|
|
98e843aa0d | ||
|
|
c364a743ae | ||
|
|
b36c5fa60f | ||
|
|
bd0ece8020 | ||
|
|
9b15254880 | ||
|
|
4f039b9708 | ||
|
|
2ab823239c | ||
|
|
78d4f2e479 | ||
|
|
6513fc6ed4 | ||
|
|
e41ddedce7 | ||
|
|
88220cabee | ||
|
|
33252c1775 | ||
|
|
57553bbda1 | ||
|
|
4a02bf529d | ||
|
|
e55b1f8ff9 | ||
|
|
73314ee985 | ||
|
|
5196dbe9af | ||
|
|
68454e914b | ||
|
|
d0924e246f | ||
|
|
59a277f0c7 | ||
|
|
5ef0dc5505 | ||
|
|
08434c36ee | ||
|
|
2f288dc92b | ||
|
|
421a21aced | ||
|
|
98a3a1a372 | ||
|
|
da65e98030 | ||
|
|
7bbbd3c849 | ||
|
|
0481bb02af | ||
|
|
c47d9c95c7 | ||
|
|
a122159268 | ||
|
|
474064df3a | ||
|
|
1f65a626e4 | ||
|
|
04800407d7 | ||
|
|
f306184b53 | ||
|
|
c1a8844f27 | ||
|
|
d4e118f331 | ||
|
|
2900a9672a | ||
|
|
e8ba94942d | ||
|
|
b3a96aed74 | ||
|
|
0db2068344 | ||
|
|
6da0d023ff | ||
|
|
88d522501f | ||
|
|
6b31e6cdc2 | ||
|
|
1ef8668c4d | ||
|
|
6721baf293 | ||
|
|
f4ec9c277f | ||
|
|
c9d897edd1 | ||
|
|
ef07edf8c3 | ||
|
|
36d49a6e1e | ||
|
|
f4ee8d0a15 | ||
|
|
71e414ae5c | ||
|
|
acda11f110 | ||
|
|
a96935bfb5 | ||
|
|
b74b4ce244 | ||
|
|
638dac9cb6 | ||
|
|
249af4c397 | ||
|
|
4130dbea91 | ||
|
|
ee57cd7d3b | ||
|
|
8939b196c0 | ||
|
|
f1b5ec6481 | ||
|
|
50ace8c6a4 | ||
|
|
264e81b4a0 | ||
|
|
282b80fe24 | ||
|
|
547e5dae52 | ||
|
|
5dcdaf459f | ||
|
|
ab89f59dd1 | ||
|
|
98451759f0 | ||
|
|
b22ddaf13e | ||
|
|
7eaf598089 | ||
|
|
3d7b504504 | ||
|
|
4dc8693216 | ||
|
|
ac472393aa | ||
|
|
477fd7d2e9 | ||
|
|
468723ad92 | ||
|
|
d58ad1e0a2 | ||
|
|
8c94245102 | ||
|
|
b4f697e052 | ||
|
|
428ffbf705 | ||
|
|
d25a903556 | ||
|
|
3db906de10 | ||
|
|
486d3170e2 | ||
|
|
15bdba85bb | ||
|
|
db1a2538cb | ||
|
|
3d61fab8b6 | ||
|
|
644f2d9a89 | ||
|
|
46a4060814 | ||
|
|
5c26c23619 | ||
|
|
60bdafca94 | ||
|
|
59cd3defce | ||
|
|
f8d28ba30a | ||
|
|
f1b96b29a7 | ||
|
|
6670e37050 | ||
|
|
2d6254b427 | ||
|
|
c287294890 | ||
|
|
fc51e5caff | ||
|
|
e32ddd07ea | ||
|
|
6b47cf54e2 | ||
|
|
40c3e7a417 | ||
|
|
4bfb543419 | ||
|
|
8c9d78337e | ||
|
|
0a132351a8 | ||
|
|
ded43f7e7b | ||
|
|
cf91a8b62c | ||
|
|
948659f3cd | ||
|
|
c7e0015ffd | ||
|
|
2fd671f513 | ||
|
|
5fa86fad5e | ||
|
|
c6d90cdb17 | ||
|
|
dcf44aa134 | ||
|
|
7de8e180dc | ||
|
|
7fee6ad52f | ||
|
|
157e459a2e | ||
|
|
50b2b0dc57 | ||
|
|
4f76295438 | ||
|
|
d4c14ffb74 | ||
|
|
111d20a1cf | ||
|
|
19840663a8 | ||
|
|
4b7527bf0c | ||
|
|
0a5bfa1ccb | ||
|
|
21e1d303aa | ||
|
|
e0d5641d26 | ||
|
|
529780f2c9 | ||
|
|
19f1093d2c | ||
|
|
5807e10286 | ||
|
|
0a1ad9f4d6 | ||
|
|
2a3e2e315e | ||
|
|
23c868b646 | ||
|
|
ed8af54324 | ||
|
|
d8c3ba1e36 | ||
|
|
17f45c6dab | ||
|
|
86745c5962 | ||
|
|
ca72c7a51f | ||
|
|
012c7cede9 | ||
|
|
15a55e996b | ||
|
|
cb50b2011f | ||
|
|
04c45756b9 | ||
|
|
8d08d81f45 | ||
|
|
d77403c403 | ||
|
|
1d94069144 | ||
|
|
6e10d6bcd7 | ||
|
|
7453431bcd | ||
|
|
2f953061b3 | ||
|
|
67610b935e | ||
|
|
09e5ee9e26 | ||
|
|
54c5f9b041 | ||
|
|
3da45dc884 | ||
|
|
afb2b5e383 | ||
|
|
e23dc72856 | ||
|
|
b86d4b5a37 | ||
|
|
2024c95be5 | ||
|
|
d51debf736 | ||
|
|
70b4b5fb2d | ||
|
|
e32fbc1d79 | ||
|
|
be43279574 | ||
|
|
900e556369 | ||
|
|
09aca188fe | ||
|
|
fc9565e13c | ||
|
|
e7f7321393 | ||
|
|
ddbe1c4546 | ||
|
|
e3fa31b701 | ||
|
|
bfe8a22b48 | ||
|
|
dab779cbef | ||
|
|
66104bb753 | ||
|
|
37c69f991f | ||
|
|
610460b3e4 | ||
|
|
43750e2bad | ||
|
|
a7df100e56 | ||
|
|
4afb9d86c7 | ||
|
|
1b7a7060a8 | ||
|
|
1f6b94003e | ||
|
|
2f1a7821b0 | ||
|
|
1ec887092f | ||
|
|
71afb088b5 | ||
|
|
01ca501607 | ||
|
|
ba34b37f8a | ||
|
|
62ff954558 | ||
|
|
fead9a48eb | ||
|
|
10e842e457 | ||
|
|
4931e6707a | ||
|
|
036dca33be | ||
|
|
74565d7995 | ||
|
|
65ab09e63c | ||
|
|
8e305b0e21 | ||
|
|
ff66e07c65 | ||
|
|
4138747fc3 | ||
|
|
87c654b43d | ||
|
|
d0062b7e22 | ||
|
|
1d3900b86c | ||
|
|
b799ce34f6 | ||
|
|
8d6cf9581b | ||
|
|
1a5853766a | ||
|
|
cd284304cd | ||
|
|
363e4152a5 | ||
|
|
0ce4ddb874 | ||
|
|
3c574b97cb | ||
|
|
b7b8f26992 | ||
|
|
4eca5069b3 | ||
|
|
03618f547d | ||
|
|
12477c07d6 | ||
|
|
0fb2d445b0 | ||
|
|
43c5482542 | ||
|
|
dc8e7a86b0 | ||
|
|
8f9e117169 | ||
|
|
2a69740559 | ||
|
|
dd0e548353 | ||
|
|
0eb19a287f | ||
|
|
14de8ffd59 | ||
|
|
ac6249f541 | ||
|
|
5af2a392ea | ||
|
|
edfdca6e5f | ||
|
|
c2ed9958c7 | ||
|
|
fddae7d0e1 | ||
|
|
fa729e220c | ||
|
|
c917fb6a57 | ||
|
|
cf2e026f75 | ||
|
|
c340544152 | ||
|
|
00c7cae923 | ||
|
|
80cff6912b | ||
|
|
b76e1d4167 | ||
|
|
f7759d2547 | ||
|
|
bf0e984e6c | ||
|
|
b33dd62dc5 | ||
|
|
d51e3979ff | ||
|
|
01275fce9f | ||
|
|
261b18f4eb | ||
|
|
fdc20aa84f | ||
|
|
a3382c2d5b | ||
|
|
52a9a2f018 | ||
|
|
cfea1c16bc | ||
|
|
180c07cf61 | ||
|
|
f880db4902 |
5
.crowdin.yml
Normal file
5
.crowdin.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
commit_message: "[ci skip]"
|
||||
escape_special_characters: 0
|
||||
files:
|
||||
- source: /main/ui/src/main/resources/i18n/strings.properties
|
||||
translation: /main/ui/src/main/resources/i18n/strings_%two_letters_code%.properties
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
github: [overheadhunter, tobihagemann] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
|
||||
1
.github/stale.yml
vendored
1
.github/stale.yml
vendored
@@ -6,6 +6,7 @@ daysUntilClose: 30
|
||||
exemptLabels:
|
||||
- type:security-issue # never close automatically
|
||||
- state:awaiting-response # handled by different bot
|
||||
- state:blocked
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
# Label to use when marking an issue as stale
|
||||
|
||||
30
.idea/compiler.xml
generated
30
.idea/compiler.xml
generated
@@ -7,26 +7,34 @@
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.22.1/dagger-compiler-2.22.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.22.1/dagger-2.22.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.25.2/dagger-compiler-2.25.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.25.2/dagger-2.25.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.22.1/dagger-producers-2.22.1.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/dagger/dagger-producers/2.25.2/dagger-producers-2.25.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/27.1-jre/guava-27.1-jre.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/2.5.2/checker-qual-2.5.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.2.0/error_prone_annotations-2.2.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/codehaus/mojo/animal-sniffer-annotations/1.14/animal-sniffer-annotations-1.14.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.22.1/dagger-spi-2.22.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.3/checker-compat-qual-2.5.3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.25.2/dagger-spi-2.25.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.11.1/javapoet-1.11.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.3.50/kotlin-stdlib-1.3.50.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.3.50/kotlin-stdlib-common-1.3.50.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.1.0/kotlinx-metadata-jvm-0.1.0.jar" />
|
||||
</processorPath>
|
||||
<module name="keychain" />
|
||||
<module name="launcher" />
|
||||
<module name="commons" />
|
||||
<module name="keychain" />
|
||||
<module name="ui" />
|
||||
<module name="launcher" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
|
||||
2
.idea/runConfigurations/Cryptomator_macOS.xml
generated
2
.idea/runConfigurations/Cryptomator_macOS.xml
generated
@@ -2,8 +2,8 @@
|
||||
<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/" -Xss2m -Xmx512m" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<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/" -Xss2m -Xmx512m -ea" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
|
||||
21
.travis.yml
21
.travis.yml
@@ -1,4 +1,4 @@
|
||||
dist: xenial
|
||||
dist: bionic
|
||||
language: java
|
||||
sudo: false
|
||||
jdk:
|
||||
@@ -17,24 +17,25 @@ addons:
|
||||
- haveged
|
||||
install:
|
||||
- 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
|
||||
- mvn -fmain/pom.xml clean install -DskipTests -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN 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
|
||||
before_script:
|
||||
- |
|
||||
if [[ -n "$TRAVIS_TAG" ]]; then
|
||||
mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=$TRAVIS_TAG
|
||||
else
|
||||
mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=SNAPSHOT-$(echo $TRAVIS_COMMIT | head -c7)
|
||||
fi
|
||||
script:
|
||||
- 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
|
||||
- curl -o ~/codacy-coverage-reporter.jar https://oss.sonatype.org/service/local/repositories/releases/content/com/codacy/codacy-coverage-reporter/6.0.7/codacy-coverage-reporter-6.0.7-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:
|
||||
- |
|
||||
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
|
||||
- mvn -fmain/pom.xml package -Prelease -DskipTests
|
||||
- export TODAY=`date +'%Y-%m-%d'`; envsubst '$TRAVIS_TAG $TODAY' < .travis-deploy-release.tmpl.json > .travis-deploy-release.json
|
||||
deploy:
|
||||
- provider: bintray # SNAPSHOTS
|
||||
|
||||
21
README.md
21
README.md
@@ -1,14 +1,29 @@
|
||||

|
||||
[](https://cryptomator.org/)
|
||||
|
||||
[](https://travis-ci.org/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://translate.cryptomator.org/)
|
||||
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
||||
[](https://community.cryptomator.org)
|
||||
|
||||
Multi-platform transparent client-side encryption of your files in the cloud.
|
||||
## Supporting Cryptomator
|
||||
|
||||
Cryptomator is provided free of charge as an open-source project despite the high development effort and is therefore dependent on donations. If you are also interested in further development, we offer you the opportunity to support us:
|
||||
|
||||
- [One-time or recurring donation via Cryptomator's website.](https://cryptomator.org/#donate)
|
||||
- [Become a sponsor via Cryptomator's sponsors website.](https://cryptomator.org/sponsors/)
|
||||
|
||||
### Silver Sponsors
|
||||
|
||||
[](https://thebestvpn.com/)
|
||||
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Cryptomator offers multi-platform transparent client-side encryption of your files in the cloud.
|
||||
|
||||
Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator.org/) or clone and build Cryptomator using Maven (instructions below).
|
||||
|
||||
|
||||
@@ -14,6 +14,13 @@
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
<include>ffi-version.txt</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
|
||||
@@ -14,6 +14,13 @@
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
<include>ffi-version.txt</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
|
||||
@@ -14,6 +14,13 @@
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
<include>ffi-version.txt</include>
|
||||
</includes>
|
||||
<outputDirectory>libs</outputDirectory>
|
||||
</fileSet>
|
||||
<fileSet>
|
||||
<directory>target/</directory>
|
||||
<includes>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.12</version>
|
||||
<version>1.5.0-beta1</version>
|
||||
</parent>
|
||||
<artifactId>buildkit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
@@ -39,6 +39,7 @@
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
<includes>
|
||||
<include>version.txt</include>
|
||||
<include>ffi-version.txt</include>
|
||||
<include>launcher-mac.sh</include>
|
||||
<include>launcher-linux.sh</include>
|
||||
<include>launcher-win.bat</include>
|
||||
|
||||
1
main/buildkit/src/main/resources/ffi-version.txt
Normal file
1
main/buildkit/src/main/resources/ffi-version.txt
Normal file
@@ -0,0 +1 @@
|
||||
${cryptomator.jni.version}
|
||||
@@ -4,35 +4,64 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.12</version>
|
||||
<version>1.5.0-beta1</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator Commons</name>
|
||||
<description>Shared utilities</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptofs</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<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>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>jni</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JavaFx -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-base</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-graphics</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Libs -->
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
<artifactId>easybind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Google -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons -->
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
<artifactId>easybind</artifactId>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DI -->
|
||||
|
||||
@@ -5,22 +5,89 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common;
|
||||
|
||||
import java.util.Comparator;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.SettingsProvider;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultComponent;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
@Module(subcomponents = {VaultComponent.class})
|
||||
public abstract class CommonsModule {
|
||||
|
||||
@Module
|
||||
public class CommonsModule {
|
||||
private static final int NUM_SCHEDULER_THREADS = 4;
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("SemVer")
|
||||
Comparator<String> providesSemVerComparator() {
|
||||
static Comparator<String> providesSemVerComparator() {
|
||||
return new SemVerComparator();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Settings provideSettings(SettingsProvider settingsProvider) {
|
||||
return settingsProvider.get();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static ObservableList<Vault> provideVaultList(VaultListManager vaultListManager) {
|
||||
return vaultListManager.getVaultList();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static 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("Background Thread " + threadNumber.getAndIncrement());
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
shutdownTaskScheduler.accept(executorService::shutdown);
|
||||
return executorService;
|
||||
}
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract ExecutorService bindExecutorService(ScheduledExecutorService executor);
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Binding<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
|
||||
return Bindings.createObjectBinding(() -> {
|
||||
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
|
||||
return InetSocketAddress.createUnresolved(host, settings.port().intValue());
|
||||
}, settings.port());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static WebDavServer provideWebDavServer(Binding<InetSocketAddress> serverSocketAddressBinding) {
|
||||
WebDavServer server = WebDavServer.create();
|
||||
// no need to unsubscribe eventually, because server is a singleton
|
||||
EasyBind.subscribe(serverSocketAddressBinding, server::bind);
|
||||
return server;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public class Environment {
|
||||
|
||||
@Inject
|
||||
public Environment() {
|
||||
LOG.debug("java.library.path: {}", System.getProperty("java.library.path"));
|
||||
LOG.debug("user.language: {}", System.getProperty("user.language"));
|
||||
LOG.debug("user.region: {}", System.getProperty("user.region"));
|
||||
LOG.debug("logback.configurationFile: {}", System.getProperty("logback.configurationFile"));
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019 Skymatic GmbH.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import org.cryptomator.jni.JniFunctions;
|
||||
import org.cryptomator.jni.MacFunctions;
|
||||
import org.cryptomator.jni.WinFunctions;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
@Module
|
||||
public class JniModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Optional<MacFunctions> provideOptionalMacFunctions() {
|
||||
return JniFunctions.macFunctions();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Optional<WinFunctions> provideOptionalWinFunctions() {
|
||||
return JniFunctions.winFunctions();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,11 +8,16 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -22,20 +27,26 @@ public class Settings {
|
||||
public static final int MAX_PORT = 65535;
|
||||
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
|
||||
public static final boolean DEFAULT_CHECK_FOR_UDPATES = false;
|
||||
public static final boolean DEFAULT_START_HIDDEN = 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 WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
|
||||
public static final boolean DEFAULT_DEBUG_MODE = false;
|
||||
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
|
||||
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
||||
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
|
||||
|
||||
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
|
||||
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
||||
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
|
||||
private final BooleanProperty startHidden = new SimpleBooleanProperty(DEFAULT_START_HIDDEN);
|
||||
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 ObjectProperty<WebDavUrlScheme> preferredGvfsScheme = new SimpleObjectProperty<>(DEFAULT_GVFS_SCHEME);
|
||||
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
||||
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
||||
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
||||
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
||||
|
||||
private Consumer<Settings> saveCmd;
|
||||
|
||||
@@ -43,21 +54,24 @@ public class Settings {
|
||||
* Package-private constructor; use {@link SettingsProvider}.
|
||||
*/
|
||||
Settings() {
|
||||
directories.addListener((ListChangeListener.Change<? extends VaultSettings> change) -> this.save());
|
||||
directories.addListener(this::somethingChanged);
|
||||
askedForUpdateCheck.addListener(this::somethingChanged);
|
||||
checkForUpdates.addListener(this::somethingChanged);
|
||||
startHidden.addListener(this::somethingChanged);
|
||||
port.addListener(this::somethingChanged);
|
||||
numTrayNotifications.addListener(this::somethingChanged);
|
||||
preferredGvfsScheme.addListener(this::somethingChanged);
|
||||
debugMode.addListener(this::somethingChanged);
|
||||
preferredVolumeImpl.addListener(this::somethingChanged);
|
||||
theme.addListener(this::somethingChanged);
|
||||
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||
}
|
||||
|
||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||
this.saveCmd = saveCmd;
|
||||
}
|
||||
|
||||
private void somethingChanged(ObservableValue<?> observable, Object oldValue, Object newValue) {
|
||||
|
||||
private void somethingChanged(@SuppressWarnings("unused") Observable observable) {
|
||||
this.save();
|
||||
}
|
||||
|
||||
@@ -80,6 +94,10 @@ public class Settings {
|
||||
public BooleanProperty checkForUpdates() {
|
||||
return checkForUpdates;
|
||||
}
|
||||
|
||||
public BooleanProperty startHidden() {
|
||||
return startHidden;
|
||||
}
|
||||
|
||||
public IntegerProperty port() {
|
||||
return port;
|
||||
@@ -89,7 +107,7 @@ public class Settings {
|
||||
return numTrayNotifications;
|
||||
}
|
||||
|
||||
public StringProperty preferredGvfsScheme() {
|
||||
public ObjectProperty<WebDavUrlScheme> preferredGvfsScheme() {
|
||||
return preferredGvfsScheme;
|
||||
}
|
||||
|
||||
@@ -101,4 +119,11 @@ public class Settings {
|
||||
return preferredVolumeImpl;
|
||||
}
|
||||
|
||||
public ObjectProperty<UiTheme> theme() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
|
||||
return userInterfaceOrientation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -29,11 +30,14 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
writeVaultSettingsArray(out, value.getDirectories());
|
||||
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
|
||||
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
|
||||
out.name("startHidden").value(value.startHidden().get());
|
||||
out.name("port").value(value.port().get());
|
||||
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
||||
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get());
|
||||
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get().name());
|
||||
out.name("debugMode").value(value.debugMode().get());
|
||||
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
||||
out.name("theme").value(value.theme().get().name());
|
||||
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
@@ -62,6 +66,9 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
case "checkForUpdatesEnabled":
|
||||
settings.checkForUpdates().set(in.nextBoolean());
|
||||
break;
|
||||
case "startHidden":
|
||||
settings.startHidden().set(in.nextBoolean());
|
||||
break;
|
||||
case "port":
|
||||
settings.port().set(in.nextInt());
|
||||
break;
|
||||
@@ -69,7 +76,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
settings.numTrayNotifications().set(in.nextInt());
|
||||
break;
|
||||
case "preferredGvfsScheme":
|
||||
settings.preferredGvfsScheme().set(in.nextString());
|
||||
settings.preferredGvfsScheme().set(parseWebDavUrlSchemePrefix(in.nextString()));
|
||||
break;
|
||||
case "debugMode":
|
||||
settings.debugMode().set(in.nextBoolean());
|
||||
@@ -77,6 +84,12 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
case "preferredVolumeImpl":
|
||||
settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
||||
break;
|
||||
case "theme":
|
||||
settings.theme().set(parseUiTheme(in.nextString()));
|
||||
break;
|
||||
case "uiOrientation":
|
||||
settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
@@ -90,12 +103,40 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
|
||||
private VolumeImpl parsePreferredVolumeImplName(String nioAdapterName) {
|
||||
try {
|
||||
return VolumeImpl.valueOf(nioAdapterName);
|
||||
return VolumeImpl.valueOf(nioAdapterName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Invalid volume type {}. Defaulting to {}.", nioAdapterName, Settings.DEFAULT_PREFERRED_VOLUME_IMPL);
|
||||
return Settings.DEFAULT_PREFERRED_VOLUME_IMPL;
|
||||
}
|
||||
}
|
||||
|
||||
private WebDavUrlScheme parseWebDavUrlSchemePrefix(String webDavUrlSchemeName) {
|
||||
try {
|
||||
return WebDavUrlScheme.valueOf(webDavUrlSchemeName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Invalid WebDAV url scheme {}. Defaulting to {}.", webDavUrlSchemeName, Settings.DEFAULT_GVFS_SCHEME);
|
||||
return Settings.DEFAULT_GVFS_SCHEME;
|
||||
}
|
||||
}
|
||||
|
||||
private UiTheme parseUiTheme(String uiThemeName) {
|
||||
try {
|
||||
return UiTheme.valueOf(uiThemeName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Invalid ui theme {}. Defaulting to {}.", uiThemeName, Settings.DEFAULT_THEME);
|
||||
return Settings.DEFAULT_THEME;
|
||||
}
|
||||
}
|
||||
|
||||
private NodeOrientation parseUiOrientation(String uiOrientationName) {
|
||||
try {
|
||||
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Invalid ui orientation {}. Defaulting to {}.", uiOrientationName, Settings.DEFAULT_USER_INTERFACE_ORIENTATION);
|
||||
return Settings.DEFAULT_USER_INTERFACE_ORIENTATION;
|
||||
}
|
||||
}
|
||||
|
||||
private List<VaultSettings> readVaultSettingsArray(JsonReader in) throws IOException {
|
||||
List<VaultSettings> result = new ArrayList<>();
|
||||
in.beginArray();
|
||||
|
||||
@@ -17,7 +17,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -38,10 +37,11 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Singleton
|
||||
public class SettingsProvider implements Provider<Settings> {
|
||||
public class SettingsProvider implements Supplier<Settings> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
|
||||
private static final long SAVE_DELAY_MS = 1000;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
public enum UiTheme {
|
||||
LIGHT("Light"),
|
||||
DARK("Dark");
|
||||
// CUSTOM("Custom (%s)");
|
||||
|
||||
private String displayName;
|
||||
|
||||
UiTheme(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
@@ -23,6 +24,7 @@ import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -36,6 +38,8 @@ public class VaultSettings {
|
||||
public static final boolean DEFAULT_USES_INDIVIDUAL_MOUNTPATH = false;
|
||||
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
||||
public static final String DEFAULT_MOUNT_FLAGS = "";
|
||||
|
||||
private static final Random RNG = new Random();
|
||||
|
||||
private final String id;
|
||||
private final ObjectProperty<Path> path = new SimpleObjectProperty();
|
||||
@@ -69,20 +73,9 @@ public class VaultSettings {
|
||||
}
|
||||
|
||||
private static String generateId() {
|
||||
return asBase64String(nineBytesFrom(UUID.randomUUID()));
|
||||
}
|
||||
|
||||
private static String asBase64String(byte[] bytes) {
|
||||
byte[] base64Bytes = Base64.getUrlEncoder().encode(bytes);
|
||||
return new String(base64Bytes, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
private static byte[] nineBytesFrom(UUID uuid) {
|
||||
ByteBuffer uuidBuffer = ByteBuffer.allocate(9);
|
||||
uuidBuffer.putLong(uuid.getMostSignificantBits());
|
||||
uuidBuffer.put((byte) (uuid.getLeastSignificantBits() & 0xFF));
|
||||
uuidBuffer.flip();
|
||||
return uuidBuffer.array();
|
||||
byte[] randomBytes = new byte[9];
|
||||
RNG.nextBytes(randomBytes);
|
||||
return BaseEncoding.base64Url().encode(randomBytes);
|
||||
}
|
||||
|
||||
public static String normalizeMountName(String mountName) {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum WebDavUrlScheme {
|
||||
DAV("dav", "dav:// (Gnome, Nautilus, ...)"),
|
||||
WEBDAV("webdav", "webdav:// (KDE, Dolphin, ...)");
|
||||
|
||||
private final String prefix;
|
||||
private final String displayName;
|
||||
|
||||
WebDavUrlScheme(String prefix, String displayName) {this.prefix = prefix;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a WebDavUrlScheme by prefix.
|
||||
*
|
||||
* @param prefix Prefix of the WebDavUrlScheme
|
||||
* @return WebDavUrlScheme with the given <code>prefix</code>.
|
||||
* @throws IllegalArgumentException if not WebDavUrlScheme with the given <code>prefix</code> was found.
|
||||
*/
|
||||
public static WebDavUrlScheme forPrefix(String prefix) throws IllegalArgumentException {
|
||||
return Arrays.stream(values()) //
|
||||
.filter(impl -> impl.prefix.equals(prefix)) //
|
||||
.findAny() //
|
||||
.orElseThrow(IllegalArgumentException::new);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
@@ -30,6 +30,7 @@ public class DokanyVolume implements Volume {
|
||||
private final MountFactory mountFactory;
|
||||
private final WindowsDriveLetters windowsDriveLetters;
|
||||
private Mount mount;
|
||||
private Path mountPoint;
|
||||
|
||||
@Inject
|
||||
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, WindowsDriveLetters windowsDriveLetters) {
|
||||
@@ -45,19 +46,19 @@ public class DokanyVolume implements Volume {
|
||||
|
||||
@Override
|
||||
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException, IOException {
|
||||
Path mountPath = getMountPoint();
|
||||
this.mountPoint = determineMountPoint();
|
||||
String mountName = vaultSettings.mountName().get();
|
||||
try {
|
||||
this.mount = mountFactory.mount(fs.getPath("/"), mountPath, mountName, FS_TYPE_NAME, mountFlags.strip());
|
||||
this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, mountName, FS_TYPE_NAME, mountFlags.strip());
|
||||
} catch (MountFailedException e) {
|
||||
if (vaultSettings.getIndividualMountPath().isPresent()) {
|
||||
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPath);
|
||||
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPoint);
|
||||
}
|
||||
throw new VolumeException("Unable to mount Filesystem", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Path getMountPoint() throws VolumeException, IOException {
|
||||
private Path determineMountPoint() throws VolumeException, IOException {
|
||||
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
|
||||
if (optionalCustomMountPoint.isPresent()) {
|
||||
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
||||
@@ -68,8 +69,9 @@ public class DokanyVolume implements Volume {
|
||||
} else {
|
||||
//auto assign drive letter
|
||||
if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
|
||||
return windowsDriveLetters.getAvailableDriveLetters().iterator().next();
|
||||
return Path.of(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\");
|
||||
} else {
|
||||
//TODO: Error Handling
|
||||
throw new VolumeException("No free drive letter available.");
|
||||
}
|
||||
}
|
||||
@@ -99,6 +101,11 @@ public class DokanyVolume implements Volume {
|
||||
mount.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> getMountPoint() {
|
||||
return Optional.ofNullable(mountPoint);
|
||||
}
|
||||
|
||||
public static boolean isSupportedStatic() {
|
||||
return MountFactory.isApplicable();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
@@ -100,8 +100,7 @@ public class FuseVolume implements Volume {
|
||||
try {
|
||||
Mounter mounter = FuseMountFactory.getMounter();
|
||||
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
||||
.withFlags(splitFlags(mountFlags))
|
||||
.withMountPoint(mountPoint) //
|
||||
.withFlags(splitFlags(mountFlags)).withMountPoint(mountPoint) //
|
||||
.build();
|
||||
this.fuseMnt = mounter.mount(root, envVars);
|
||||
} catch (CommandFailedException e) {
|
||||
@@ -166,6 +165,11 @@ public class FuseVolume implements Volume {
|
||||
return FuseVolume.isSupportedStatic();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> getMountPoint() {
|
||||
return Optional.ofNullable(mountPoint);
|
||||
}
|
||||
|
||||
public static boolean isSupportedStatic() {
|
||||
return (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) && FuseMountFactory.isFuseSupported();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
@@ -6,14 +6,16 @@
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
@@ -39,36 +41,51 @@ import java.nio.file.NotDirectoryException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@PerVault
|
||||
public class Vault {
|
||||
|
||||
public static final Predicate<Vault> NOT_LOCKED = hasState(State.LOCKED).negate();
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
|
||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
|
||||
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
|
||||
|
||||
private final VaultSettings vaultSettings;
|
||||
private final Provider<Volume> volumeProvider;
|
||||
private final Supplier<String> defaultMountFlags;
|
||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem = new AtomicReference<>();
|
||||
private final ObjectProperty<State> state = new SimpleObjectProperty<State>(State.LOCKED);
|
||||
private final StringBinding defaultMountFlags;
|
||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
||||
private final ObjectProperty<VaultState> state;
|
||||
private final VaultStats stats;
|
||||
private final StringBinding displayableName;
|
||||
private final StringBinding displayablePath;
|
||||
private final BooleanBinding locked;
|
||||
private final BooleanBinding processing;
|
||||
private final BooleanBinding unlocked;
|
||||
private final BooleanBinding needsMigration;
|
||||
private final ObjectBinding<Path> accessPoint;
|
||||
|
||||
private Volume volume;
|
||||
|
||||
public enum State {
|
||||
LOCKED, PROCESSING, UNLOCKED
|
||||
}
|
||||
private volatile Volume volume;
|
||||
|
||||
@Inject
|
||||
Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags Supplier<String> defaultMountFlags) {
|
||||
Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, ObjectProperty<VaultState> state, VaultStats stats) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.volumeProvider = volumeProvider;
|
||||
this.defaultMountFlags = defaultMountFlags;
|
||||
this.cryptoFileSystem = cryptoFileSystem;
|
||||
this.state = state;
|
||||
this.stats = stats;
|
||||
this.displayableName = Bindings.createStringBinding(this::getDisplayableName, vaultSettings.path());
|
||||
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
||||
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
||||
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
||||
this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state);
|
||||
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
|
||||
this.accessPoint = Bindings.createObjectBinding(this::getAccessPoint, state);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
@@ -80,7 +97,7 @@ public class Vault {
|
||||
}
|
||||
|
||||
private CryptoFileSystem unlockCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
|
||||
List<FileSystemFlags> flags = new ArrayList<>();
|
||||
Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class);
|
||||
if (vaultSettings.usesReadOnlyMode().get()) {
|
||||
flags.add(FileSystemFlags.READONLY);
|
||||
}
|
||||
@@ -92,40 +109,16 @@ public class Vault {
|
||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||
}
|
||||
|
||||
public void create(CharSequence passphrase) throws IOException {
|
||||
if (!isValidVaultDirectory()) {
|
||||
CryptoFileSystemProvider.initialize(getPath(), MASTERKEY_FILENAME, passphrase);
|
||||
} else {
|
||||
throw new FileAlreadyExistsException(getPath().toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void changePassphrase(CharSequence oldPassphrase, CharSequence newPassphrase) throws IOException, InvalidPassphraseException {
|
||||
CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase);
|
||||
}
|
||||
|
||||
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
|
||||
Platform.runLater(() -> state.set(State.PROCESSING));
|
||||
try {
|
||||
if (vaultSettings.usesIndividualMountPath().get() && Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
|
||||
throw new NotDirectoryException("");
|
||||
}
|
||||
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
|
||||
volume = volumeProvider.get();
|
||||
volume.mount(fs, getMountFlags());
|
||||
Platform.runLater(() -> {
|
||||
state.set(State.UNLOCKED);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> state.set(State.LOCKED));
|
||||
throw e;
|
||||
if (vaultSettings.usesIndividualMountPath().get() && Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
|
||||
throw new NotDirectoryException("");
|
||||
}
|
||||
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
|
||||
volume = volumeProvider.get();
|
||||
volume.mount(fs, getEffectiveMountFlags());
|
||||
}
|
||||
|
||||
public synchronized void lock(boolean forced) throws Volume.VolumeException {
|
||||
Platform.runLater(() -> {
|
||||
state.set(State.PROCESSING);
|
||||
});
|
||||
if (forced && volume.supportsForcedUnmount()) {
|
||||
volume.unmountForced();
|
||||
} else {
|
||||
@@ -139,28 +132,6 @@ public class Vault {
|
||||
LOG.error("Error closing file system.", e);
|
||||
}
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
state.set(State.LOCKED);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ejects any mounted drives and locks this vault. no-op if this vault is currently locked.
|
||||
*/
|
||||
public void prepareForShutdown() {
|
||||
try {
|
||||
lock(false);
|
||||
} catch (Volume.VolumeException e) {
|
||||
if (volume.supportsForcedUnmount()) {
|
||||
try {
|
||||
lock(true);
|
||||
} catch (Volume.VolumeException e1) {
|
||||
LOG.warn("Failed to force lock vault.", e1);
|
||||
}
|
||||
} else {
|
||||
LOG.warn("Failed to gracefully lock vault.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reveal() throws Volume.VolumeException {
|
||||
@@ -168,21 +139,96 @@ public class Vault {
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
// Getter/Setter
|
||||
// *******************************************************************************/
|
||||
// Observable Properties
|
||||
// *******************************************************************************
|
||||
|
||||
public State getState() {
|
||||
return state.get();
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
public ObjectProperty<VaultState> stateProperty() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public static Predicate<Vault> hasState(State state) {
|
||||
return vault -> {
|
||||
return vault.getState() == state;
|
||||
};
|
||||
public VaultState getState() {
|
||||
return state.get();
|
||||
}
|
||||
|
||||
public void setState(VaultState value) {
|
||||
state.setValue(value);
|
||||
}
|
||||
|
||||
public BooleanBinding lockedProperty() {
|
||||
return locked;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return state.get() == VaultState.LOCKED;
|
||||
}
|
||||
|
||||
public BooleanBinding processingProperty() {
|
||||
return processing;
|
||||
}
|
||||
|
||||
public boolean isProcessing() {
|
||||
return state.get() == VaultState.PROCESSING;
|
||||
}
|
||||
|
||||
public BooleanBinding unlockedProperty() {
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
public boolean isUnlocked() {
|
||||
return state.get() == VaultState.UNLOCKED;
|
||||
}
|
||||
|
||||
public BooleanBinding needsMigrationProperty() {
|
||||
return needsMigration;
|
||||
}
|
||||
|
||||
public boolean isNeedsMigration() {
|
||||
return state.get() == VaultState.NEEDS_MIGRATION;
|
||||
}
|
||||
|
||||
public StringBinding displayableNameProperty() {
|
||||
return displayableName;
|
||||
}
|
||||
|
||||
public String getDisplayableName() {
|
||||
Path p = vaultSettings.path().get();
|
||||
return p.getFileName().toString();
|
||||
}
|
||||
|
||||
public ObjectBinding<Path> accessPointProperty() {
|
||||
return accessPoint;
|
||||
}
|
||||
|
||||
public Path getAccessPoint() {
|
||||
if (state.get() == VaultState.UNLOCKED) {
|
||||
assert volume != null;
|
||||
return volume.getMountPoint().orElse(Path.of(""));
|
||||
} else {
|
||||
return Path.of("");
|
||||
}
|
||||
}
|
||||
|
||||
public StringBinding displayablePathProperty() {
|
||||
return displayablePath;
|
||||
}
|
||||
|
||||
public String getDisplayablePath() {
|
||||
Path p = vaultSettings.path().get();
|
||||
if (p.startsWith(HOME_DIR)) {
|
||||
Path relativePath = HOME_DIR.relativize(p);
|
||||
String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/";
|
||||
return homePrefix + relativePath.toString();
|
||||
} else {
|
||||
return p.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
// Getter/Setter
|
||||
// *******************************************************************************/
|
||||
|
||||
public VaultStats getStats() {
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Observable[] observables() {
|
||||
@@ -197,106 +243,31 @@ public class Vault {
|
||||
return vaultSettings.path().getValue();
|
||||
}
|
||||
|
||||
public Binding<String> displayablePath() {
|
||||
Path homeDir = Paths.get(SystemUtils.USER_HOME);
|
||||
return EasyBind.map(vaultSettings.path(), p -> {
|
||||
if (p.startsWith(homeDir)) {
|
||||
Path relativePath = homeDir.relativize(p);
|
||||
String homePrefix = SystemUtils.IS_OS_WINDOWS ? "~\\" : "~/";
|
||||
return homePrefix + relativePath.toString();
|
||||
} else {
|
||||
return p.toString();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Directory name without preceeding path components and file extension
|
||||
*/
|
||||
public Binding<String> name() {
|
||||
return EasyBind.map(vaultSettings.path(), Path::getFileName).map(Path::toString);
|
||||
}
|
||||
|
||||
public boolean doesVaultDirectoryExist() {
|
||||
return Files.isDirectory(getPath());
|
||||
}
|
||||
|
||||
public boolean isValidVaultDirectory() {
|
||||
return CryptoFileSystemProvider.containsVault(getPath(), MASTERKEY_FILENAME);
|
||||
}
|
||||
|
||||
public long pollBytesRead() {
|
||||
CryptoFileSystem fs = cryptoFileSystem.get();
|
||||
if (fs != null) {
|
||||
return fs.getStats().pollBytesRead();
|
||||
} else {
|
||||
return 0l;
|
||||
}
|
||||
}
|
||||
|
||||
public long pollBytesWritten() {
|
||||
CryptoFileSystem fs = cryptoFileSystem.get();
|
||||
if (fs != null) {
|
||||
return fs.getStats().pollBytesWritten();
|
||||
} else {
|
||||
return 0l;
|
||||
}
|
||||
}
|
||||
|
||||
public String getCustomMountPath() {
|
||||
return vaultSettings.individualMountPath().getValueSafe();
|
||||
}
|
||||
|
||||
public void setCustomMountPath(String mountPath) {
|
||||
vaultSettings.individualMountPath().set(mountPath);
|
||||
}
|
||||
|
||||
public String getMountName() {
|
||||
return vaultSettings.mountName().get();
|
||||
}
|
||||
|
||||
public void setMountName(String mountName) throws IllegalArgumentException {
|
||||
if (StringUtils.isBlank(mountName)) {
|
||||
throw new IllegalArgumentException("mount name is empty");
|
||||
} else {
|
||||
vaultSettings.mountName().set(VaultSettings.normalizeMountName(mountName));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isHavingCustomMountFlags() {
|
||||
return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get());
|
||||
}
|
||||
|
||||
public String getMountFlags() {
|
||||
public StringBinding defaultMountFlagsProperty() {
|
||||
return defaultMountFlags;
|
||||
}
|
||||
|
||||
public String getDefaultMountFlags() {
|
||||
return defaultMountFlags.get();
|
||||
}
|
||||
|
||||
public String getEffectiveMountFlags() {
|
||||
String mountFlags = vaultSettings.mountFlags().get();
|
||||
if (Strings.isNullOrEmpty(mountFlags)) {
|
||||
return defaultMountFlags.get();
|
||||
return getDefaultMountFlags();
|
||||
} else {
|
||||
return mountFlags;
|
||||
}
|
||||
}
|
||||
|
||||
public void setMountFlags(String mountFlags) {
|
||||
public void setCustomMountFlags(String mountFlags) {
|
||||
vaultSettings.mountFlags().set(mountFlags);
|
||||
}
|
||||
|
||||
public Character getWinDriveLetter() {
|
||||
if (vaultSettings.winDriveLetter().get() == null) {
|
||||
return null;
|
||||
} else {
|
||||
return vaultSettings.winDriveLetter().get().charAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void setWinDriveLetter(Path root) {
|
||||
if (root == null) {
|
||||
vaultSettings.winDriveLetter().set(null);
|
||||
} else {
|
||||
char winDriveLetter = root.toString().charAt(0);
|
||||
vaultSettings.winDriveLetter().set(String.valueOf(winDriveLetter));
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return vaultSettings.getId();
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
@@ -22,6 +22,9 @@ public interface VaultComponent {
|
||||
@BindsInstance
|
||||
Builder vaultSettings(VaultSettings vaultSettings);
|
||||
|
||||
@BindsInstance
|
||||
Builder initialVaultState(VaultState vaultState);
|
||||
|
||||
VaultComponent build();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This listener makes sure to reflect any changes to the vault list back to the settings.
|
||||
*/
|
||||
class VaultListChangeListener implements ListChangeListener<Vault> {
|
||||
|
||||
private final ObservableList<VaultSettings> vaultSettingsList;
|
||||
|
||||
public VaultListChangeListener(ObservableList<VaultSettings> vaultSettingsList) {
|
||||
this.vaultSettingsList = vaultSettingsList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(Change<? extends Vault> c) {
|
||||
while(c.next()) {
|
||||
if (c.wasAdded()) {
|
||||
List<VaultSettings> addedSettings = c.getAddedSubList().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
|
||||
vaultSettingsList.addAll(c.getFrom(), addedSettings);
|
||||
} else if (c.wasRemoved()) {
|
||||
List<VaultSettings> removedSettings = c.getRemoved().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
|
||||
vaultSettingsList.removeAll(removedSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
|
||||
* 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.vaults;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptofs.migration.Migrators;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
public class VaultListManager {
|
||||
|
||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
|
||||
|
||||
private final VaultComponent.Builder vaultComponentBuilder;
|
||||
private final ObservableList<Vault> vaultList;
|
||||
|
||||
@Inject
|
||||
public VaultListManager(VaultComponent.Builder vaultComponentBuilder, Settings settings) {
|
||||
this.vaultComponentBuilder = vaultComponentBuilder;
|
||||
this.vaultList = FXCollections.observableArrayList(Vault::observables);
|
||||
|
||||
addAll(settings.getDirectories());
|
||||
vaultList.addListener(new VaultListChangeListener(settings.getDirectories()));
|
||||
}
|
||||
|
||||
public ObservableList<Vault> getVaultList() {
|
||||
return vaultList;
|
||||
}
|
||||
|
||||
public Vault add(Path pathToVault) throws NoSuchFileException {
|
||||
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
|
||||
throw new NoSuchFileException(pathToVault.toString(), null, "Not a vault directory");
|
||||
}
|
||||
Optional<Vault> alreadyExistingVault = get(pathToVault);
|
||||
if (alreadyExistingVault.isPresent()) {
|
||||
return alreadyExistingVault.get();
|
||||
} else {
|
||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||
vaultSettings.path().set(pathToVault);
|
||||
Vault newVault = create(vaultSettings);
|
||||
vaultList.add(newVault);
|
||||
return newVault;
|
||||
}
|
||||
}
|
||||
|
||||
private void addAll(Collection<VaultSettings> vaultSettings) {
|
||||
Collection<Vault> vaults = vaultSettings.stream().map(this::create).collect(Collectors.toList());
|
||||
vaultList.addAll(vaults);
|
||||
}
|
||||
|
||||
private Optional<Vault> get(Path vaultPath) {
|
||||
return vaultList.stream().filter(v -> v.getPath().equals(vaultPath)).findAny();
|
||||
}
|
||||
|
||||
private Vault create(VaultSettings vaultSettings) {
|
||||
VaultState vaultState = determineVaultState(vaultSettings.path().get());
|
||||
VaultComponent comp = vaultComponentBuilder.vaultSettings(vaultSettings).initialVaultState(vaultState).build();
|
||||
return comp.vault();
|
||||
}
|
||||
|
||||
private VaultState determineVaultState(Path pathToVault) {
|
||||
try {
|
||||
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
|
||||
return VaultState.MISSING;
|
||||
} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {
|
||||
return VaultState.NEEDS_MIGRATION;
|
||||
} else {
|
||||
return VaultState.LOCKED;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return VaultState.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,14 +3,23 @@
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -18,13 +27,25 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@Module
|
||||
public class VaultModule {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class);
|
||||
|
||||
@Provides
|
||||
@PerVault
|
||||
public AtomicReference<CryptoFileSystem> provideCryptoFileSystemReference() {
|
||||
return new AtomicReference<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@PerVault
|
||||
public ObjectProperty<VaultState> provideVaultState(VaultState initialState) {
|
||||
return new SimpleObjectProperty<>(initialState);
|
||||
}
|
||||
|
||||
@Provides
|
||||
public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
|
||||
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
|
||||
@@ -44,33 +65,33 @@ public class VaultModule {
|
||||
@Provides
|
||||
@PerVault
|
||||
@DefaultMountFlags
|
||||
public Supplier<String> provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
||||
return () -> {
|
||||
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
|
||||
switch (preferredImpl) {
|
||||
case FUSE:
|
||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
return getMacFuseDefaultMountFlags(settings, vaultSettings);
|
||||
} else if (SystemUtils.IS_OS_LINUX) {
|
||||
return getLinuxFuseDefaultMountFlags(settings, vaultSettings);
|
||||
}
|
||||
case DOKANY:
|
||||
return getDokanyDefaultMountFlags(settings, vaultSettings);
|
||||
default:
|
||||
return "--flags-supported-on-FUSE-or-DOKANY-only";
|
||||
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
||||
ObjectProperty<VolumeImpl> preferredVolumeImpl = settings.preferredVolumeImpl();
|
||||
StringProperty mountName = vaultSettings.mountName();
|
||||
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
|
||||
|
||||
return Bindings.createStringBinding(() -> {
|
||||
VolumeImpl v = preferredVolumeImpl.get();
|
||||
if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_MAC) {
|
||||
return getMacFuseDefaultMountFlags(mountName, readOnly);
|
||||
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_LINUX) {
|
||||
return getLinuxFuseDefaultMountFlags(readOnly);
|
||||
} else if (v == VolumeImpl.DOKANY && SystemUtils.IS_OS_WINDOWS) {
|
||||
return getDokanyDefaultMountFlags(readOnly);
|
||||
} else {
|
||||
return "--flags-supported-on-FUSE-or-DOKANY-only";
|
||||
}
|
||||
};
|
||||
}, mountName, readOnly, preferredVolumeImpl);
|
||||
}
|
||||
|
||||
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
|
||||
private String getMacFuseDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
||||
private String getMacFuseDefaultMountFlags(ReadOnlyStringProperty mountName, ReadOnlyBooleanProperty readOnly) {
|
||||
assert SystemUtils.IS_OS_MAC_OSX;
|
||||
|
||||
StringBuilder flags = new StringBuilder();
|
||||
if (vaultSettings.usesReadOnlyMode().get()) {
|
||||
if (readOnly.get()) {
|
||||
flags.append(" -ordonly");
|
||||
}
|
||||
flags.append(" -ovolname=").append(vaultSettings.mountName().get());
|
||||
flags.append(" -ovolname=").append(mountName.get());
|
||||
flags.append(" -oatomic_o_trunc");
|
||||
flags.append(" -oauto_xattr");
|
||||
flags.append(" -oauto_cache");
|
||||
@@ -92,11 +113,10 @@ public class VaultModule {
|
||||
}
|
||||
|
||||
// see https://manpages.debian.org/testing/fuse/mount.fuse.8.en.html
|
||||
private String getLinuxFuseDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
||||
private String getLinuxFuseDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
|
||||
assert SystemUtils.IS_OS_LINUX;
|
||||
|
||||
StringBuilder flags = new StringBuilder();
|
||||
if (vaultSettings.usesReadOnlyMode().get()) {
|
||||
if (readOnly.get()) {
|
||||
flags.append(" -oro");
|
||||
}
|
||||
flags.append(" -oauto_unmount");
|
||||
@@ -115,12 +135,11 @@ public class VaultModule {
|
||||
}
|
||||
|
||||
// see https://github.com/cryptomator/dokany-nio-adapter/blob/develop/src/main/java/org/cryptomator/frontend/dokany/MountUtil.java#L30-L34
|
||||
private String getDokanyDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
||||
private String getDokanyDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
|
||||
assert SystemUtils.IS_OS_WINDOWS;
|
||||
|
||||
StringBuilder flags = new StringBuilder();
|
||||
flags.append(" --options CURRENT_SESSION");
|
||||
if (vaultSettings.usesReadOnlyMode().get()) {
|
||||
if (readOnly.get()) {
|
||||
flags.append(",WRITE_PROTECTION");
|
||||
}
|
||||
flags.append(" --thread-count 5");
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
public enum VaultState {
|
||||
/**
|
||||
* No vault found at the provided path
|
||||
*/
|
||||
MISSING,
|
||||
|
||||
/**
|
||||
* Vault requires migration to a newer vault format
|
||||
*/
|
||||
NEEDS_MIGRATION,
|
||||
|
||||
/**
|
||||
* Vault ready to be unlocked
|
||||
*/
|
||||
LOCKED,
|
||||
|
||||
/**
|
||||
* Vault in transition between two other states
|
||||
*/
|
||||
PROCESSING,
|
||||
|
||||
/**
|
||||
* Vault is unlocked
|
||||
*/
|
||||
UNLOCKED,
|
||||
|
||||
/**
|
||||
* Unknown state due to preceeding unrecoverable exceptions.
|
||||
*/
|
||||
ERROR;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.util.Duration;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemStats;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@PerVault
|
||||
public class VaultStats {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultStats.class);
|
||||
|
||||
private final AtomicReference<CryptoFileSystem> fs;
|
||||
private final ObjectProperty<VaultState> state;
|
||||
private final ScheduledService<Optional<CryptoFileSystemStats>> updateService;
|
||||
private final LongProperty bytesPerSecondRead = new SimpleLongProperty();
|
||||
private final LongProperty bytesPerSecondWritten = new SimpleLongProperty();
|
||||
|
||||
@Inject
|
||||
VaultStats(AtomicReference<CryptoFileSystem> fs, ObjectProperty<VaultState> state, ExecutorService executor) {
|
||||
this.fs = fs;
|
||||
this.state = state;
|
||||
this.updateService = new UpdateStatsService();
|
||||
updateService.setExecutor(executor);
|
||||
updateService.setPeriod(Duration.seconds(1));
|
||||
|
||||
state.addListener(this::vaultStateChanged);
|
||||
}
|
||||
|
||||
private void vaultStateChanged(@SuppressWarnings("unused") Observable observable) {
|
||||
switch (state.get()) {
|
||||
case UNLOCKED:
|
||||
assert fs.get() != null;
|
||||
LOG.debug("start recording stats");
|
||||
updateService.restart();
|
||||
break;
|
||||
default:
|
||||
LOG.debug("stop recording stats");
|
||||
updateService.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStats(Optional<CryptoFileSystemStats> stats) {
|
||||
assert Platform.isFxApplicationThread();
|
||||
bytesPerSecondRead.set(stats.map(CryptoFileSystemStats::pollBytesRead).orElse(0l));
|
||||
bytesPerSecondWritten.set(stats.map(CryptoFileSystemStats::pollBytesWritten).orElse(0l));
|
||||
}
|
||||
|
||||
private class UpdateStatsService extends ScheduledService<Optional<CryptoFileSystemStats>> {
|
||||
|
||||
@Override
|
||||
protected Task<Optional<CryptoFileSystemStats>> createTask() {
|
||||
return new Task<>() {
|
||||
@Override
|
||||
protected Optional<CryptoFileSystemStats> call() {
|
||||
return Optional.ofNullable(fs.get()).map(CryptoFileSystem::getStats);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
assert getValue() != null;
|
||||
updateStats(getValue());
|
||||
super.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/* Observables */
|
||||
|
||||
public LongProperty bytesPerSecondReadProperty() {
|
||||
return bytesPerSecondRead;
|
||||
}
|
||||
|
||||
public long getBytesPerSecondRead() {
|
||||
return bytesPerSecondRead.get();
|
||||
}
|
||||
|
||||
public LongProperty bytesPerSecondWrittenProperty() {
|
||||
return bytesPerSecondWritten;
|
||||
}
|
||||
|
||||
public long getBytesPerSecondWritten() {
|
||||
return bytesPerSecondWritten.get();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
@@ -28,6 +30,8 @@ public interface Volume {
|
||||
|
||||
void unmount() throws VolumeException;
|
||||
|
||||
Optional<Path> getMountPoint();
|
||||
|
||||
// optional forced unmounting:
|
||||
|
||||
default boolean supportsForcedUnmount() {
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
@@ -13,6 +13,8 @@ import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public class WebDavVolume implements Volume {
|
||||
|
||||
@@ -25,6 +27,7 @@ public class WebDavVolume implements Volume {
|
||||
private WebDavServer server;
|
||||
private WebDavServletController servlet;
|
||||
private Mounter.Mount mount;
|
||||
private Path mountPoint;
|
||||
|
||||
@Inject
|
||||
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings) {
|
||||
@@ -52,7 +55,7 @@ public class WebDavVolume implements Volume {
|
||||
}
|
||||
MountParams mountParams = MountParams.create() //
|
||||
.withWindowsDriveLetter(vaultSettings.winDriveLetter().get()) //
|
||||
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get())//
|
||||
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get().getPrefix())//
|
||||
.withWebdavHostname(getLocalhostAliasOrNull()) //
|
||||
.build();
|
||||
try {
|
||||
@@ -93,6 +96,11 @@ public class WebDavVolume implements Volume {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Path> getMountPoint() {
|
||||
return Optional.ofNullable(mountPoint);
|
||||
}
|
||||
|
||||
private String getLocalhostAliasOrNull() {
|
||||
try {
|
||||
InetAddress alias = InetAddress.getByName(LOCALHOST_ALIAS);
|
||||
@@ -3,31 +3,31 @@
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@FxApplicationScoped
|
||||
@Singleton
|
||||
public final class WindowsDriveLetters {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WindowsDriveLetters.class);
|
||||
private static final Set<Path> D_TO_Z;
|
||||
private static final Set<String> A_TO_Z;
|
||||
|
||||
static {
|
||||
try (IntStream stream = IntStream.rangeClosed('D', 'Z')) {
|
||||
D_TO_Z = stream.mapToObj(i -> Path.of(((char) i)+":\\")).collect(Collectors.toSet());
|
||||
try (IntStream stream = IntStream.rangeClosed('A', 'Z')) {
|
||||
A_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,20 +35,22 @@ public final class WindowsDriveLetters {
|
||||
public WindowsDriveLetters() {
|
||||
}
|
||||
|
||||
public Set<Path> getOccupiedDriveLetters() {
|
||||
public Set<String> getAllDriveLetters() {
|
||||
return A_TO_Z;
|
||||
}
|
||||
|
||||
public Set<String> getOccupiedDriveLetters() {
|
||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
||||
LOG.warn("Attempted to get occupied drive letters on non-Windows machine.");
|
||||
return Set.of();
|
||||
} else {
|
||||
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
|
||||
return StreamSupport.stream(rootDirs.spliterator(), false).collect(Collectors.toSet());
|
||||
return StreamSupport.stream(rootDirs.spliterator(), false).map(p -> p.toString().substring(0,1)).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
public Set<Path> getAvailableDriveLetters() {
|
||||
Set<Path> occupiedDriveLetters = getOccupiedDriveLetters();
|
||||
Predicate<Path> isOccupiedDriveLetter = occupiedDriveLetters::contains;
|
||||
return D_TO_Z.stream().filter(isOccupiedDriveLetter.negate()).collect(Collectors.toSet());
|
||||
public Set<String> getAvailableDriveLetters() {
|
||||
return Sets.difference(A_TO_Z, getOccupiedDriveLetters());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public class SettingsJsonAdapterTest {
|
||||
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(WebDavUrlScheme.DAV, settings.preferredGvfsScheme().get());
|
||||
Assertions.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
|
||||
}
|
||||
|
||||
|
||||
@@ -8,22 +8,20 @@ package org.cryptomator.common.settings;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class SettingsTest {
|
||||
|
||||
@Test
|
||||
public void testAutoSave() throws IOException {
|
||||
@SuppressWarnings("unchecked")
|
||||
Consumer<Settings> changeListener = Mockito.mock(Consumer.class);
|
||||
public void testAutoSave() {
|
||||
@SuppressWarnings("unchecked") Consumer<Settings> changeListener = Mockito.mock(Consumer.class);
|
||||
Settings settings = new Settings();
|
||||
settings.setSaveCmd(changeListener);
|
||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||
Mockito.verify(changeListener, Mockito.times(0)).accept(settings);
|
||||
|
||||
// first change (to property):
|
||||
settings.preferredGvfsScheme().set("asd");
|
||||
settings.preferredGvfsScheme().set(WebDavUrlScheme.WEBDAV);
|
||||
Mockito.verify(changeListener, Mockito.times(1)).accept(settings);
|
||||
|
||||
// second change (to list):
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.common.settings.VolumeImpl;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class VaultModuleTest {
|
||||
|
||||
private final Settings settings = Mockito.mock(Settings.class);
|
||||
private final VaultSettings vaultSettings = Mockito.mock(VaultSettings.class);
|
||||
|
||||
private final VaultModule module = new VaultModule();
|
||||
|
||||
@BeforeEach
|
||||
public void setup(@TempDir Path tmpDir) {
|
||||
Mockito.when(vaultSettings.mountName()).thenReturn(new SimpleStringProperty("TEST"));
|
||||
Mockito.when(vaultSettings.usesReadOnlyMode()).thenReturn(new SimpleBooleanProperty(true));
|
||||
System.setProperty("user.home", tmpDir.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("provideDefaultMountFlags on Mac/FUSE")
|
||||
@EnabledOnOs(OS.MAC)
|
||||
public void testMacFuseDefaultMountFlags() {
|
||||
Mockito.when(settings.preferredVolumeImpl()).thenReturn(new SimpleObjectProperty<>(VolumeImpl.FUSE));
|
||||
|
||||
StringBinding result = module.provideDefaultMountFlags(settings, vaultSettings);
|
||||
|
||||
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ovolname=TEST"));
|
||||
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ordonly"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("provideDefaultMountFlags on Linux/FUSE")
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
public void testLinuxFuseDefaultMountFlags() {
|
||||
Mockito.when(settings.preferredVolumeImpl()).thenReturn(new SimpleObjectProperty<>(VolumeImpl.FUSE));
|
||||
|
||||
StringBinding result = module.provideDefaultMountFlags(settings, vaultSettings);
|
||||
|
||||
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-oro"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("provideDefaultMountFlags on Windows/Dokany")
|
||||
@EnabledOnOs(OS.WINDOWS)
|
||||
public void testWinDokanyDefaultMountFlags() {
|
||||
Mockito.when(settings.preferredVolumeImpl()).thenReturn(new SimpleObjectProperty<>(VolumeImpl.DOKANY));
|
||||
|
||||
StringBinding result = module.provideDefaultMountFlags(settings, vaultSettings);
|
||||
|
||||
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("--options CURRENT_SESSION,WRITE_PROTECTION"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.12</version>
|
||||
<version>1.5.0-beta1</version>
|
||||
</parent>
|
||||
<artifactId>keychain</artifactId>
|
||||
<name>System Keychain Access</name>
|
||||
@@ -23,11 +23,7 @@
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>jni</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Google -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
public interface GnomeKeyringAccess {
|
||||
public void storePassword(String key, CharSequence passphrase);
|
||||
|
||||
public char[] loadPassword(String key);
|
||||
|
||||
public void deletePassword(String key);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import org.freedesktop.secret.simple.SimpleCollection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class GnomeKeyringAccessImpl implements GnomeKeyringAccess {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GnomeKeyringAccessImpl.class);
|
||||
private SimpleCollection keyring;
|
||||
|
||||
public GnomeKeyringAccessImpl() {
|
||||
try {
|
||||
keyring = new SimpleCollection();
|
||||
} catch (IOException e) {
|
||||
LOG.error("D-Bus reports a problem.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void storePassword(String key, CharSequence passphrase) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list == null) {
|
||||
keyring.createItem("Cryptomator", passphrase, createAttributes(key));
|
||||
}
|
||||
}
|
||||
|
||||
public char[] loadPassword(String key) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list != null) {
|
||||
return keyring.getSecret(list.get(0));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void deletePassword(String key) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list != null) {
|
||||
keyring.deleteItem(list.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> createAttributes(String key) {
|
||||
Map<String, String> attributes = new HashMap();
|
||||
attributes.put("Vault", key);
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
@@ -13,19 +13,19 @@ public interface KeychainAccess {
|
||||
* @param key Key used to retrieve the passphrase via {@link #loadPassphrase(String)}.
|
||||
* @param passphrase The secret to store in this keychain.
|
||||
*/
|
||||
void storePassphrase(String key, CharSequence passphrase);
|
||||
void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;
|
||||
|
||||
/**
|
||||
* @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
|
||||
* @return The stored passphrase for the given key or <code>null</code> if no value for the given key could be found.
|
||||
*/
|
||||
char[] loadPassphrase(String key);
|
||||
char[] loadPassphrase(String key) throws KeychainAccessException;
|
||||
|
||||
/**
|
||||
* Deletes a passphrase with a given key.
|
||||
*
|
||||
* @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
|
||||
*/
|
||||
void deletePassphrase(String key);
|
||||
void deletePassphrase(String key) throws KeychainAccessException;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
/**
|
||||
* Indicates an error during communication with the operating system's keychain.
|
||||
*/
|
||||
public class KeychainAccessException extends Exception {
|
||||
|
||||
KeychainAccessException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,8 @@ interface KeychainAccessStrategy extends KeychainAccess {
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if this KeychainAccessStrategy works on the current machine.
|
||||
* @implNote This method must not throw any exceptions and should fail fast
|
||||
* returning <code>false</code> if it can't determine availability of the checked strategy
|
||||
*/
|
||||
boolean isSupported();
|
||||
|
||||
|
||||
@@ -5,33 +5,21 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
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;
|
||||
import org.cryptomator.common.JniModule;
|
||||
|
||||
@Module
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@Module(includes = {JniModule.class})
|
||||
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, LinuxSecretServiceAccess linKeychain) {
|
||||
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
|
||||
return Sets.newHashSet(macKeychain, winKeychain, linKeychain);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class LinuxKeychainTester {
|
||||
|
||||
public static boolean secretServiceIsAvailable() {
|
||||
try {
|
||||
Class.forName("org.freedesktop.secret.simple.SimpleCollection");
|
||||
return SystemUtils.IS_OS_LINUX; // even if the classes could be loaded, secretService is only available on linux
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<GnomeKeyringAccess> getSecretService() {
|
||||
if (!secretServiceIsAvailable()) return Optional.empty();
|
||||
try {
|
||||
Class clazz = Class.forName("org.cryptomator.keychain.GnomeKeyringAccessImpl");
|
||||
GnomeKeyringAccess keyring = (GnomeKeyringAccess) clazz.getDeclaredConstructor().newInstance();
|
||||
return Optional.of(keyring);
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
public class LinuxSecretServiceAccess implements KeychainAccessStrategy {
|
||||
private final Optional<GnomeKeyringAccess> gnomeLoginKeyring;
|
||||
|
||||
@Inject
|
||||
public LinuxSecretServiceAccess() {
|
||||
gnomeLoginKeyring = LinuxKeychainTester.getSecretService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return SystemUtils.IS_OS_LINUX && LinuxKeychainTester.getSecretService().isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePassphrase(String key, CharSequence passphrase) {
|
||||
Preconditions.checkState(gnomeLoginKeyring.isPresent());
|
||||
gnomeLoginKeyring.get().storePassword(key, passphrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] loadPassphrase(String key) {
|
||||
Preconditions.checkState(gnomeLoginKeyring.isPresent());
|
||||
return gnomeLoginKeyring.get().loadPassword(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePassphrase(String key) {
|
||||
Preconditions.checkState(gnomeLoginKeyring.isPresent());
|
||||
gnomeLoginKeyring.get().deletePassword(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A facade to LinuxSecretServiceKeychainAccessImpl that doesn't depend on libraries that are unavailable on Mac and Windows.
|
||||
*/
|
||||
public class LinuxSecretServiceKeychainAccess implements KeychainAccessStrategy {
|
||||
|
||||
// the actual implementation is hidden in this delegate object which is loaded via reflection,
|
||||
// as it depends on libraries that aren't necessarily available:
|
||||
private final Optional<KeychainAccessStrategy> delegate;
|
||||
|
||||
@Inject
|
||||
public LinuxSecretServiceKeychainAccess() {
|
||||
this.delegate = constructGnomeKeyringKeychainAccess();
|
||||
}
|
||||
|
||||
private static Optional<KeychainAccessStrategy> constructGnomeKeyringKeychainAccess() {
|
||||
try {
|
||||
Class<?> clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl");
|
||||
KeychainAccessStrategy instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
|
||||
return Optional.of(instance);
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return SystemUtils.IS_OS_LINUX && delegate.map(KeychainAccessStrategy::isSupported).orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||
delegate.orElseThrow(IllegalStateException::new).storePassphrase(key, passphrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] loadPassphrase(String key) throws KeychainAccessException {
|
||||
return delegate.orElseThrow(IllegalStateException::new).loadPassphrase(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePassphrase(String key) throws KeychainAccessException {
|
||||
delegate.orElseThrow(IllegalStateException::new).deletePassphrase(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.cryptomator.keychain;
|
||||
|
||||
import org.freedesktop.secret.simple.SimpleCollection;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class LinuxSecretServiceKeychainAccessImpl implements KeychainAccessStrategy {
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
try (@SuppressWarnings("unused") SimpleCollection keyring = new SimpleCollection()) {
|
||||
// seems like we're able to access the keyring.
|
||||
return true;
|
||||
} catch (IOException | RuntimeException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||
try (SimpleCollection keyring = new SimpleCollection()) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list == null) {
|
||||
keyring.createItem("Cryptomator", passphrase, createAttributes(key));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new KeychainAccessException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] loadPassphrase(String key) throws KeychainAccessException {
|
||||
try (SimpleCollection keyring = new SimpleCollection()) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list != null) {
|
||||
return keyring.getSecret(list.get(0));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new KeychainAccessException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePassphrase(String key) throws KeychainAccessException {
|
||||
try (SimpleCollection keyring = new SimpleCollection()) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list != null) {
|
||||
keyring.deleteItem(list.get(0));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new KeychainAccessException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> createAttributes(String key) {
|
||||
Map<String, String> attributes = new HashMap();
|
||||
attributes.put("Vault", key);
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import java.util.Optional;
|
||||
public class KeychainModuleTest {
|
||||
|
||||
@Test
|
||||
public void testGetKeychain() {
|
||||
public void testGetKeychain() throws KeychainAccessException {
|
||||
Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess();
|
||||
Assertions.assertTrue(keychainAccess.isPresent());
|
||||
Assertions.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.Set;
|
||||
public class TestKeychainModule extends KeychainModule {
|
||||
|
||||
@Override
|
||||
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceAccess linKeychain) {
|
||||
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
|
||||
return Set.of(new MapKeychainAccess());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.12</version>
|
||||
<version>1.5.0-beta1</version>
|
||||
</parent>
|
||||
<artifactId>launcher</artifactId>
|
||||
<name>Cryptomator Launcher</name>
|
||||
|
||||
@@ -11,30 +11,38 @@ import java.util.concurrent.ConcurrentMap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
class CleanShutdownPerformer extends Thread {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CleanShutdownPerformer.class);
|
||||
static final ConcurrentMap<Runnable, Boolean> SHUTDOWN_TASKS = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Runnable, Boolean> tasks = new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
CleanShutdownPerformer() {
|
||||
super(null, null, "ShutdownTasks", 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.debug("Running graceful shutdown tasks...");
|
||||
SHUTDOWN_TASKS.keySet().forEach(r -> {
|
||||
tasks.keySet().forEach(r -> {
|
||||
try {
|
||||
r.run();
|
||||
} catch (RuntimeException e) {
|
||||
LOG.error("Exception while shutting down.", e);
|
||||
}
|
||||
});
|
||||
SHUTDOWN_TASKS.clear();
|
||||
LOG.info("Goodbye.");
|
||||
tasks.clear();
|
||||
}
|
||||
|
||||
static void scheduleShutdownTask(Runnable task) {
|
||||
SHUTDOWN_TASKS.put(task, Boolean.TRUE);
|
||||
void scheduleShutdownTask(Runnable task) {
|
||||
tasks.put(task, Boolean.TRUE);
|
||||
}
|
||||
|
||||
static void registerShutdownHook() {
|
||||
Runtime.getRuntime().addShutdownHook(new CleanShutdownPerformer());
|
||||
void registerShutdownHook() {
|
||||
Runtime.getRuntime().addShutdownHook(this);
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,10 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import javafx.application.Application;
|
||||
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.cryptomator.ui.launcher.UiLauncher;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -19,6 +17,7 @@ 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 {
|
||||
@@ -32,13 +31,19 @@ public class Cryptomator {
|
||||
private final DebugMode debugMode;
|
||||
private final IpcFactory ipcFactory;
|
||||
private final Optional<String> applicationVersion;
|
||||
private final CountDownLatch shutdownLatch;
|
||||
private final CleanShutdownPerformer shutdownPerformer;
|
||||
private final UiLauncher uiLauncher;
|
||||
|
||||
@Inject
|
||||
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion) {
|
||||
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch, CleanShutdownPerformer shutdownPerformer, UiLauncher uiLauncher) {
|
||||
this.logConfig = logConfig;
|
||||
this.debugMode = debugMode;
|
||||
this.ipcFactory = ipcFactory;
|
||||
this.applicationVersion = applicationVersion;
|
||||
this.shutdownLatch = shutdownLatch;
|
||||
this.shutdownPerformer = shutdownPerformer;
|
||||
this.uiLauncher = uiLauncher;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
@@ -48,6 +53,7 @@ public class Cryptomator {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -63,6 +69,7 @@ public class Cryptomator {
|
||||
try (IpcFactory.IpcEndpoint endpoint = ipcFactory.create()) {
|
||||
endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
|
||||
if (endpoint.isConnectedToRemote()) {
|
||||
endpoint.getRemote().revealRunningApp();
|
||||
LOG.info("Found running application instance. Shutting down...");
|
||||
return 2;
|
||||
} else {
|
||||
@@ -77,45 +84,22 @@ public class Cryptomator {
|
||||
|
||||
/**
|
||||
* Launches the JavaFX application and waits until shutdown is requested.
|
||||
*
|
||||
* @return Nonzero exit code in case of an error.
|
||||
* @implNote This method blocks until {@link #shutdownLatch} reached zero.
|
||||
*/
|
||||
private int runGuiApplication() {
|
||||
try {
|
||||
CleanShutdownPerformer.registerShutdownHook();
|
||||
Application.launch(MainApp.class);
|
||||
LOG.info("Shutting down...");
|
||||
shutdownPerformer.registerShutdownHook();
|
||||
uiLauncher.launch();
|
||||
shutdownLatch.await();
|
||||
LOG.info("UI shut down");
|
||||
return 0;
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Terminating due to error", e);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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 stop() {
|
||||
LOG.info("JavaFX application stopped.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,15 +3,14 @@ package org.cryptomator.launcher;
|
||||
import dagger.Component;
|
||||
import org.cryptomator.common.CommonsModule;
|
||||
import org.cryptomator.logging.LoggerModule;
|
||||
import org.cryptomator.ui.launcher.UiLauncherModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class})
|
||||
@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class, UiLauncherModule.class})
|
||||
public interface CryptomatorComponent {
|
||||
|
||||
Cryptomator application();
|
||||
|
||||
FxApplicationComponent.Builder fxApplicationComponent();
|
||||
|
||||
}
|
||||
|
||||
@@ -2,31 +2,28 @@ 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;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Module
|
||||
class CryptomatorModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static Settings provideSettings(SettingsProvider settingsProvider) {
|
||||
return settingsProvider.get();
|
||||
@Named("shutdownTaskScheduler")
|
||||
Consumer<Runnable> provideShutdownTaskScheduler(CleanShutdownPerformer shutdownPerformer) {
|
||||
return shutdownPerformer::scheduleShutdownTask;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("launchEventQueue")
|
||||
static BlockingQueue<AppLaunchEvent> provideFileOpenRequests() {
|
||||
return new ArrayBlockingQueue<>(10);
|
||||
@Named("shutdownLatch")
|
||||
static CountDownLatch provideShutdownLatch() {
|
||||
return new CountDownLatch(1);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.cryptomator.ui.model.AppLaunchEvent;
|
||||
import org.cryptomator.ui.launcher.AppLaunchEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -34,16 +34,14 @@ class FileOpenRequestHandler {
|
||||
@Inject
|
||||
public FileOpenRequestHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
|
||||
this.launchEventQueue = launchEventQueue;
|
||||
try {
|
||||
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.APP_OPEN_FILE)) {
|
||||
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) {
|
||||
private void openFiles(OpenFilesEvent evt) {
|
||||
Stream<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath);
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen);
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||
tryToEnqueueFileOpenRequest(launchEvent);
|
||||
}
|
||||
|
||||
@@ -61,7 +59,7 @@ class FileOpenRequestHandler {
|
||||
return null;
|
||||
}
|
||||
}).filter(Objects::nonNull);
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen);
|
||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||
tryToEnqueueFileOpenRequest(launchEvent);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +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 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import java.rmi.RemoteException;
|
||||
|
||||
interface IpcProtocol extends Remote {
|
||||
|
||||
void handleLaunchArgs(String[] args) throws RemoteException;
|
||||
void revealRunningApp() throws RemoteException;
|
||||
|
||||
void handleLaunchArgs(String... args) throws RemoteException;
|
||||
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.cryptomator.ui.launcher.AppLaunchEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Singleton
|
||||
class IpcProtocolImpl implements IpcProtocol {
|
||||
@@ -13,15 +17,22 @@ class IpcProtocolImpl implements IpcProtocol {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(IpcProtocolImpl.class);
|
||||
|
||||
private final FileOpenRequestHandler fileOpenRequestHandler;
|
||||
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
|
||||
|
||||
@Inject
|
||||
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler) {
|
||||
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
|
||||
this.fileOpenRequestHandler = fileOpenRequestHandler;
|
||||
this.launchEventQueue = launchEventQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(String[] args) {
|
||||
LOG.info("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
|
||||
public void revealRunningApp() {
|
||||
launchEventQueue.add(new AppLaunchEvent(AppLaunchEvent.EventType.REVEAL_APP, Stream.empty()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLaunchArgs(String... args) {
|
||||
LOG.debug("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
|
||||
fileOpenRequestHandler.handleLaunchArgs(args);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.launcher;
|
||||
|
||||
import org.cryptomator.ui.model.AppLaunchEvent;
|
||||
import org.cryptomator.ui.launcher.AppLaunchEvent;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
@@ -64,7 +64,7 @@ public class FileOpenRequestHandlerTest {
|
||||
@Test
|
||||
@DisplayName("./cryptomator.exe foo (with full event queue)")
|
||||
public void testOpenArgsWithFullQueue() throws IOException {
|
||||
queue.add(new AppLaunchEvent(Stream.empty()));
|
||||
queue.add(new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, Stream.empty()));
|
||||
Assumptions.assumeTrue(queue.remainingCapacity() == 0);
|
||||
|
||||
inTest.handleLaunchArgs(new String[]{"foo"});
|
||||
|
||||
63
main/pom.xml
63
main/pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.12</version>
|
||||
<version>1.5.0-beta1</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
@@ -24,43 +24,31 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<!-- dependency versions -->
|
||||
<cryptomator.cryptolib.version>1.2.1</cryptomator.cryptolib.version>
|
||||
<cryptomator.cryptofs.version>1.8.6</cryptomator.cryptofs.version>
|
||||
<cryptomator.jni.version>2.0.0</cryptomator.jni.version>
|
||||
<cryptomator.fuse.version>1.2.0</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.1.9</cryptomator.dokany.version>
|
||||
<cryptomator.cryptofs.version>1.9.0-rc2</cryptomator.cryptofs.version>
|
||||
<cryptomator.jni.version>2.2.1</cryptomator.jni.version>
|
||||
<cryptomator.fuse.version>1.2.1</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.1.11</cryptomator.dokany.version>
|
||||
<cryptomator.webdav.version>1.0.10</cryptomator.webdav.version>
|
||||
|
||||
<javafx.version>12</javafx.version>
|
||||
<javafx.version>13.0.1</javafx.version>
|
||||
|
||||
<commons-io.version>2.6</commons-io.version>
|
||||
<commons-lang3.version>3.8.1</commons-lang3.version>
|
||||
<commons-lang3.version>3.9</commons-lang3.version>
|
||||
|
||||
<easybind.version>1.0.3</easybind.version>
|
||||
|
||||
<guava.version>27.1-jre</guava.version>
|
||||
<dagger.version>2.22.1</dagger.version>
|
||||
<gson.version>2.8.5</gson.version>
|
||||
<guava.version>28.1-jre</guava.version>
|
||||
<dagger.version>2.25.2</dagger.version>
|
||||
<gson.version>2.8.6</gson.version>
|
||||
|
||||
<slf4j.version>1.7.26</slf4j.version>
|
||||
<slf4j.version>1.7.29</slf4j.version>
|
||||
<logback.version>1.2.3</logback.version>
|
||||
|
||||
<junit.jupiter.version>5.4.2</junit.jupiter.version>
|
||||
<mockito.version>2.27.0</mockito.version>
|
||||
<hamcrest.version>2.1</hamcrest.version>
|
||||
<junit.jupiter.version>5.5.2</junit.jupiter.version>
|
||||
<mockito.version>3.1.0</mockito.version>
|
||||
<hamcrest.version>2.2</hamcrest.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>ossrh-snapshots</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jcenter</id>
|
||||
<url>http://jcenter.bintray.com</url>
|
||||
@@ -92,11 +80,6 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Cryptomator Libs -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptolib</artifactId>
|
||||
<version>${cryptomator.cryptolib.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptofs</artifactId>
|
||||
@@ -129,6 +112,11 @@
|
||||
<artifactId>javafx-base</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-graphics</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
@@ -163,11 +151,6 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
@@ -278,7 +261,7 @@
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.1.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-libs</id>
|
||||
@@ -307,7 +290,7 @@
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.2</version>
|
||||
<version>0.8.5</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>prepare-agent</id>
|
||||
@@ -334,7 +317,7 @@
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<release>11</release>
|
||||
<annotationProcessorPaths>
|
||||
@@ -349,7 +332,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.1</version>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.4.12</version>
|
||||
<version>1.5.0-beta1</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
@@ -18,33 +18,10 @@
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptofs</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>jni</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<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>
|
||||
@@ -72,11 +49,7 @@
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- apache commons -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
<!-- Apache Commons -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
@@ -92,7 +65,7 @@
|
||||
<dependency>
|
||||
<groupId>com.nulab-inc</groupId>
|
||||
<artifactId>zxcvbn</artifactId>
|
||||
<version>1.2.2</version>
|
||||
<version>1.2.7</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
|
||||
* 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 - implementation of github issue #56
|
||||
*******************************************************************************/
|
||||
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;
|
||||
import java.awt.PopupMenu;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.TrayIcon.MessageType;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class ExitUtil {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ExitUtil.class);
|
||||
|
||||
private final Stage mainWindow;
|
||||
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) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.localization = localization;
|
||||
this.settings = settings;
|
||||
this.macFunctions = macFunctions;
|
||||
}
|
||||
|
||||
public void initExitHandler(Runnable exitCommand) {
|
||||
if (SystemUtils.IS_OS_LINUX) {
|
||||
initMinimizeExitHandler(exitCommand);
|
||||
} else {
|
||||
initTrayIconExitHandler(exitCommand);
|
||||
}
|
||||
}
|
||||
|
||||
private void initMinimizeExitHandler(Runnable exitCommand) {
|
||||
mainWindow.setOnCloseRequest(e -> {
|
||||
if (Platform.isImplicitExit()) {
|
||||
exitCommand.run();
|
||||
} else {
|
||||
mainWindow.setIconified(true);
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initTrayIconExitHandler(Runnable exitCommand) {
|
||||
trayIcon = createTrayIcon(exitCommand);
|
||||
try {
|
||||
// double clicking tray icon should open Cryptomator
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
trayIcon.addMouseListener(new TrayIconMouseListener());
|
||||
}
|
||||
|
||||
SystemTray.getSystemTray().add(trayIcon);
|
||||
mainWindow.setOnCloseRequest((e) -> {
|
||||
if (Platform.isImplicitExit()) {
|
||||
exitCommand.run();
|
||||
} else {
|
||||
macFunctions.map(MacFunctions::uiState).ifPresent(JniException.ignore(MacApplicationUiState::transformToAgentApplication));
|
||||
mainWindow.close();
|
||||
this.showTrayNotification(trayIcon);
|
||||
}
|
||||
});
|
||||
} catch (SecurityException | AWTException ex) {
|
||||
// not working? then just go ahead and close the app
|
||||
mainWindow.setOnCloseRequest((ev) -> {
|
||||
exitCommand.run();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private TrayIcon createTrayIcon(Runnable exitCommand) {
|
||||
final PopupMenu popup = new PopupMenu();
|
||||
|
||||
final MenuItem showItem = new MenuItem(localization.getString("tray.menu.open"));
|
||||
showItem.addActionListener(this::restoreFromTray);
|
||||
popup.add(showItem);
|
||||
|
||||
final MenuItem exitItem = new MenuItem(localization.getString("tray.menu.quit"));
|
||||
exitItem.addActionListener(e -> exitCommand.run());
|
||||
popup.add(exitItem);
|
||||
|
||||
final Image image = getAppropriateTrayIconImage(true);
|
||||
|
||||
return new TrayIcon(image, localization.getString("app.name"), popup);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if <code>defaults read -g AppleInterfaceStyle</code> has an exit status of <code>0</code> (i.e. _not_ returning "key not found").
|
||||
*/
|
||||
private boolean isMacMenuBarDarkMode() {
|
||||
try {
|
||||
// check for exit status only. Once there are more modes than "dark" and "default", we might need to analyze string contents..
|
||||
final Process proc = Runtime.getRuntime().exec(new String[] {"defaults", "read", "-g", "AppleInterfaceStyle"});
|
||||
proc.waitFor(100, TimeUnit.MILLISECONDS);
|
||||
return proc.exitValue() == 0;
|
||||
} catch (IOException | InterruptedException | IllegalThreadStateException ex) {
|
||||
// IllegalThreadStateException thrown by proc.exitValue(), if process didn't terminate
|
||||
LOG.warn("Determining MAC OS X dark mode settings failed. Assuming default (light) mode.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void showTrayNotification(TrayIcon trayIcon) {
|
||||
int remainingTrayNotification = settings.numTrayNotifications().get();
|
||||
if (remainingTrayNotification <= 0) {
|
||||
return;
|
||||
} else {
|
||||
settings.numTrayNotifications().set(remainingTrayNotification - 1);
|
||||
}
|
||||
final Runnable notificationCmd;
|
||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
final String title = localization.getString("tray.infoMsg.title");
|
||||
final String msg = localization.getString("tray.infoMsg.msg.osx");
|
||||
final String notificationCenterAppleScript = String.format("display notification \"%s\" with title \"%s\"", msg, title);
|
||||
notificationCmd = () -> {
|
||||
try {
|
||||
final ScriptEngineManager mgr = new ScriptEngineManager();
|
||||
final ScriptEngine engine = mgr.getEngineByName("AppleScriptEngine");
|
||||
if (engine != null) {
|
||||
engine.eval(notificationCenterAppleScript);
|
||||
} else {
|
||||
Runtime.getRuntime().exec(new String[] {"/usr/bin/osascript", "-e", notificationCenterAppleScript});
|
||||
}
|
||||
} catch (ScriptException | IOException e) {
|
||||
// ignore, user will notice the tray icon anyway.
|
||||
}
|
||||
};
|
||||
} else {
|
||||
final String title = localization.getString("tray.infoMsg.title");
|
||||
final String msg = localization.getString("tray.infoMsg.msg");
|
||||
notificationCmd = () -> {
|
||||
trayIcon.displayMessage(title, msg, MessageType.INFO);
|
||||
};
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
notificationCmd.run();
|
||||
});
|
||||
}
|
||||
|
||||
private class TrayIconMouseListener extends MouseAdapter {
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
|
||||
restoreFromTray(new ActionEvent(e.getSource(), e.getID(), e.paramString()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void restoreFromTray(ActionEvent event) {
|
||||
Platform.runLater(() -> {
|
||||
macFunctions.map(MacFunctions::uiState).ifPresent(JniException.ignore(MacApplicationUiState::transformToForegroundApplication));
|
||||
mainWindow.show();
|
||||
mainWindow.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
|
||||
* 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;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.beans.binding.Binding;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.cryptomator.keychain.KeychainModule;
|
||||
import org.cryptomator.ui.controllers.ViewControllerModule;
|
||||
import org.cryptomator.ui.model.VaultComponent;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
import javax.inject.Named;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Module(includes = {ViewControllerModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class})
|
||||
public class UiModule {
|
||||
|
||||
private static final int NUM_SCHEDULER_THREADS = 4;
|
||||
|
||||
@Provides
|
||||
@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
|
||||
@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";
|
||||
return InetSocketAddress.createUnresolved(host, port.intValue());
|
||||
});
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
WebDavServer provideWebDavServer(Binding<InetSocketAddress> serverSocketAddressBinding) {
|
||||
WebDavServer server = WebDavServer.create();
|
||||
// no need to unsubscribe eventually, because server is a singleton
|
||||
EasyBind.subscribe(serverSocketAddressBinding, server::bind);
|
||||
return server;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class AddVaultFailureExisitingController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> previousScene;
|
||||
private final StringBinding vaultName;
|
||||
|
||||
@Inject
|
||||
AddVaultFailureExisitingController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_EXISTING) Lazy<Scene> previousScene, ObjectProperty<Path> pathOfFailedVault){
|
||||
this.window = window;
|
||||
this.previousScene = previousScene;
|
||||
this.vaultName = Bindings.createStringBinding(() -> pathOfFailedVault.get().getFileName().toString(),pathOfFailedVault);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close(){
|
||||
window.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back(){
|
||||
window.setScene(previousScene.get());
|
||||
}
|
||||
|
||||
// Getter & Setter
|
||||
|
||||
public StringBinding vaultNameProperty(){
|
||||
return vaultName;
|
||||
}
|
||||
|
||||
public String getVaultName(){
|
||||
return vaultName.get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.mainwindow.MainWindow;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
public abstract class AddVaultModule {
|
||||
|
||||
@Provides
|
||||
@AddVaultWizardWindow
|
||||
@AddVaultWizardScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AddVaultWizardWindow
|
||||
@AddVaultWizardScoped
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("addvaultwizard.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AddVaultWizardScoped
|
||||
static ObjectProperty<Path> provideVaultPath() {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("vaultName")
|
||||
@AddVaultWizardScoped
|
||||
static StringProperty provideVaultName() {
|
||||
return new SimpleStringProperty("");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AddVaultWizardWindow
|
||||
@AddVaultWizardScoped
|
||||
static ObjectProperty<Vault> provideVault() {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("recoveryKey")
|
||||
@AddVaultWizardScoped
|
||||
static StringProperty provideRecoveryKey() {
|
||||
return new SimpleStringProperty();
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_WELCOME)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideWelcomeScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_WELCOME.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_EXISTING)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideChooseExistingVaultScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_EXISTING.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_EXISTING_ERROR)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideChooseExistingVaultErrorScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_EXISTING_ERROR.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_NAME)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideCreateNewVaultNameScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_NAME.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideCreateNewVaultLocationScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_LOCATION.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideCreateNewVaultPasswordScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_PASSWORD.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideCreateNewVaultRecoveryKeyScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY.getRessourcePathString());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.ADDVAULT_SUCCESS)
|
||||
@AddVaultWizardScoped
|
||||
static Scene provideCreateNewVaultSuccessScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.ADDVAULT_SUCCESS.getRessourcePathString());
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(AddVaultWelcomeController.class)
|
||||
abstract FxController bindWelcomeController(AddVaultWelcomeController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(ChooseExistingVaultController.class)
|
||||
abstract FxController bindChooseExistingVaultController(ChooseExistingVaultController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(AddVaultFailureExisitingController.class)
|
||||
abstract FxController bindAddVaultFailureExistingController(AddVaultFailureExisitingController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(CreateNewVaultNameController.class)
|
||||
abstract FxController bindCreateNewVaultNameController(CreateNewVaultNameController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(CreateNewVaultLocationController.class)
|
||||
abstract FxController bindCreateNewVaultLocationController(CreateNewVaultLocationController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(CreateNewVaultPasswordController.class)
|
||||
abstract FxController bindCreateNewVaultPasswordController(CreateNewVaultPasswordController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(AddVaultSuccessController.class)
|
||||
abstract FxController bindAddVaultSuccessController(AddVaultSuccessController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(CreateNewVaultRecoveryKeyController.class)
|
||||
abstract FxController bindCreateNewVaultRecoveryKeyController(CreateNewVaultRecoveryKeyController controller);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.fxapp.FxApplication;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class AddVaultSuccessController implements FxController {
|
||||
|
||||
private final FxApplication fxApplication;
|
||||
private final Stage window;
|
||||
private final ReadOnlyObjectProperty<Vault> vault;
|
||||
|
||||
@Inject
|
||||
AddVaultSuccessController(FxApplication fxApplication, @AddVaultWizardWindow Stage window, @AddVaultWizardWindow ObjectProperty<Vault> vault) {
|
||||
this.fxApplication = fxApplication;
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void unlockAndClose() {
|
||||
close();
|
||||
fxApplication.showUnlockWindow(vault.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
window.close();
|
||||
}
|
||||
|
||||
/* Observables */
|
||||
|
||||
public ReadOnlyObjectProperty<Vault> vaultProperty() {
|
||||
return vault;
|
||||
}
|
||||
|
||||
public Vault getVault() {
|
||||
return vault.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class AddVaultWelcomeController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AddVaultWelcomeController.class);
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> chooseExistingVaultScene;
|
||||
private final Lazy<Scene> createNewVaultScene;
|
||||
|
||||
@Inject
|
||||
AddVaultWelcomeController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_EXISTING) Lazy<Scene> chooseExistingVaultScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> createNewVaultScene) {
|
||||
this.window = window;
|
||||
this.chooseExistingVaultScene = chooseExistingVaultScene;
|
||||
this.createNewVaultScene = createNewVaultScene;
|
||||
}
|
||||
|
||||
public void createNewVault() {
|
||||
LOG.debug("AddVaultWelcomeController.createNewVault()");
|
||||
window.setScene(createNewVaultScene.get());
|
||||
}
|
||||
|
||||
public void chooseExistingVault() {
|
||||
LOG.debug("AddVaultWelcomeController.chooseExistingVault()");
|
||||
window.setScene(chooseExistingVaultScene.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import dagger.Subcomponent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
@Subcomponent(modules = {AddVaultModule.class})
|
||||
public interface AddVaultWizardComponent {
|
||||
|
||||
@AddVaultWizardWindow
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.ADDVAULT_WELCOME)
|
||||
Lazy<Scene> scene();
|
||||
|
||||
default void showAddVaultWizard() {
|
||||
Stage stage = window();
|
||||
stage.setScene(scene().get());
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
}
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
|
||||
AddVaultWizardComponent build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface AddVaultWizardScoped {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@interface AddVaultWizardWindow {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class ChooseExistingVaultController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ChooseExistingVaultController.class);
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> welcomeScene;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final Lazy<Scene> errorScene;
|
||||
private final ObjectProperty<Path> vaultPath;
|
||||
private final ObjectProperty<Vault> vault;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
@Inject
|
||||
ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.ADDVAULT_EXISTING_ERROR) Lazy<Scene> errorScene, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.welcomeScene = welcomeScene;
|
||||
this.successScene = successScene;
|
||||
this.errorScene = errorScene;
|
||||
this.vaultPath = vaultPath;
|
||||
this.vault = vault;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back() {
|
||||
window.setScene(welcomeScene.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void chooseFileAndNext() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(resourceBundle.getString("addvaultwizard.existing.filePickerTitle"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator"));
|
||||
File masterkeyFile = fileChooser.showOpenDialog(window);
|
||||
if (masterkeyFile != null) {
|
||||
vaultPath.setValue(masterkeyFile.toPath().toAbsolutePath().getParent());
|
||||
try {
|
||||
Vault newVault = vaultListManager.add(vaultPath.get());
|
||||
vault.set(newVault);
|
||||
window.setScene(successScene.get());
|
||||
} catch (NoSuchFileException e) {
|
||||
LOG.error("Failed to open existing vault.", e);
|
||||
window.setScene(errorScene.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class CreateNewVaultLocationController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultLocationController.class);
|
||||
private static final Path DEFAULT_CUSTOM_VAULT_PATH = Paths.get(System.getProperty("user.home"));
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> chooseNameScene;
|
||||
private final Lazy<Scene> choosePasswordScene;
|
||||
private final LocationPresets locationPresets;
|
||||
private final ObjectProperty<Path> vaultPath;
|
||||
private final StringProperty vaultName;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final BooleanBinding validVaultPath;
|
||||
private final BooleanBinding invalidVaultPath;
|
||||
private final BooleanProperty usePresetPath;
|
||||
private final StringProperty warningText;
|
||||
|
||||
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
|
||||
public ToggleGroup predefinedLocationToggler;
|
||||
public RadioButton dropboxRadioButton;
|
||||
public RadioButton gdriveRadioButton;
|
||||
public RadioButton customRadioButton;
|
||||
|
||||
@Inject
|
||||
CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> choosePasswordScene, LocationPresets locationPresets, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.chooseNameScene = chooseNameScene;
|
||||
this.choosePasswordScene = choosePasswordScene;
|
||||
this.locationPresets = locationPresets;
|
||||
this.vaultPath = vaultPath;
|
||||
this.vaultName = vaultName;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.validVaultPath = Bindings.createBooleanBinding(this::isValidVaultPath, vaultPath);
|
||||
this.invalidVaultPath = validVaultPath.not();
|
||||
this.usePresetPath = new SimpleBooleanProperty();
|
||||
this.warningText = new SimpleStringProperty();
|
||||
}
|
||||
|
||||
private boolean isValidVaultPath() {
|
||||
return vaultPath.get() != null && Files.notExists(vaultPath.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
|
||||
usePresetPath.bind(predefinedLocationToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
|
||||
vaultPath.addListener(this::vaultPathDidChange);
|
||||
}
|
||||
|
||||
private void vaultPathDidChange(@SuppressWarnings("unused") ObservableValue<? extends Path> observable, @SuppressWarnings("unused") Path oldValue, Path newValue) {
|
||||
if (!Files.notExists(newValue)) {
|
||||
warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
|
||||
} else {
|
||||
warningText.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
||||
if (dropboxRadioButton.equals(newValue)) {
|
||||
vaultPath.set(locationPresets.getDropboxLocation().resolve(vaultName.get()));
|
||||
} else if (gdriveRadioButton.equals(newValue)) {
|
||||
vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get()));
|
||||
} else if (customRadioButton.equals(newValue)) {
|
||||
vaultPath.set(customVaultPath.resolve(vaultName.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back() {
|
||||
window.setScene(chooseNameScene.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void next() {
|
||||
try {
|
||||
// check if we have write access AND the vaultPath doesn't already exist:
|
||||
assert Files.isDirectory(vaultPath.get().getParent());
|
||||
Path createdDir = Files.createDirectory(vaultPath.get());
|
||||
Files.delete(createdDir); // assert: dir exists and is empty
|
||||
window.setScene(choosePasswordScene.get());
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
LOG.warn("Can not use already existing vault path {}", vaultPath.get());
|
||||
warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Can not create vault at path: {}", vaultPath.get());
|
||||
LOG.warn("Thrown Exception:", e);
|
||||
warningText.set(resourceBundle.getString("addvaultwizard.new.ioException"));
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void chooseCustomVaultPath() {
|
||||
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||
directoryChooser.setTitle(resourceBundle.getString("addvaultwizard.new.directoryPickerTitle"));
|
||||
directoryChooser.setInitialDirectory(customVaultPath.toFile());
|
||||
final File file = directoryChooser.showDialog(window);
|
||||
if (file != null) {
|
||||
customVaultPath = file.toPath().toAbsolutePath();
|
||||
vaultPath.set(customVaultPath.resolve(vaultName.get()));
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Path getVaultPath() {
|
||||
return vaultPath.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Path> vaultPathProperty() {
|
||||
return vaultPath;
|
||||
}
|
||||
|
||||
public BooleanBinding invalidVaultPathProperty() {
|
||||
return invalidVaultPath;
|
||||
}
|
||||
|
||||
public Boolean getInvalidVaultPath() {
|
||||
return invalidVaultPath.get();
|
||||
}
|
||||
|
||||
public LocationPresets getLocationPresets() {
|
||||
return locationPresets;
|
||||
}
|
||||
|
||||
public BooleanProperty usePresetPathProperty() {
|
||||
return usePresetPath;
|
||||
}
|
||||
|
||||
public boolean getUsePresetPath() {
|
||||
return usePresetPath.get();
|
||||
}
|
||||
|
||||
public StringProperty warningTextProperty() {
|
||||
return warningText;
|
||||
}
|
||||
|
||||
public String getWarningText() {
|
||||
return warningText.get();
|
||||
}
|
||||
|
||||
public BooleanBinding showWarningProperty() {
|
||||
return warningText.isNotEmpty();
|
||||
}
|
||||
|
||||
public boolean isShowWarning() {
|
||||
return showWarningProperty().get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class CreateNewVaultNameController implements FxController {
|
||||
|
||||
private static final Pattern VALID_NAME_PATTERN = Pattern.compile("[\\w -]+", Pattern.UNICODE_CHARACTER_CLASS);
|
||||
|
||||
public TextField textField;
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> welcomeScene;
|
||||
private final Lazy<Scene> chooseLocationScene;
|
||||
private final ObjectProperty<Path> vaultPath;
|
||||
private final StringProperty vaultName;
|
||||
private final BooleanBinding validVaultName;
|
||||
private final BooleanBinding invalidVaultName;
|
||||
private final StringBinding warningText;
|
||||
|
||||
@Inject
|
||||
CreateNewVaultNameController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy<Scene> welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, ObjectProperty<Path> vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.welcomeScene = welcomeScene;
|
||||
this.chooseLocationScene = chooseLocationScene;
|
||||
this.vaultPath = vaultPath;
|
||||
this.vaultName = vaultName;
|
||||
this.validVaultName = Bindings.createBooleanBinding(this::isValidVaultName, vaultName);
|
||||
this.invalidVaultName = validVaultName.not();
|
||||
this.warningText = Bindings.when(vaultName.isNotEmpty().and(invalidVaultName)).then(resourceBundle.getString("addvaultwizard.new.invalidName")).otherwise((String) null);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
vaultName.bind(textField.textProperty());
|
||||
vaultName.addListener(this::vaultNameChanged);
|
||||
}
|
||||
|
||||
public boolean isValidVaultName() {
|
||||
return vaultName.get() != null && VALID_NAME_PATTERN.matcher(vaultName.get().trim()).matches();
|
||||
}
|
||||
|
||||
private void vaultNameChanged(@SuppressWarnings("unused") Observable observable) {
|
||||
if (isValidVaultName()) {
|
||||
if (vaultPath.get() != null) {
|
||||
// update vaultPath if it is already set but the user went back to change its name:
|
||||
vaultPath.set(vaultPath.get().resolveSibling(vaultName.get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back() {
|
||||
window.setScene(welcomeScene.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void next() {
|
||||
window.setScene(chooseLocationScene.get());
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public BooleanBinding invalidVaultNameProperty() {
|
||||
return invalidVaultName;
|
||||
}
|
||||
|
||||
public boolean isInvalidVaultName() {
|
||||
return invalidVaultName.get();
|
||||
}
|
||||
|
||||
public StringBinding warningTextProperty() {
|
||||
return warningText;
|
||||
}
|
||||
|
||||
public String getWarningText() {
|
||||
return warningText.get();
|
||||
}
|
||||
|
||||
public BooleanBinding showWarningProperty() {
|
||||
return warningText.isNotEmpty();
|
||||
}
|
||||
|
||||
public boolean isShowWarning() {
|
||||
return showWarningProperty().get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.Tasks;
|
||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Collections;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class CreateNewVaultPasswordController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultPasswordController.class);
|
||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> chooseLocationScene;
|
||||
private final Lazy<Scene> recoveryKeyScene;
|
||||
private final ExecutorService executor;
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final StringProperty vaultNameProperty;
|
||||
private final ObjectProperty<Path> vaultPathProperty;
|
||||
private final ObjectProperty<Vault> vaultProperty;
|
||||
private final StringProperty recoveryKeyProperty;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final PasswordStrengthUtil strengthRater;
|
||||
private final ReadmeGenerator readmeGenerator;
|
||||
private final IntegerProperty passwordStrength;
|
||||
private final BooleanProperty processing;
|
||||
private final BooleanProperty readyToCreateVault;
|
||||
private final ObjectBinding<ContentDisplay> createVaultButtonState;
|
||||
|
||||
public NiceSecurePasswordField passwordField;
|
||||
public NiceSecurePasswordField reenterField;
|
||||
public Label passwordStrengthLabel;
|
||||
public HBox passwordMatchBox;
|
||||
public FontAwesome5IconView checkmark;
|
||||
public FontAwesome5IconView cross;
|
||||
public Label passwordMatchLabel;
|
||||
|
||||
@Inject
|
||||
CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, ReadmeGenerator readmeGenerator) {
|
||||
this.window = window;
|
||||
this.chooseLocationScene = chooseLocationScene;
|
||||
this.recoveryKeyScene = recoveryKeyScene;
|
||||
this.executor = executor;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.vaultNameProperty = vaultName;
|
||||
this.vaultPathProperty = vaultPath;
|
||||
this.vaultProperty = vault;
|
||||
this.recoveryKeyProperty = recoveryKey;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.strengthRater = strengthRater;
|
||||
this.readmeGenerator = readmeGenerator;
|
||||
this.passwordStrength = new SimpleIntegerProperty(-1);
|
||||
this.processing = new SimpleBooleanProperty();
|
||||
this.readyToCreateVault = new SimpleBooleanProperty();
|
||||
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
//binds the actual strength value to the rating of the password util
|
||||
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters().toString()), passwordField.textProperty()));
|
||||
//binding indicating if the passwords not match
|
||||
BooleanBinding passwordsMatch = Bindings.createBooleanBinding(() -> CharSequence.compare(passwordField.getCharacters(), reenterField.getCharacters()) == 0, passwordField.textProperty(), reenterField.textProperty());
|
||||
BooleanBinding reenterFieldNotEmpty = reenterField.textProperty().isNotEmpty();
|
||||
readyToCreateVault.bind(reenterFieldNotEmpty.and(passwordsMatch).and(processing.not()));
|
||||
//make match indicator invisible when passwords do not match or one is empty
|
||||
passwordMatchBox.visibleProperty().bind(reenterFieldNotEmpty);
|
||||
checkmark.visibleProperty().bind(passwordsMatch.and(reenterFieldNotEmpty));
|
||||
checkmark.managedProperty().bind(checkmark.visibleProperty());
|
||||
cross.visibleProperty().bind(passwordsMatch.not().and(reenterFieldNotEmpty));
|
||||
cross.managedProperty().bind(cross.visibleProperty());
|
||||
passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("addvaultwizard.new.passwordsMatch")).otherwise(resourceBundle.getString("addvaultwizard.new.passwordsDoNotMatch")));
|
||||
|
||||
//bindsings for the password strength indicator
|
||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back() {
|
||||
window.setScene(chooseLocationScene.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void next() {
|
||||
Path pathToVault = vaultPathProperty.get();
|
||||
|
||||
try {
|
||||
Files.createDirectory(pathToVault);
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
LOG.error("Vault dir already exists.", e);
|
||||
window.setScene(chooseLocationScene.get());
|
||||
} catch (IOException e) {
|
||||
// TODO show generic error screen
|
||||
LOG.error("", e);
|
||||
}
|
||||
|
||||
processing.set(true);
|
||||
Tasks.create(() -> {
|
||||
initializeVault(pathToVault, passwordField.getCharacters());
|
||||
return recoveryKeyFactory.createRecoveryKey(pathToVault, passwordField.getCharacters());
|
||||
}).onSuccess(recoveryKey -> {
|
||||
initializationSucceeded(pathToVault, recoveryKey);
|
||||
}).onError(IOException.class, e -> {
|
||||
// TODO show generic error screen
|
||||
LOG.error("", e);
|
||||
}).andFinally(() -> {
|
||||
processing.set(false);
|
||||
}).runOnce(executor);
|
||||
}
|
||||
|
||||
private void initializeVault(Path path, CharSequence passphrase) throws IOException {
|
||||
CryptoFileSystemProvider.initialize(path, MASTERKEY_FILENAME, passphrase);
|
||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
||||
.withPassphrase(passphrase) //
|
||||
.withFlags(Collections.emptySet()) //
|
||||
.withMasterkeyFilename(MASTERKEY_FILENAME) //
|
||||
.build();
|
||||
|
||||
String vaultReadmeFileName = resourceBundle.getString("addvault.new.readme.accessLocation.fileName");
|
||||
try (FileSystem fs = CryptoFileSystemProvider.newFileSystem(path, fsProps); //
|
||||
WritableByteChannel ch = Files.newByteChannel(fs.getPath("/", vaultReadmeFileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
|
||||
ch.write(US_ASCII.encode(readmeGenerator.createVaultAccessLocationReadmeRtf()));
|
||||
}
|
||||
|
||||
String storagePathReadmeFileName = resourceBundle.getString("addvault.new.readme.storageLocation.fileName");
|
||||
try (WritableByteChannel ch = Files.newByteChannel(path.resolve(storagePathReadmeFileName), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) {
|
||||
ch.write(US_ASCII.encode(readmeGenerator.createVaultStorageLocationReadmeRtf()));
|
||||
}
|
||||
LOG.info("Created vault at {}", path);
|
||||
}
|
||||
|
||||
private void initializationSucceeded(Path pathToVault, String recoveryKey) {
|
||||
try {
|
||||
Vault newVault = vaultListManager.add(pathToVault);
|
||||
vaultProperty.set(newVault);
|
||||
recoveryKeyProperty.set(recoveryKey);
|
||||
window.setScene(recoveryKeyScene.get());
|
||||
} catch (NoSuchFileException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public String getVaultName() {
|
||||
return vaultNameProperty.get();
|
||||
}
|
||||
|
||||
public StringProperty vaultNameProperty() {
|
||||
return vaultNameProperty;
|
||||
}
|
||||
|
||||
public IntegerProperty passwordStrengthProperty() {
|
||||
return passwordStrength;
|
||||
}
|
||||
|
||||
public int getPasswordStrength() {
|
||||
return passwordStrength.get();
|
||||
}
|
||||
|
||||
public BooleanProperty readyToCreateVaultProperty() {
|
||||
return readyToCreateVault;
|
||||
}
|
||||
|
||||
public boolean isReadyToCreateVault() {
|
||||
return readyToCreateVault.get();
|
||||
}
|
||||
|
||||
public ObjectBinding<ContentDisplay> createVaultButtonStateProperty() {
|
||||
return createVaultButtonState;
|
||||
}
|
||||
|
||||
public ContentDisplay getCreateVaultButtonState() {
|
||||
return processing.get() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
public class CreateNewVaultRecoveryKeyController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final StringProperty recoveryKeyProperty;
|
||||
|
||||
@Inject
|
||||
CreateNewVaultRecoveryKeyController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, @Named("recoveryKey")StringProperty recoveryKey) {
|
||||
this.window = window;
|
||||
this.successScene = successScene;
|
||||
this.recoveryKeyProperty = recoveryKey;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void next() {
|
||||
window.setScene(successScene.get());
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public String getRecoveryKey() {
|
||||
return recoveryKeyProperty.get();
|
||||
}
|
||||
|
||||
public StringProperty recoveryKeyProperty() {
|
||||
return recoveryKeyProperty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class LocationPresets {
|
||||
|
||||
private static final String USER_HOME = System.getProperty("user.home");
|
||||
private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"};
|
||||
private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive"};
|
||||
|
||||
private final ReadOnlyObjectProperty<Path> dropboxLocation;
|
||||
private final ReadOnlyObjectProperty<Path> gdriveLocation;
|
||||
private final BooleanBinding foundDropbox;
|
||||
private final BooleanBinding foundGdrive;
|
||||
|
||||
@Inject
|
||||
public LocationPresets() {
|
||||
this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS));
|
||||
this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS));
|
||||
this.foundDropbox = dropboxLocation.isNotNull();
|
||||
this.foundGdrive = gdriveLocation.isNotNull();
|
||||
}
|
||||
|
||||
private static Path existingWritablePath(String... candidates) {
|
||||
for (String candidate : candidates) {
|
||||
Path path = Paths.get(resolveHomePath(candidate));
|
||||
if (Files.isDirectory(path)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String resolveHomePath(String path) {
|
||||
if (path.startsWith("~/")) {
|
||||
return USER_HOME + path.substring(1);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
/* Observables */
|
||||
|
||||
public ReadOnlyObjectProperty<Path> dropboxLocationProperty() {
|
||||
return dropboxLocation;
|
||||
}
|
||||
|
||||
public Path getDropboxLocation() {
|
||||
return dropboxLocation.get();
|
||||
}
|
||||
|
||||
public BooleanBinding foundDropboxProperty() {
|
||||
return foundDropbox;
|
||||
}
|
||||
|
||||
public boolean isFoundDropbox() {
|
||||
return foundDropbox.get();
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<Path> gdriveLocationProperty() {
|
||||
return gdriveLocation;
|
||||
}
|
||||
|
||||
public Path getGdriveLocation() {
|
||||
return gdriveLocation.get();
|
||||
}
|
||||
|
||||
public BooleanBinding froundGdriveProperty() {
|
||||
return foundGdrive;
|
||||
}
|
||||
|
||||
public boolean isFoundGdrive() {
|
||||
return foundGdrive.get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class ReadmeGenerator {
|
||||
|
||||
// specs: https://web.archive.org/web/20190708132914/http://www.kleinlercher.at/tools/Windows_Protocols/Word2007RTFSpec9.pdf
|
||||
private static final String RTF_HEADER = "{\\rtf1\\fbidis\\ansi\\uc0\\fs32\n";
|
||||
private static final String RTF_FOOTER = "}";
|
||||
private static final String HELP_URL = "{\\field{\\*\\fldinst HYPERLINK \"http://www.google.com/\"}{\\fldrslt google.com}}";
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
@Inject
|
||||
public ReadmeGenerator(ResourceBundle resourceBundle){
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
public String createVaultStorageLocationReadmeRtf() {
|
||||
return createDocument(List.of( //
|
||||
resourceBundle.getString("addvault.new.readme.storageLocation.1"), //
|
||||
resourceBundle.getString("addvault.new.readme.storageLocation.2"), //
|
||||
resourceBundle.getString("addvault.new.readme.storageLocation.3"), //
|
||||
String.format(resourceBundle.getString("addvault.new.readme.storageLocation.4"), HELP_URL) //
|
||||
));
|
||||
}
|
||||
|
||||
public String createVaultAccessLocationReadmeRtf() {
|
||||
return createDocument(List.of( //
|
||||
resourceBundle.getString("addvault.new.readme.accessLocation.1"), //
|
||||
resourceBundle.getString("addvault.new.readme.accessLocation.2"), //
|
||||
resourceBundle.getString("addvault.new.readme.accessLocation.3") //
|
||||
));
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
String createDocument(Iterable<String> paragraphs) {
|
||||
StringBuilder sb = new StringBuilder(RTF_HEADER);
|
||||
for (String p : paragraphs) {
|
||||
sb.append("\\par {\\sa80 ");
|
||||
appendEscaped(sb, p);
|
||||
sb.append("}\n");
|
||||
}
|
||||
sb.append(RTF_FOOTER);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
String escapeNonAsciiChars(CharSequence input) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
appendEscaped(sb, input);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void appendEscaped(StringBuilder sb, CharSequence input) {
|
||||
input.chars().forEachOrdered(c -> {
|
||||
if (c < 128) {
|
||||
sb.append((char) c);
|
||||
} else if (c < 0xFFFF) {
|
||||
sb.append("\\u").append(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.cryptomator.ui.changepassword;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Lazy;
|
||||
import dagger.Subcomponent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
@ChangePasswordScoped
|
||||
@Subcomponent(modules = {ChangePasswordModule.class})
|
||||
public interface ChangePasswordComponent {
|
||||
|
||||
@ChangePasswordWindow
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.CHANGEPASSWORD)
|
||||
Lazy<Scene> scene();
|
||||
|
||||
default void showChangePasswordWindow() {
|
||||
Stage stage = window();
|
||||
stage.setScene(scene().get());
|
||||
stage.show();
|
||||
}
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
|
||||
@BindsInstance
|
||||
Builder vault(@ChangePasswordWindow Vault vault);
|
||||
|
||||
@BindsInstance
|
||||
Builder owner(@Named("changePasswordOwner") Stage owner);
|
||||
|
||||
ChangePasswordComponent build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package org.cryptomator.ui.changepassword;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@ChangePasswordScoped
|
||||
public class ChangePasswordController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
|
||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final PasswordStrengthUtil strengthRater;
|
||||
private final IntegerProperty passwordStrength;
|
||||
|
||||
public NiceSecurePasswordField oldPasswordField;
|
||||
public NiceSecurePasswordField newPasswordField;
|
||||
public NiceSecurePasswordField reenterPasswordField;
|
||||
public Label passwordStrengthLabel;
|
||||
public HBox passwordMatchBox;
|
||||
public FontAwesome5IconView checkmark;
|
||||
public FontAwesome5IconView cross;
|
||||
public Label passwordMatchLabel;
|
||||
public CheckBox finalConfirmationCheckbox;
|
||||
public Button finishButton;
|
||||
|
||||
@Inject
|
||||
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.strengthRater = strengthRater;
|
||||
this.passwordStrength = new SimpleIntegerProperty(-1);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
//binds the actual strength value to the rating of the password util
|
||||
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(newPasswordField.getCharacters().toString()), newPasswordField.textProperty()));
|
||||
//binding indicating if the passwords not match
|
||||
BooleanBinding passwordsMatch = Bindings.createBooleanBinding(() -> CharSequence.compare(newPasswordField.getCharacters(), reenterPasswordField.getCharacters()) == 0, newPasswordField.textProperty(), reenterPasswordField.textProperty());
|
||||
BooleanBinding reenterFieldNotEmpty = reenterPasswordField.textProperty().isNotEmpty();
|
||||
//disable the finish button when passwords do not match or one is empty
|
||||
finishButton.disableProperty().bind(reenterFieldNotEmpty.not().or(passwordsMatch.not()).or(finalConfirmationCheckbox.selectedProperty().not()));
|
||||
//make match indicator invisible when passwords do not match or one is empty
|
||||
passwordMatchBox.visibleProperty().bind(reenterFieldNotEmpty);
|
||||
checkmark.visibleProperty().bind(passwordsMatch.and(reenterFieldNotEmpty));
|
||||
checkmark.managedProperty().bind(checkmark.visibleProperty());
|
||||
cross.visibleProperty().bind(passwordsMatch.not().and(reenterFieldNotEmpty));
|
||||
cross.managedProperty().bind(cross.visibleProperty());
|
||||
passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("changepassword.passwordsMatch")).otherwise(resourceBundle.getString("changepassword.passwordsDoNotMatch")));
|
||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void cancel() {
|
||||
window.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void finish() {
|
||||
try {
|
||||
CryptoFileSystemProvider.changePassphrase(vault.getPath(), MASTERKEY_FILENAME, oldPasswordField.getCharacters(), newPasswordField.getCharacters());
|
||||
LOG.info("Successful changed password for {}", vault.getDisplayableName());
|
||||
window.close();
|
||||
} catch (IOException e) {
|
||||
//TODO
|
||||
LOG.error("IO error occured during password change. Unable to perform operation.", e);
|
||||
e.printStackTrace();
|
||||
} catch (InvalidPassphraseException e) {
|
||||
//TODO
|
||||
LOG.info("Wrong old password.");
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Vault getVault() {
|
||||
return vault;
|
||||
}
|
||||
|
||||
public IntegerProperty passwordStrengthProperty() {
|
||||
return passwordStrength;
|
||||
}
|
||||
|
||||
public int getPasswordStrength() {
|
||||
return passwordStrength.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.cryptomator.ui.changepassword;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
abstract class ChangePasswordModule {
|
||||
|
||||
@Provides
|
||||
@ChangePasswordWindow
|
||||
@ChangePasswordScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ChangePasswordWindow
|
||||
@ChangePasswordScoped
|
||||
static Stage provideStage(@Named("changePasswordOwner") Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional<Image> windowIcon) {
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(resourceBundle.getString("changepassword.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
windowIcon.ifPresent(stage.getIcons()::add);
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.CHANGEPASSWORD)
|
||||
@ChangePasswordScoped
|
||||
static Scene provideUnlockScene(@ChangePasswordWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/changepassword.fxml");
|
||||
}
|
||||
|
||||
|
||||
// ------------------
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(ChangePasswordController.class)
|
||||
abstract FxController bindUnlockController(ChangePasswordController controller);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.cryptomator.ui.changepassword;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ChangePasswordScoped {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.cryptomator.ui.changepassword;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@interface ChangePasswordWindow {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.function.Function;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class DefaultSceneFactory implements Function<Parent, Scene> {
|
||||
|
||||
protected static final KeyCodeCombination ALT_F4 = new KeyCodeCombination(KeyCode.F4, KeyCombination.ALT_DOWN);
|
||||
protected static final KeyCodeCombination SHORTCUT_W = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN);
|
||||
|
||||
protected final Settings settings;
|
||||
|
||||
@Inject
|
||||
public DefaultSceneFactory(Settings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scene apply(Parent root) {
|
||||
Scene scene = new Scene(root);
|
||||
configureRoot(root);
|
||||
configureScene(scene);
|
||||
return scene;
|
||||
}
|
||||
|
||||
protected void configureRoot(Parent root) {
|
||||
root.nodeOrientationProperty().bind(settings.userInterfaceOrientation());
|
||||
}
|
||||
|
||||
protected void configureScene(Scene scene) {
|
||||
scene.windowProperty().addListener(observable -> {
|
||||
Window window = scene.getWindow();
|
||||
if (window instanceof Stage) {
|
||||
setupDefaultAccelerators(scene, (Stage) window);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void setupDefaultAccelerators(Scene scene, Stage stage) {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
scene.getAccelerators().put(ALT_F4, stage::close);
|
||||
} else {
|
||||
scene.getAccelerators().put(SHORTCUT_W, stage::close);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class FXMLLoaderFactory {
|
||||
|
||||
private final Map<Class<? extends FxController>, Provider<FxController>> controllerFactories;
|
||||
private final Function<Parent, Scene> sceneFactory;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
public FXMLLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> controllerFactories, Function<Parent, Scene> sceneFactory, ResourceBundle resourceBundle) {
|
||||
this.controllerFactories = controllerFactories;
|
||||
this.sceneFactory = sceneFactory;
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A new FXMLLoader instance
|
||||
*/
|
||||
public FXMLLoader construct() {
|
||||
FXMLLoader loader = new FXMLLoader();
|
||||
loader.setControllerFactory(this::constructController);
|
||||
loader.setResources(resourceBundle);
|
||||
return loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the FXML given fxml resource in a new FXMLLoader instance.
|
||||
*
|
||||
* @param fxmlResourceName Name of the resource (as in {@link Class#getResource(String)}).
|
||||
* @return The FXMLLoader used to load the file
|
||||
* @throws IOException if an error occurs while loading the FXML file
|
||||
*/
|
||||
public FXMLLoader load(String fxmlResourceName) throws IOException {
|
||||
FXMLLoader loader = construct();
|
||||
try (InputStream in = getClass().getResourceAsStream(fxmlResourceName)) {
|
||||
loader.load(in);
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link #load(String) Loads} the FXML file and creates a new Scene containing the loaded ui.
|
||||
*
|
||||
* @param fxmlResourceName Name of the resource (as in {@link Class#getResource(String)}).
|
||||
* @throws UncheckedIOException wrapping any IOException thrown by {@link #load(String)).
|
||||
*/
|
||||
public Scene createScene(String fxmlResourceName) {
|
||||
final FXMLLoader loader;
|
||||
try {
|
||||
loader = load(fxmlResourceName);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to load " + fxmlResourceName, e);
|
||||
}
|
||||
Parent root = loader.getRoot();
|
||||
List<String> addtionalStyleSheets = Splitter.on(',').omitEmptyStrings().splitToList(resourceBundle.getString("additionalStyleSheets"));
|
||||
addtionalStyleSheets.forEach(styleSheet -> root.getStylesheets().add("/css/" + styleSheet));
|
||||
return sceneFactory.apply(root);
|
||||
}
|
||||
|
||||
private FxController constructController(Class<?> aClass) {
|
||||
if (!controllerFactories.containsKey(aClass)) {
|
||||
throw new IllegalArgumentException("ViewController not registered: " + aClass);
|
||||
} else {
|
||||
return controllerFactories.get(aClass).get();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import javafx.scene.text.Font;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class FontLoader {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FontLoader.class);
|
||||
private static final double DEFAULT_FONT_SIZE = 12;
|
||||
|
||||
public static Font load(String resourcePath) throws FontLoaderException {
|
||||
try (InputStream in = FontLoader.class.getResourceAsStream(resourcePath)) {
|
||||
if (in == null) {
|
||||
throw new FontLoaderException(resourcePath);
|
||||
} else {
|
||||
return load(resourcePath, in);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new FontLoaderException(resourcePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Font load(String resourcePath, InputStream in) throws FontLoaderException {
|
||||
Font font = Font.loadFont(in, DEFAULT_FONT_SIZE);
|
||||
if (font != null) {
|
||||
LOG.debug("Loaded family: {}", font.getFamily());
|
||||
return font;
|
||||
} else {
|
||||
throw new FontLoaderException(resourcePath);
|
||||
}
|
||||
}
|
||||
|
||||
public static class FontLoaderException extends IOException {
|
||||
|
||||
private FontLoaderException(String resourceName) {
|
||||
super("Failed to load font: " + resourceName);
|
||||
}
|
||||
|
||||
private FontLoaderException(String resourceName, Throwable cause) {
|
||||
super("Failed to load font: " + resourceName, cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
public interface FxController {
|
||||
}
|
||||
@@ -3,21 +3,22 @@
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
import dagger.MapKey;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import dagger.MapKey;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
// TODO rename after refactoring
|
||||
@Documented
|
||||
@Target(METHOD)
|
||||
@Retention(RUNTIME)
|
||||
@MapKey
|
||||
public @interface ViewControllerKey {
|
||||
Class<? extends ViewController> value();
|
||||
public @interface FxControllerKey {
|
||||
Class<? extends FxController> value();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
public enum FxmlFile {
|
||||
ADDVAULT_WELCOME("/fxml/addvault_welcome.fxml"), //
|
||||
ADDVAULT_EXISTING("/fxml/addvault_existing.fxml"), //
|
||||
ADDVAULT_EXISTING_ERROR("/fxml/addvault_existing_error.fxml"),
|
||||
ADDVAULT_NEW_NAME("/fxml/addvault_new_name.fxml"), //
|
||||
ADDVAULT_NEW_LOCATION("/fxml/addvault_new_location.fxml"), //
|
||||
ADDVAULT_NEW_PASSWORD("/fxml/addvault_new_password.fxml"), //
|
||||
ADDVAULT_NEW_RECOVERYKEY("/fxml/addvault_new_recoverykey.fxml"), //
|
||||
ADDVAULT_SUCCESS("/fxml/addvault_success.fxml"), //
|
||||
CHANGEPASSWORD("/fxml/changepassword.fxml"), //
|
||||
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
||||
MAIN_WINDOW("/fxml/main_window.fxml"), //
|
||||
MIGRATION_RUN("/fxml/migration_run.fxml"), //
|
||||
MIGRATION_START("/fxml/migration_start.fxml"), //
|
||||
MIGRATION_SUCCESS("/fxml/migration_success.fxml"), //
|
||||
PREFERENCES("/fxml/preferences.fxml"), //
|
||||
QUIT("/fxml/quit.fxml"), //
|
||||
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
|
||||
RECOVERYKEY_DISPLAY("/fxml/recoverykey_display.fxml"), //
|
||||
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
|
||||
UNLOCK("/fxml/unlock.fxml"),
|
||||
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //
|
||||
VAULT_OPTIONS("/fxml/vault_options.fxml"), //
|
||||
WRONGFILEALERT("/fxml/wrongfilealert.fxml");
|
||||
|
||||
private final String ressourcePathString;
|
||||
|
||||
FxmlFile(String ressourcePathString) {
|
||||
this.ressourcePathString = ressourcePathString;
|
||||
}
|
||||
|
||||
public String getRessourcePathString(){
|
||||
return ressourcePathString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
public @interface FxmlScene {
|
||||
|
||||
FxmlFile value();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
|
||||
* All rights reserved.
|
||||
* This program and the accompanying materials are made available under the terms of the accompanying LICENSE file.
|
||||
*
|
||||
* Contributors:
|
||||
* Jean-Noël Charon - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class PasswordStrengthUtil {
|
||||
|
||||
private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length
|
||||
private static final String RESSOURCE_PREFIX = "passwordStrength.messageLabel.";
|
||||
|
||||
private final Zxcvbn zxcvbn;
|
||||
private final List<String> sanitizedInputs;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
@Inject
|
||||
public PasswordStrengthUtil(ResourceBundle resourceBundle) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.zxcvbn = new Zxcvbn();
|
||||
this.sanitizedInputs = List.of("cryptomator");
|
||||
}
|
||||
|
||||
public int computeRate(String password) {
|
||||
if (Strings.isNullOrEmpty(password)) {
|
||||
return -1;
|
||||
} else {
|
||||
int numCharsToRate = Math.min(PW_TRUNC_LEN, password.length());
|
||||
return zxcvbn.measure(password.substring(0, numCharsToRate), sanitizedInputs).getScore();
|
||||
}
|
||||
}
|
||||
|
||||
public String getStrengthDescription(Number score) {
|
||||
if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) {
|
||||
return resourceBundle.getString("passwordStrength.messageLabel." + score.intValue());
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.util;
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -1,203 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
|
||||
* 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 - password strength meter
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.text.Text;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.util.PasswordStrengthUtil;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ChangePasswordController implements ViewController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
|
||||
|
||||
private final Application app;
|
||||
private final PasswordStrengthUtil strengthRater;
|
||||
private final Localization localization;
|
||||
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(-1); // 0-4
|
||||
private Optional<ChangePasswordListener> listener = Optional.empty();
|
||||
private Vault vault;
|
||||
|
||||
@Inject
|
||||
public ChangePasswordController(Application app, PasswordStrengthUtil strengthRater, Localization localization) {
|
||||
this.app = app;
|
||||
this.strengthRater = strengthRater;
|
||||
this.localization = localization;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private SecPasswordField oldPasswordField;
|
||||
|
||||
@FXML
|
||||
private SecPasswordField newPasswordField;
|
||||
|
||||
@FXML
|
||||
private SecPasswordField retypePasswordField;
|
||||
|
||||
@FXML
|
||||
private Button changePasswordButton;
|
||||
|
||||
@FXML
|
||||
private Text messageText;
|
||||
|
||||
@FXML
|
||||
private Hyperlink downloadsPageLink;
|
||||
|
||||
@FXML
|
||||
private Label passwordStrengthLabel;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel0;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel1;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel2;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel3;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel4;
|
||||
|
||||
@FXML
|
||||
private GridPane root;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
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));
|
||||
passwordStrengthLevel2.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(2), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel3.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(3), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel4.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(4), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||
}
|
||||
|
||||
private void passwordsChanged(@SuppressWarnings("unused") 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:
|
||||
changePasswordButton.setDefaultButton(false);
|
||||
changePasswordButton.setDefaultButton(true);
|
||||
messageText.setText(null);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Downloads link
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
public void didClickDownloadsLink(ActionEvent event) {
|
||||
app.getHostServices().showDocument("https://cryptomator.org/downloads/");
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Change password button
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
private void didClickChangePasswordButton(ActionEvent event) {
|
||||
downloadsPageLink.setVisible(false);
|
||||
try {
|
||||
vault.changePassphrase(oldPasswordField.getCharacters(), newPasswordField.getCharacters());
|
||||
messageText.setText(null);
|
||||
listener.ifPresent(this::invokeListenerLater);
|
||||
} catch (InvalidPassphraseException e) {
|
||||
messageText.setText(localization.getString("changePassword.errorMessage.wrongPassword"));
|
||||
Platform.runLater(oldPasswordField::requestFocus);
|
||||
} catch (UncheckedIOException | IOException ex) {
|
||||
messageText.setText(localization.getString("changePassword.errorMessage.decryptionFailed"));
|
||||
LOG.error("Decryption failed for technical reasons.", ex);
|
||||
} catch (UnsupportedVaultFormatException e) {
|
||||
downloadsPageLink.setVisible(true);
|
||||
LOG.warn("Unable to unlock vault: " + e.getMessage());
|
||||
if (e.isVaultOlderThanSoftware()) {
|
||||
messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
|
||||
} else if (e.isSoftwareOlderThanVault()) {
|
||||
messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
|
||||
}
|
||||
} finally {
|
||||
oldPasswordField.swipe();
|
||||
newPasswordField.swipe();
|
||||
retypePasswordField.swipe();
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public ChangePasswordListener getListener() {
|
||||
return listener.orElse(null);
|
||||
}
|
||||
|
||||
public void setListener(ChangePasswordListener listener) {
|
||||
this.listener = Optional.ofNullable(listener);
|
||||
}
|
||||
|
||||
/* callback */
|
||||
|
||||
private void invokeListenerLater(ChangePasswordListener listener) {
|
||||
Platform.runLater(() -> {
|
||||
listener.didChangePassword();
|
||||
});
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface ChangePasswordListener {
|
||||
void didChangePassword();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 - password strength meter
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.l10n.Localization;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.util.PasswordStrengthUtil;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class InitializeController implements ViewController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
|
||||
|
||||
private final Localization localization;
|
||||
private final PasswordStrengthUtil strengthRater;
|
||||
private IntegerProperty passwordStrength = new SimpleIntegerProperty(-1); // strengths: 0-4
|
||||
private Optional<InitializationListener> listener = Optional.empty();
|
||||
private Vault vault;
|
||||
|
||||
@Inject
|
||||
public InitializeController(Localization localization, PasswordStrengthUtil strengthRater) {
|
||||
this.localization = localization;
|
||||
this.strengthRater = strengthRater;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private SecPasswordField passwordField;
|
||||
|
||||
@FXML
|
||||
private SecPasswordField retypePasswordField;
|
||||
|
||||
@FXML
|
||||
private Button okButton;
|
||||
|
||||
@FXML
|
||||
private Label messageLabel;
|
||||
|
||||
@FXML
|
||||
private Label passwordStrengthLabel;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel0;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel1;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel2;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel3;
|
||||
|
||||
@FXML
|
||||
private Region passwordStrengthLevel4;
|
||||
|
||||
@FXML
|
||||
private GridPane root;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
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));
|
||||
passwordStrengthLevel2.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(2), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel3.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(3), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLevel4.backgroundProperty().bind(EasyBind.combine(passwordStrength, new SimpleIntegerProperty(4), strengthRater::getBackgroundWithStrengthColor));
|
||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||
}
|
||||
|
||||
private void passwordsChanged(@SuppressWarnings("unused") 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:
|
||||
okButton.setDefaultButton(false);
|
||||
okButton.setDefaultButton(true);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// OK button
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
protected void initializeVault(ActionEvent event) {
|
||||
final CharSequence passphrase = passwordField.getCharacters();
|
||||
try {
|
||||
vault.create(passphrase);
|
||||
listener.ifPresent(this::invokeListenerLater);
|
||||
} catch (FileAlreadyExistsException ex) {
|
||||
messageLabel.setText(localization.getString("initialize.messageLabel.alreadyInitialized"));
|
||||
} catch (IOException ex) {
|
||||
LOG.error("I/O Exception", ex);
|
||||
messageLabel.setText(localization.getString("initialize.messageLabel.initializationFailed"));
|
||||
} finally {
|
||||
passwordField.swipe();
|
||||
retypePasswordField.swipe();
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public InitializationListener getListener() {
|
||||
return listener.orElse(null);
|
||||
}
|
||||
|
||||
public void setListener(InitializationListener listener) {
|
||||
this.listener = Optional.ofNullable(listener);
|
||||
}
|
||||
|
||||
/* callback */
|
||||
|
||||
private void invokeListenerLater(InitializationListener listener) {
|
||||
Platform.runLater(() -> {
|
||||
listener.didInitialize();
|
||||
});
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface InitializationListener {
|
||||
void didInitialize();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,554 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.binding.ObjectExpression;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Parent;
|
||||
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.Vault;
|
||||
import org.cryptomator.ui.model.VaultFactory;
|
||||
import org.cryptomator.ui.model.VaultList;
|
||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategy;
|
||||
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;
|
||||
|
||||
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);
|
||||
private static final String ACTIVE_WINDOW_STYLE_CLASS = "active-window";
|
||||
private static final String INACTIVE_WINDOW_STYLE_CLASS = "inactive-window";
|
||||
|
||||
private final Stage mainWindow;
|
||||
private final ExitUtil exitUtil;
|
||||
private final Localization localization;
|
||||
private final ExecutorService executorService;
|
||||
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
|
||||
private final VaultFactory vaultFactoy;
|
||||
private final ViewControllerLoader viewControllerLoader;
|
||||
private final ObjectProperty<ViewController> activeController = new SimpleObjectProperty<>();
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final BooleanBinding areAllVaultsLocked;
|
||||
private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
|
||||
private final ObjectExpression<Vault.State> selectedVaultState = ObjectExpression.objectExpression(EasyBind.select(selectedVault).selectObject(Vault::stateProperty));
|
||||
private final BooleanExpression isSelectedVaultValid = BooleanExpression.booleanExpression(EasyBind.monadic(selectedVault).map(Vault::isValidVaultDirectory).orElse(false));
|
||||
private final BooleanExpression canEditSelectedVault = selectedVaultState.isEqualTo(Vault.State.LOCKED);
|
||||
private final MonadicBinding<UpgradeStrategy> upgradeStrategyForSelectedVault;
|
||||
private final BooleanBinding isShowingSettings;
|
||||
private final Map<Vault, UnlockedController> unlockedVaults = new HashMap<>();
|
||||
|
||||
private Subscription subs = Subscription.EMPTY;
|
||||
|
||||
@Inject
|
||||
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.launchEventQueue = launchEventQueue;
|
||||
this.exitUtil = exitUtil;
|
||||
this.localization = localization;
|
||||
this.vaultFactoy = vaultFactoy;
|
||||
this.viewControllerLoader = viewControllerLoader;
|
||||
this.vaults = vaults;
|
||||
|
||||
// derived bindings:
|
||||
this.isShowingSettings = Bindings.equal(SettingsController.class, EasyBind.monadic(activeController).map(ViewController::getClass));
|
||||
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();
|
||||
|
||||
try {
|
||||
Desktop.getDesktop().setPreferencesHandler(e -> {
|
||||
Platform.runLater(this::toggleShowSettings);
|
||||
});
|
||||
} catch (UnsupportedOperationException e) {
|
||||
LOG.info("Unable to setPreferencesHandler, probably not supported on this OS.");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private ContextMenu vaultListCellContextMenu;
|
||||
|
||||
@FXML
|
||||
private MenuItem changePasswordMenuItem;
|
||||
|
||||
@FXML
|
||||
private ContextMenu addVaultContextMenu;
|
||||
|
||||
@FXML
|
||||
private HBox root;
|
||||
|
||||
@FXML
|
||||
private ListView<Vault> vaultList;
|
||||
|
||||
@FXML
|
||||
private ToggleButton addVaultButton;
|
||||
|
||||
@FXML
|
||||
private Button removeVaultButton;
|
||||
|
||||
@FXML
|
||||
private ToggleButton settingsButton;
|
||||
|
||||
@FXML
|
||||
private Pane contentPane;
|
||||
|
||||
@FXML
|
||||
private Pane emptyListInstructions;
|
||||
|
||||
@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());
|
||||
emptyListInstructions.visibleProperty().bind(Bindings.isEmpty(vaults));
|
||||
changePasswordMenuItem.visibleProperty().bind(isSelectedVaultValid.and(Bindings.isNull(upgradeStrategyForSelectedVault)));
|
||||
|
||||
subs = subs.and(EasyBind.subscribe(selectedVault, this::selectedVaultDidChange));
|
||||
subs = subs.and(EasyBind.subscribe(activeController, this::activeControllerDidChange));
|
||||
subs = subs.and(EasyBind.subscribe(isShowingSettings, settingsButton::setSelected));
|
||||
subs = subs.and(EasyBind.subscribe(addVaultContextMenu.showingProperty(), addVaultButton::setSelected));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parent getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
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");
|
||||
loadFont("/css/fontawesome-webfont.ttf");
|
||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
||||
subs = subs.and(EasyBind.includeWhen(mainWindow.getScene().getRoot().getStyleClass(), ACTIVE_WINDOW_STYLE_CLASS, mainWindow.focusedProperty()));
|
||||
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_32.png")));
|
||||
Application.setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString());
|
||||
}
|
||||
exitUtil.initExitHandler(() -> Platform.runLater(this::gracefulShutdown));
|
||||
listenToFileOpenRequests(stage);
|
||||
}
|
||||
|
||||
private void gracefulShutdown() {
|
||||
vaults.filtered(Vault.NOT_LOCKED).forEach(Vault::prepareForShutdown);
|
||||
if (!vaults.filtered(Vault.NOT_LOCKED).isEmpty()) {
|
||||
mainWindow.show(); // to keep the application open
|
||||
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 {
|
||||
if (!vaults.filtered(Vault.NOT_LOCKED).isEmpty()) {
|
||||
showUnlockedView(vaults.get(0), false); //if there are still unlocked vaults, show one of them
|
||||
} else {
|
||||
showUnlockView(UnlockController.State.UNLOCKING); //otherwise show any vault
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Platform.runLater(Platform::exit);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFont(String resourcePath) {
|
||||
try (InputStream in = getClass().getResourceAsStream(resourcePath)) {
|
||||
Font.loadFont(in, 12.0);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Error loading font from path: " + resourcePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void listenToFileOpenRequests(Stage stage) {
|
||||
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;
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// UI Events
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
private void didClickAddVault() {
|
||||
if (addVaultContextMenu.isShowing()) {
|
||||
addVaultContextMenu.hide();
|
||||
} else {
|
||||
addVaultContextMenu.show(addVaultButton, Side.BOTTOM, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickCreateNewVault() {
|
||||
final FileChooser fileChooser = new FileChooser();
|
||||
final File file = fileChooser.showSaveDialog(mainWindow);
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final Path vaultDir = file.toPath();
|
||||
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);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Unable to create vault", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNotHidden(Path file) {
|
||||
return !file.getFileName().toString().startsWith(".");
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickAddExistingVaults() {
|
||||
final FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator"));
|
||||
final List<File> files = fileChooser.showOpenMultipleDialog(mainWindow);
|
||||
if (files != null) {
|
||||
for (final File file : files) {
|
||||
addVault(file.toPath(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.isReadable(path)) {
|
||||
vaultPath = path.getParent();
|
||||
} else {
|
||||
LOG.warn("Ignoring attempt to add vault with invalid path: {}", path);
|
||||
return;
|
||||
}
|
||||
|
||||
final Vault vault = vaults.stream().filter(v -> v.getPath().equals(vaultPath)).findAny().orElseGet(() -> {
|
||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||
vaultSettings.path().set(vaultPath);
|
||||
return vaultFactoy.get(vaultSettings);
|
||||
});
|
||||
|
||||
if (!vaults.contains(vault)) {
|
||||
vaults.add(vault);
|
||||
}
|
||||
if (select) {
|
||||
vaultList.getSelectionModel().select(vault);
|
||||
activeController.get().focus();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickRemoveSelectedEntry() {
|
||||
Alert confirmDialog = DialogBuilderUtil.buildConfirmationDialog( //
|
||||
localization.getString("main.directoryList.remove.confirmation.title"), //
|
||||
localization.getString("main.directoryList.remove.confirmation.header"), //
|
||||
localization.getString("main.directoryList.remove.confirmation.content"), //
|
||||
SystemUtils.IS_OS_MAC_OSX ? ButtonType.CANCEL : ButtonType.OK);
|
||||
|
||||
Optional<ButtonType> choice = confirmDialog.showAndWait();
|
||||
if (ButtonType.OK.equals(choice.get())) {
|
||||
vaults.remove(selectedVault.get());
|
||||
if (vaults.isEmpty()) {
|
||||
activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
|
||||
} else {
|
||||
activeController.get().focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickChangePassword() {
|
||||
showChangePasswordView();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickShowSettings() {
|
||||
toggleShowSettings();
|
||||
}
|
||||
|
||||
private void toggleShowSettings() {
|
||||
if (isShowingSettings.get()) {
|
||||
showWelcomeView();
|
||||
} else {
|
||||
showPreferencesView();
|
||||
}
|
||||
vaultList.getSelectionModel().clearSelection();
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Binding Listeners
|
||||
// ****************************************
|
||||
|
||||
private void activeControllerDidChange(ViewController newValue) {
|
||||
final Parent root = newValue.getRoot();
|
||||
contentPane.getChildren().clear();
|
||||
contentPane.getChildren().add(root);
|
||||
}
|
||||
|
||||
private void selectedVaultDidChange(Vault newValue) {
|
||||
if (newValue == null) {
|
||||
return;
|
||||
}
|
||||
if (newValue.getState() != Vault.State.LOCKED) {
|
||||
this.showUnlockedView(newValue, false);
|
||||
} else if (!newValue.doesVaultDirectoryExist()) {
|
||||
this.showNotFoundView();
|
||||
} else if (newValue.isValidVaultDirectory() && upgradeStrategyForSelectedVault.isPresent()) {
|
||||
this.showUpgradeView();
|
||||
} else if (newValue.isValidVaultDirectory()) {
|
||||
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
|
||||
// ****************************************
|
||||
|
||||
public Binding<String> windowTitle() {
|
||||
return EasyBind.monadic(selectedVault).flatMap(Vault::name).orElse(localization.getString("app.name"));
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Subcontroller for right panel
|
||||
// ****************************************
|
||||
|
||||
private void showWelcomeView() {
|
||||
activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
|
||||
}
|
||||
|
||||
private void showPreferencesView() {
|
||||
activeController.set(viewControllerLoader.load("/fxml/settings.fxml"));
|
||||
}
|
||||
|
||||
private void showNotFoundView() {
|
||||
activeController.set(viewControllerLoader.load("/fxml/notfound.fxml"));
|
||||
}
|
||||
|
||||
private void showInitializeView() {
|
||||
final InitializeController ctrl = viewControllerLoader.load("/fxml/initialize.fxml");
|
||||
ctrl.setVault(selectedVault.get());
|
||||
ctrl.setListener(this::didInitialize);
|
||||
activeController.set(ctrl);
|
||||
}
|
||||
|
||||
public void didInitialize() {
|
||||
showUnlockView(UnlockController.State.INITIALIZED);
|
||||
activeController.get().focus();
|
||||
}
|
||||
|
||||
private void showUpgradeView() {
|
||||
final UpgradeController ctrl = viewControllerLoader.load("/fxml/upgrade.fxml");
|
||||
ctrl.setVault(selectedVault.get());
|
||||
ctrl.setListener(this::didUpgrade);
|
||||
activeController.set(ctrl);
|
||||
}
|
||||
|
||||
public void didUpgrade() {
|
||||
showUnlockView(UnlockController.State.UPGRADED);
|
||||
activeController.get().focus();
|
||||
}
|
||||
|
||||
private void showUnlockView(UnlockController.State state) {
|
||||
final UnlockController ctrl = viewControllerLoader.load("/fxml/unlock.fxml");
|
||||
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, vault.getVaultSettings().revealAfterMount().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
if (ctrl.getVault().getId() == selectedVault.get().getId()) {
|
||||
showUnlockView(UnlockController.State.UNLOCKING);
|
||||
}
|
||||
activeController.get().focus();
|
||||
}
|
||||
|
||||
private void showChangePasswordView() {
|
||||
final ChangePasswordController ctrl = viewControllerLoader.load("/fxml/change_password.fxml");
|
||||
ctrl.setVault(selectedVault.get());
|
||||
ctrl.setListener(this::didChangePassword);
|
||||
activeController.set(ctrl);
|
||||
Platform.runLater(ctrl::focus);
|
||||
}
|
||||
|
||||
public void didChangePassword() {
|
||||
showUnlockView(UnlockController.State.PASSWORD_CHANGED);
|
||||
activeController.get().focus();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the accompanying LICENSE file.
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.cryptomator.common.FxApplicationScoped;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class NotFoundController implements ViewController {
|
||||
|
||||
@Inject
|
||||
public NotFoundController() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@FXML
|
||||
VBox root;
|
||||
|
||||
@Override
|
||||
public Parent getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 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;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
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;
|
||||
|
||||
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');
|
||||
|
||||
private final Localization localization;
|
||||
private final Settings settings;
|
||||
private final Optional<String> applicationVersion;
|
||||
|
||||
@Inject
|
||||
public SettingsController(Localization localization, Settings settings, @Named("applicationVersion") Optional<String> applicationVersion) {
|
||||
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;
|
||||
|
||||
@FXML
|
||||
private Button changePortButton;
|
||||
|
||||
@FXML
|
||||
private Label versionLabel;
|
||||
|
||||
@FXML
|
||||
private Label prefGvfsSchemeLabel;
|
||||
|
||||
@FXML
|
||||
private ChoiceBox<String> prefGvfsScheme;
|
||||
|
||||
@FXML
|
||||
private ChoiceBox<VolumeImpl> volume;
|
||||
|
||||
@FXML
|
||||
private CheckBox debugModeCheckbox;
|
||||
|
||||
@FXML
|
||||
private VBox root;
|
||||
|
||||
@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());
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parent getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void changePort() {
|
||||
assert isPortValid() : "Button must be disabled, if port is invalid.";
|
||||
try {
|
||||
int port = Integer.parseInt(portField.getText());
|
||||
settings.port().set(port);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalStateException("Button must be disabled, if port is invalid.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPortValid() {
|
||||
try {
|
||||
int port = Integer.parseInt(portField.getText());
|
||||
return port == 0 // choose port automatically
|
||||
|| port >= Settings.MIN_PORT && port <= Settings.MAX_PORT; // port within range
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void filterNumericKeyEvents(KeyEvent t) {
|
||||
if (!Strings.isNullOrEmpty(t.getCharacter()) && !DIGITS_MATCHER.matchesAllOf(t.getCharacter())) {
|
||||
t.consume();
|
||||
}
|
||||
}
|
||||
|
||||
private void setVisibilityGvfsElements(@SuppressWarnings("unused") Observable obs, @SuppressWarnings("unused")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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,568 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* 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 com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Strings;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.StringConverter;
|
||||
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 {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);
|
||||
private static final CharMatcher ALPHA_NUMERIC_MATCHER = CharMatcher.inRange('a', 'z') //
|
||||
.or(CharMatcher.inRange('A', 'Z')) //
|
||||
.or(CharMatcher.inRange('0', '9')) //
|
||||
.or(CharMatcher.is('_')) //
|
||||
.precomputed();
|
||||
|
||||
private final Application app;
|
||||
private final Stage mainWindow;
|
||||
private final Localization localization;
|
||||
private final WindowsDriveLetters driveLetters;
|
||||
private final ChangeListener<Path> 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;
|
||||
private BooleanProperty unlocking = new SimpleBooleanProperty();
|
||||
|
||||
@Inject
|
||||
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.driveLetters = driveLetters;
|
||||
this.keychainAccess = keychainAccess;
|
||||
this.settings = settings;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private SecPasswordField passwordField;
|
||||
|
||||
@FXML
|
||||
private Button advancedOptionsButton;
|
||||
|
||||
@FXML
|
||||
private Button unlockButton;
|
||||
|
||||
@FXML
|
||||
private Text messageText;
|
||||
|
||||
@FXML
|
||||
private CheckBox savePassword;
|
||||
|
||||
@FXML
|
||||
private TextField mountName;
|
||||
|
||||
@FXML
|
||||
private CheckBox useCustomMountFlags;
|
||||
|
||||
@FXML
|
||||
private TextField mountFlags;
|
||||
|
||||
@FXML
|
||||
private CheckBox revealAfterMount;
|
||||
|
||||
@FXML
|
||||
private CheckBox useCustomWinDriveLetter;
|
||||
|
||||
@FXML
|
||||
private ChoiceBox<Path> winDriveLetter;
|
||||
|
||||
@FXML
|
||||
private CheckBox useCustomMountPoint;
|
||||
|
||||
@FXML
|
||||
private HBox customMountPoint;
|
||||
|
||||
@FXML
|
||||
private Label customMountPointLabel;
|
||||
|
||||
@FXML
|
||||
private Hyperlink downloadsPageLink;
|
||||
|
||||
@FXML
|
||||
private VBox advancedOptions;
|
||||
|
||||
@FXML
|
||||
private VBox root;
|
||||
|
||||
@FXML
|
||||
private CheckBox unlockAfterStartup;
|
||||
|
||||
@FXML
|
||||
private CheckBox useReadOnlyMode;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
|
||||
advancedOptions.disableProperty().bind(unlocking);
|
||||
unlockButton.disableProperty().bind(unlocking.or(passwordField.textProperty().isEmpty()));
|
||||
mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
|
||||
mountName.textProperty().addListener(this::mountNameDidChange);
|
||||
useReadOnlyMode.selectedProperty().addListener(this::useReadOnlyDidChange);
|
||||
useCustomMountFlags.selectedProperty().addListener(this::useCustomMountFlagsDidChange);
|
||||
mountFlags.disableProperty().bind(useCustomMountFlags.selectedProperty().not());
|
||||
mountFlags.textProperty().addListener(this::mountFlagsDidChange);
|
||||
savePassword.setDisable(!keychainAccess.isPresent());
|
||||
unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
|
||||
downloadsPageLink.visibleProperty().bind(downloadsPageLink.managedProperty());
|
||||
|
||||
customMountPoint.visibleProperty().bind(useCustomMountPoint.selectedProperty());
|
||||
customMountPoint.managedProperty().bind(useCustomMountPoint.selectedProperty());
|
||||
winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
|
||||
winDriveLetter.disableProperty().bind(useCustomWinDriveLetter.selectedProperty().not());
|
||||
|
||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
||||
useCustomWinDriveLetter.setVisible(false);
|
||||
useCustomWinDriveLetter.setManaged(false);
|
||||
winDriveLetter.setVisible(false);
|
||||
winDriveLetter.setManaged(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parent getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focus() {
|
||||
passwordField.requestFocus();
|
||||
}
|
||||
|
||||
void setVault(Vault vault, State state) {
|
||||
vaultSubs.unsubscribe();
|
||||
vaultSubs = Subscription.EMPTY;
|
||||
|
||||
// trigger "default" change to refresh key bindings:
|
||||
unlockButton.setDefaultButton(false);
|
||||
unlockButton.setDefaultButton(true);
|
||||
if (Objects.equals(this.vault, Objects.requireNonNull(vault))) {
|
||||
return;
|
||||
}
|
||||
assert vault != null;
|
||||
this.vault = vault;
|
||||
advancedOptions.setVisible(false);
|
||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
||||
unlockButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
|
||||
state.successMessage().map(localization::getString).ifPresent(messageText::setText);
|
||||
downloadsPageLink.setManaged(false);
|
||||
mountName.setText(vault.getMountName());
|
||||
useCustomMountFlags.setSelected(vault.isHavingCustomMountFlags());
|
||||
mountFlags.setText(vault.getMountFlags());
|
||||
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.setPassword(storedPw);
|
||||
passwordField.selectRange(storedPw.length, storedPw.length);
|
||||
Arrays.fill(storedPw, ' ');
|
||||
}
|
||||
}
|
||||
VaultSettings vaultSettings = vault.getVaultSettings();
|
||||
unlockAfterStartup.setSelected(savePassword.isSelected() && vaultSettings.unlockAfterStartup().get());
|
||||
revealAfterMount.setSelected(vaultSettings.revealAfterMount().get());
|
||||
useReadOnlyMode.setSelected(vaultSettings.usesReadOnlyMode().get());
|
||||
|
||||
// WEBDAV-dependent controls:
|
||||
if (VolumeImpl.WEBDAV.equals(settings.preferredVolumeImpl().get())) {
|
||||
useCustomMountPoint.setVisible(false);
|
||||
useCustomMountPoint.setManaged(false);
|
||||
useCustomMountFlags.setVisible(false);
|
||||
useCustomMountFlags.setManaged(false);
|
||||
mountFlags.setVisible(false);
|
||||
mountFlags.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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// OS-dependent controls:
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
winDriveLetter.valueProperty().removeListener(driveLetterChangeListener);
|
||||
winDriveLetter.getItems().clear();
|
||||
winDriveLetter.getItems().add(null);
|
||||
winDriveLetter.getItems().addAll(driveLetters.getAvailableDriveLetters());
|
||||
winDriveLetter.getItems().sort(new WinDriveLetterComparator());
|
||||
winDriveLetter.valueProperty().addListener(driveLetterChangeListener);
|
||||
chooseSelectedDriveLetter();
|
||||
|
||||
winDriveLetter.visibleProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||
winDriveLetter.managedProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||
useCustomWinDriveLetter.setSelected(!vaultSettings.usesIndividualMountPath().get());
|
||||
useCustomWinDriveLetter.visibleProperty().bind(useCustomMountPoint.selectedProperty().not());
|
||||
useCustomWinDriveLetter.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();
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Downloads link
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
public void didClickDownloadsLink() {
|
||||
app.getHostServices().showDocument("https://cryptomator.org/downloads/#allVersions");
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Advanced options button
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
private void didClickAdvancedOptionsButton() {
|
||||
messageText.setText(null);
|
||||
downloadsPageLink.setManaged(false);
|
||||
advancedOptions.setVisible(!advancedOptions.isVisible());
|
||||
if (advancedOptions.isVisible()) {
|
||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.hide"));
|
||||
} else {
|
||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
||||
}
|
||||
}
|
||||
|
||||
private void filterAlphanumericKeyEvents(KeyEvent t) {
|
||||
if (!Strings.isNullOrEmpty(t.getCharacter()) && !ALPHA_NUMERIC_MATCHER.matchesAllOf(t.getCharacter())) {
|
||||
t.consume();
|
||||
}
|
||||
}
|
||||
|
||||
private void mountNameDidChange(@SuppressWarnings("unused") ObservableValue<? extends String> property, @SuppressWarnings("unused") String oldValue, String newValue) {
|
||||
// newValue is guaranteed to be a-z0-9_, see #filterAlphanumericKeyEvents
|
||||
if (newValue.isEmpty()) {
|
||||
mountName.setText(vault.getMountName());
|
||||
} else {
|
||||
vault.setMountName(newValue);
|
||||
}
|
||||
if (!useCustomMountFlags.isSelected()) {
|
||||
mountFlags.setText(vault.getMountFlags()); // update default flags
|
||||
}
|
||||
}
|
||||
|
||||
private void useReadOnlyDidChange(@SuppressWarnings("unused") ObservableValue<? extends Boolean> property, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) {
|
||||
vault.getVaultSettings().usesReadOnlyMode().setValue(newValue);
|
||||
if (!useCustomMountFlags.isSelected()) {
|
||||
mountFlags.setText(vault.getMountFlags()); // update default flags
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void useCustomMountFlagsDidChange(@SuppressWarnings("unused") ObservableValue<? extends Boolean> property, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) {
|
||||
if (!newValue) {
|
||||
vault.setMountFlags(VaultSettings.DEFAULT_MOUNT_FLAGS);
|
||||
mountFlags.setText(vault.getMountFlags());
|
||||
}
|
||||
}
|
||||
|
||||
private void mountFlagsDidChange(@SuppressWarnings("unused") ObservableValue<? extends String> property, @SuppressWarnings("unused") String oldValue, String newValue) {
|
||||
if (useCustomMountFlags.isSelected()) {
|
||||
vault.setMountFlags(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void didClickChooseCustomMountPoint() {
|
||||
DirectoryChooser dirChooser = new DirectoryChooser();
|
||||
File file = dirChooser.showDialog(mainWindow);
|
||||
if (file != null) {
|
||||
customMountPointLabel.setText(displayablePath(file.toString()));
|
||||
vault.setCustomMountPath(file.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void didClickCustomWinDriveLetterCheckbox() {
|
||||
if (!useCustomWinDriveLetter.isSelected()) {
|
||||
winDriveLetter.setValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void didClickCustomMountPointCheckbox() {
|
||||
useCustomWinDriveLetter.setSelected(vault.getWinDriveLetter() != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts 'C' to "C:" to translate between model and GUI.
|
||||
*/
|
||||
private class WinDriveLetterLabelConverter extends StringConverter<Path> {
|
||||
|
||||
@Override
|
||||
public String toString(Path root) {
|
||||
if (root == null) {
|
||||
return localization.getString("unlock.choicebox.winDriveLetter.auto");
|
||||
} else if (root.endsWith("occupied")) {
|
||||
return root.getRoot().toString().substring(0, 1) + " (" + localization.getString("unlock.choicebox.winDriveLetter.occupied") + ")";
|
||||
} else {
|
||||
return root.toString().substring(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path fromString(String string) {
|
||||
if (localization.getString("unlock.choicebox.winDriveLetter.auto").equals(string)) {
|
||||
return null;
|
||||
} else {
|
||||
return Path.of(string);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Natural sorting of ASCII letters, but <code>null</code> always on first, as this is "auto-assign".
|
||||
*/
|
||||
private static class WinDriveLetterComparator implements Comparator<Path> {
|
||||
|
||||
@Override
|
||||
public int compare(Path c1, Path c2) {
|
||||
if (c1 == null) {
|
||||
return -1;
|
||||
} else if (c2 == null) {
|
||||
return 1;
|
||||
} else {
|
||||
return c1.compareTo(c2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void winDriveLetterDidChange(@SuppressWarnings("unused") ObservableValue<? extends Path> property, @SuppressWarnings("unused") Path oldValue, Path newValue) {
|
||||
vault.setWinDriveLetter(newValue);
|
||||
}
|
||||
|
||||
private void chooseSelectedDriveLetter() {
|
||||
assert SystemUtils.IS_OS_WINDOWS;
|
||||
// if the vault prefers a drive letter, that is currently occupied, this is our last chance to reset this:
|
||||
if (vault.getWinDriveLetter() != null) {
|
||||
final Path pickedRoot = Path.of(vault.getWinDriveLetter() + ":\\");
|
||||
if (driveLetters.getOccupiedDriveLetters().contains(pickedRoot)) {
|
||||
Path alteredPath = pickedRoot.resolve("occupied");
|
||||
this.winDriveLetter.getItems().add(alteredPath);
|
||||
this.winDriveLetter.getSelectionModel().select(alteredPath);
|
||||
} else {
|
||||
this.winDriveLetter.getSelectionModel().select(pickedRoot);
|
||||
}
|
||||
} else {
|
||||
// first option is known to be 'auto-assign' due to #WinDriveLetterComparator.
|
||||
this.winDriveLetter.getSelectionModel().selectFirst();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Save password checkbox
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
private void didClickSavePasswordCheckbox() {
|
||||
if (!savePassword.isSelected() && hasStoredPassword()) {
|
||||
Alert confirmDialog = DialogBuilderUtil.buildConfirmationDialog( //
|
||||
localization.getString("unlock.savePassword.delete.confirmation.title"), //
|
||||
localization.getString("unlock.savePassword.delete.confirmation.header"), //
|
||||
localization.getString("unlock.savePassword.delete.confirmation.content"), //
|
||||
SystemUtils.IS_OS_MAC_OSX ? ButtonType.CANCEL : ButtonType.OK);
|
||||
|
||||
Optional<ButtonType> choice = confirmDialog.showAndWait();
|
||||
if (ButtonType.OK.equals(choice.get())) {
|
||||
keychainAccess.get().deletePassphrase(vault.getId());
|
||||
} else if (ButtonType.CANCEL.equals(choice.get())) {
|
||||
savePassword.setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasStoredPassword() {
|
||||
char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
|
||||
boolean hasPw = (storedPw != null);
|
||||
if (storedPw != null) {
|
||||
Arrays.fill(storedPw, ' ');
|
||||
}
|
||||
return hasPw;
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Unlock button
|
||||
// ****************************************
|
||||
|
||||
@FXML
|
||||
private void didClickUnlockButton() {
|
||||
unlocking.set(true);
|
||||
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
|
||||
unlockButton.setContentDisplay(ContentDisplay.LEFT);
|
||||
|
||||
CharSequence password = passwordField.getCharacters();
|
||||
Tasks.create(() -> {
|
||||
vault.unlock(password);
|
||||
if (keychainAccess.isPresent() && savePassword.isSelected()) {
|
||||
keychainAccess.get().storePassphrase(vault.getId(), password);
|
||||
}
|
||||
}).onSuccess(() -> {
|
||||
messageText.setText(null);
|
||||
downloadsPageLink.setManaged(false);
|
||||
listener.ifPresent(lstnr -> lstnr.didUnlock(vault));
|
||||
passwordField.swipe();
|
||||
}).onError(InvalidPassphraseException.class, e -> {
|
||||
messageText.setText(localization.getString("unlock.errorMessage.wrongPassword"));
|
||||
downloadsPageLink.setManaged(false);
|
||||
passwordField.selectAll();
|
||||
passwordField.requestFocus();
|
||||
}).onError(UnsupportedVaultFormatException.class, e -> {
|
||||
if (e.isVaultOlderThanSoftware()) {
|
||||
// whitespace after localized text used as separator between text and hyperlink
|
||||
messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
|
||||
downloadsPageLink.setManaged(true);
|
||||
} else if (e.isSoftwareOlderThanVault()) {
|
||||
messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
|
||||
downloadsPageLink.setManaged(true);
|
||||
} else if (e.getDetectedVersion() == Integer.MAX_VALUE) {
|
||||
messageText.setText(localization.getString("unlock.errorMessage.unauthenticVersionMac"));
|
||||
}
|
||||
}).onError(NotDirectoryException.class, e -> {
|
||||
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
|
||||
advancedOptions.setVisible(true);
|
||||
messageText.setText(null);
|
||||
downloadsPageLink.setManaged(false);
|
||||
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);
|
||||
downloadsPageLink.setManaged(false);
|
||||
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"));
|
||||
downloadsPageLink.setManaged(false);
|
||||
}).andFinally(() -> {
|
||||
unlocking.set(false);
|
||||
unlockButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
|
||||
}).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 */
|
||||
|
||||
public void setListener(UnlockListener listener) {
|
||||
this.listener = Optional.ofNullable(listener);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user