mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 07:54:19 +00:00
Compare commits
521
Commits
1.4.14
...
1.5.0-beta1
| 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 | ||
|
|
50b2b0dc57 | ||
|
|
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 | ||
|
|
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 | ||
|
|
2a69740559 | ||
|
|
dd0e548353 | ||
|
|
0eb19a287f | ||
|
|
14de8ffd59 | ||
|
|
ac6249f541 | ||
|
|
5af2a392ea | ||
|
|
edfdca6e5f | ||
|
|
c2ed9958c7 | ||
|
|
fddae7d0e1 | ||
|
|
fa729e220c | ||
|
|
c917fb6a57 | ||
|
|
cf2e026f75 | ||
|
|
c340544152 | ||
|
|
00c7cae923 | ||
|
|
80cff6912b | ||
|
|
b76e1d4167 | ||
|
|
f7759d2547 | ||
|
|
d51e3979ff | ||
|
|
01275fce9f | ||
|
|
261b18f4eb | ||
|
|
fdc20aa84f | ||
|
|
a3382c2d5b | ||
|
|
52a9a2f018 | ||
|
|
cfea1c16bc | ||
|
|
180c07cf61 | ||
|
|
f880db4902 |
@@ -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
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# These are supported funding model platforms
|
# 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
|
patreon: # Replace with a single Patreon username
|
||||||
open_collective: # Replace with a single Open Collective username
|
open_collective: # Replace with a single Open Collective username
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ daysUntilClose: 30
|
|||||||
exemptLabels:
|
exemptLabels:
|
||||||
- type:security-issue # never close automatically
|
- type:security-issue # never close automatically
|
||||||
- state:awaiting-response # handled by different bot
|
- state:awaiting-response # handled by different bot
|
||||||
|
- state:blocked
|
||||||
# Set to true to ignore issues in a milestone (defaults to false)
|
# Set to true to ignore issues in a milestone (defaults to false)
|
||||||
exemptMilestones: true
|
exemptMilestones: true
|
||||||
# Label to use when marking an issue as stale
|
# Label to use when marking an issue as stale
|
||||||
|
|||||||
Generated
+19
-11
@@ -7,26 +7,34 @@
|
|||||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
<outputRelativeToContentRoot value="true" />
|
<outputRelativeToContentRoot value="true" />
|
||||||
<processorPath useClasspath="false">
|
<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-compiler/2.25.2/dagger-compiler-2.25.2.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/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$/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/dagger/dagger-producers/2.25.2/dagger-producers-2.25.2.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/25.0-jre/guava-25.0-jre.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/27.1-jre/guava-27.1-jre.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/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/errorprone/error_prone_annotations/2.1.3/error_prone_annotations-2.1.3.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$/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$/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.22.1/dagger-spi-2.22.1.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/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/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
<entry name="$MAVEN_REPOSITORY$/com/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$/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>
|
</processorPath>
|
||||||
<module name="keychain" />
|
|
||||||
<module name="launcher" />
|
|
||||||
<module name="commons" />
|
<module name="commons" />
|
||||||
|
<module name="keychain" />
|
||||||
<module name="ui" />
|
<module name="ui" />
|
||||||
|
<module name="launcher" />
|
||||||
</profile>
|
</profile>
|
||||||
</annotationProcessing>
|
</annotationProcessing>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
+1
-1
@@ -2,8 +2,8 @@
|
|||||||
<configuration default="false" name="Cryptomator macOS" type="Application" factoryName="Application">
|
<configuration default="false" name="Cryptomator macOS" type="Application" factoryName="Application">
|
||||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
<module name="launcher" />
|
<module name="launcher" />
|
||||||
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/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="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">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+11
-10
@@ -1,4 +1,4 @@
|
|||||||
dist: xenial
|
dist: bionic
|
||||||
language: java
|
language: java
|
||||||
sudo: false
|
sudo: false
|
||||||
jdk:
|
jdk:
|
||||||
@@ -17,24 +17,25 @@ addons:
|
|||||||
- haveged
|
- haveged
|
||||||
install:
|
install:
|
||||||
- curl -o $HOME/.m2/settings.xml https://gist.githubusercontent.com/cryptobot/cf5fbd909c4782aaeeeb7c7f4a1a43da/raw/e60ee486e34ee0c79f89f947abe2c83b4290c6bb/settings.xml
|
- 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:
|
script:
|
||||||
- mvn --update-snapshots -fmain/pom.xml clean test verify -Pcoverage
|
- mvn --update-snapshots -fmain/pom.xml clean test verify -Pcoverage
|
||||||
after_success:
|
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/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/keychain/target/site/jacoco/jacoco.xml --partial
|
||||||
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
|
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/ui/target/site/jacoco/jacoco.xml --partial
|
||||||
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
|
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/launcher/target/site/jacoco/jacoco.xml --partial
|
||||||
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
|
- $JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
|
||||||
before_deploy:
|
before_deploy:
|
||||||
- |
|
- mvn -fmain/pom.xml package -Prelease -DskipTests
|
||||||
if [[ -n "$TRAVIS_TAG" ]]; then
|
|
||||||
mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=$TRAVIS_TAG
|
|
||||||
elif [[ $TRAVIS_BRANCH == "develop" ]] && [[ $TRAVIS_PULL_REQUEST == "false" ]]; then
|
|
||||||
mvn -fmain/pom.xml org.codehaus.mojo:versions-maven-plugin:set -DnewVersion=SNAPSHOT-$(echo $TRAVIS_COMMIT | head -c7)
|
|
||||||
fi
|
|
||||||
- mvn -fmain/pom.xml clean package -Prelease -DskipTests
|
|
||||||
- export TODAY=`date +'%Y-%m-%d'`; envsubst '$TRAVIS_TAG $TODAY' < .travis-deploy-release.tmpl.json > .travis-deploy-release.json
|
- export TODAY=`date +'%Y-%m-%d'`; envsubst '$TRAVIS_TAG $TODAY' < .travis-deploy-release.tmpl.json > .travis-deploy-release.json
|
||||||
deploy:
|
deploy:
|
||||||
- provider: bintray # SNAPSHOTS
|
- provider: bintray # SNAPSHOTS
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
[](https://snyk.io/test/github/cryptomator/cryptomator?targetFile=main%2Fpom.xml)
|
[](https://snyk.io/test/github/cryptomator/cryptomator?targetFile=main%2Fpom.xml)
|
||||||
[](https://www.codacy.com/app/cryptomator/cryptomator?utm_source=github.com&utm_medium=referral&utm_content=cryptomator/cryptomator&utm_campaign=Badge_Grade)
|
[](https://www.codacy.com/app/cryptomator/cryptomator?utm_source=github.com&utm_medium=referral&utm_content=cryptomator/cryptomator&utm_campaign=Badge_Grade)
|
||||||
[](http://twitter.com/Cryptomator)
|
[](http://twitter.com/Cryptomator)
|
||||||
[](https://poeditor.com/join/project/bHwbvJmx0E)
|
[](https://translate.cryptomator.org/)
|
||||||
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
||||||
[](https://community.cryptomator.org)
|
[](https://community.cryptomator.org)
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,13 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>target/</directory>
|
||||||
|
<includes>
|
||||||
|
<include>ffi-version.txt</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>libs</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target/</directory>
|
<directory>target/</directory>
|
||||||
<includes>
|
<includes>
|
||||||
|
|||||||
@@ -14,6 +14,13 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>target/</directory>
|
||||||
|
<includes>
|
||||||
|
<include>ffi-version.txt</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>libs</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target/</directory>
|
<directory>target/</directory>
|
||||||
<includes>
|
<includes>
|
||||||
|
|||||||
@@ -14,6 +14,13 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>target/</directory>
|
||||||
|
<includes>
|
||||||
|
<include>ffi-version.txt</include>
|
||||||
|
</includes>
|
||||||
|
<outputDirectory>libs</outputDirectory>
|
||||||
|
</fileSet>
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target/</directory>
|
<directory>target/</directory>
|
||||||
<includes>
|
<includes>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.4.14</version>
|
<version>1.5.0-beta1</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>buildkit</artifactId>
|
<artifactId>buildkit</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
<directory>${project.basedir}/src/main/resources</directory>
|
<directory>${project.basedir}/src/main/resources</directory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>version.txt</include>
|
<include>version.txt</include>
|
||||||
|
<include>ffi-version.txt</include>
|
||||||
<include>launcher-mac.sh</include>
|
<include>launcher-mac.sh</include>
|
||||||
<include>launcher-linux.sh</include>
|
<include>launcher-linux.sh</include>
|
||||||
<include>launcher-win.bat</include>
|
<include>launcher-win.bat</include>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
${cryptomator.jni.version}
|
||||||
+37
-8
@@ -4,35 +4,64 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.4.14</version>
|
<version>1.5.0-beta1</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
<name>Cryptomator Commons</name>
|
<name>Cryptomator Commons</name>
|
||||||
<description>Shared utilities</description>
|
<description>Shared utilities</description>
|
||||||
|
|
||||||
<dependencies>
|
<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 -->
|
<!-- JavaFx -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
<artifactId>javafx-base</artifactId>
|
<artifactId>javafx-base</artifactId>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
<artifactId>guava</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.commons</groupId>
|
|
||||||
<artifactId>commons-lang3</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.code.gson</groupId>
|
<groupId>com.google.code.gson</groupId>
|
||||||
<artifactId>gson</artifactId>
|
<artifactId>gson</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Apache Commons -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.fxmisc.easybind</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>easybind</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- DI -->
|
<!-- DI -->
|
||||||
|
|||||||
@@ -5,22 +5,89 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common;
|
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.Named;
|
||||||
import javax.inject.Singleton;
|
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;
|
@Module(subcomponents = {VaultComponent.class})
|
||||||
import dagger.Provides;
|
public abstract class CommonsModule {
|
||||||
|
|
||||||
@Module
|
private static final int NUM_SCHEDULER_THREADS = 4;
|
||||||
public class CommonsModule {
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@Named("SemVer")
|
@Named("SemVer")
|
||||||
Comparator<String> providesSemVerComparator() {
|
static Comparator<String> providesSemVerComparator() {
|
||||||
return new SemVerComparator();
|
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
|
@Inject
|
||||||
public Environment() {
|
public Environment() {
|
||||||
|
LOG.debug("java.library.path: {}", System.getProperty("java.library.path"));
|
||||||
LOG.debug("user.language: {}", System.getProperty("user.language"));
|
LOG.debug("user.language: {}", System.getProperty("user.language"));
|
||||||
LOG.debug("user.region: {}", System.getProperty("user.region"));
|
LOG.debug("user.region: {}", System.getProperty("user.region"));
|
||||||
LOG.debug("logback.configurationFile: {}", System.getProperty("logback.configurationFile"));
|
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;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.value.ObservableValue;
|
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.FXCollections;
|
||||||
import javafx.collections.ListChangeListener;
|
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.geometry.NodeOrientation;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -22,20 +27,26 @@ public class Settings {
|
|||||||
public static final int MAX_PORT = 65535;
|
public static final int MAX_PORT = 65535;
|
||||||
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
|
public static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
|
||||||
public static final boolean DEFAULT_CHECK_FOR_UDPATES = 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_PORT = 42427;
|
||||||
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||||
public static final String DEFAULT_GVFS_SCHEME = "dav";
|
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
|
||||||
public static final boolean DEFAULT_DEBUG_MODE = false;
|
public static final boolean DEFAULT_DEBUG_MODE = false;
|
||||||
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
|
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = 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 ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
|
||||||
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
||||||
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
|
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
|
||||||
|
private final BooleanProperty startHidden = new SimpleBooleanProperty(DEFAULT_START_HIDDEN);
|
||||||
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
|
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
|
||||||
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
|
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
|
||||||
private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME);
|
private final ObjectProperty<WebDavUrlScheme> preferredGvfsScheme = new SimpleObjectProperty<>(DEFAULT_GVFS_SCHEME);
|
||||||
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
||||||
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
private 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;
|
private Consumer<Settings> saveCmd;
|
||||||
|
|
||||||
@@ -43,21 +54,24 @@ public class Settings {
|
|||||||
* Package-private constructor; use {@link SettingsProvider}.
|
* Package-private constructor; use {@link SettingsProvider}.
|
||||||
*/
|
*/
|
||||||
Settings() {
|
Settings() {
|
||||||
directories.addListener((ListChangeListener.Change<? extends VaultSettings> change) -> this.save());
|
directories.addListener(this::somethingChanged);
|
||||||
askedForUpdateCheck.addListener(this::somethingChanged);
|
askedForUpdateCheck.addListener(this::somethingChanged);
|
||||||
checkForUpdates.addListener(this::somethingChanged);
|
checkForUpdates.addListener(this::somethingChanged);
|
||||||
|
startHidden.addListener(this::somethingChanged);
|
||||||
port.addListener(this::somethingChanged);
|
port.addListener(this::somethingChanged);
|
||||||
numTrayNotifications.addListener(this::somethingChanged);
|
numTrayNotifications.addListener(this::somethingChanged);
|
||||||
preferredGvfsScheme.addListener(this::somethingChanged);
|
preferredGvfsScheme.addListener(this::somethingChanged);
|
||||||
debugMode.addListener(this::somethingChanged);
|
debugMode.addListener(this::somethingChanged);
|
||||||
preferredVolumeImpl.addListener(this::somethingChanged);
|
preferredVolumeImpl.addListener(this::somethingChanged);
|
||||||
|
theme.addListener(this::somethingChanged);
|
||||||
|
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||||
this.saveCmd = saveCmd;
|
this.saveCmd = saveCmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void somethingChanged(ObservableValue<?> observable, Object oldValue, Object newValue) {
|
private void somethingChanged(@SuppressWarnings("unused") Observable observable) {
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +94,10 @@ public class Settings {
|
|||||||
public BooleanProperty checkForUpdates() {
|
public BooleanProperty checkForUpdates() {
|
||||||
return checkForUpdates;
|
return checkForUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty startHidden() {
|
||||||
|
return startHidden;
|
||||||
|
}
|
||||||
|
|
||||||
public IntegerProperty port() {
|
public IntegerProperty port() {
|
||||||
return port;
|
return port;
|
||||||
@@ -89,7 +107,7 @@ public class Settings {
|
|||||||
return numTrayNotifications;
|
return numTrayNotifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty preferredGvfsScheme() {
|
public ObjectProperty<WebDavUrlScheme> preferredGvfsScheme() {
|
||||||
return preferredGvfsScheme;
|
return preferredGvfsScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,4 +119,11 @@ public class Settings {
|
|||||||
return preferredVolumeImpl;
|
return preferredVolumeImpl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<UiTheme> theme() {
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
|
||||||
|
return userInterfaceOrientation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-3
@@ -9,6 +9,7 @@ import com.google.gson.TypeAdapter;
|
|||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.google.gson.stream.JsonToken;
|
import com.google.gson.stream.JsonToken;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
import javafx.geometry.NodeOrientation;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -29,11 +30,14 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
writeVaultSettingsArray(out, value.getDirectories());
|
writeVaultSettingsArray(out, value.getDirectories());
|
||||||
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
|
out.name("askedForUpdateCheck").value(value.askedForUpdateCheck().get());
|
||||||
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
|
out.name("checkForUpdatesEnabled").value(value.checkForUpdates().get());
|
||||||
|
out.name("startHidden").value(value.startHidden().get());
|
||||||
out.name("port").value(value.port().get());
|
out.name("port").value(value.port().get());
|
||||||
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
out.name("numTrayNotifications").value(value.numTrayNotifications().get());
|
||||||
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get());
|
out.name("preferredGvfsScheme").value(value.preferredGvfsScheme().get().name());
|
||||||
out.name("debugMode").value(value.debugMode().get());
|
out.name("debugMode").value(value.debugMode().get());
|
||||||
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
||||||
|
out.name("theme").value(value.theme().get().name());
|
||||||
|
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +66,9 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
case "checkForUpdatesEnabled":
|
case "checkForUpdatesEnabled":
|
||||||
settings.checkForUpdates().set(in.nextBoolean());
|
settings.checkForUpdates().set(in.nextBoolean());
|
||||||
break;
|
break;
|
||||||
|
case "startHidden":
|
||||||
|
settings.startHidden().set(in.nextBoolean());
|
||||||
|
break;
|
||||||
case "port":
|
case "port":
|
||||||
settings.port().set(in.nextInt());
|
settings.port().set(in.nextInt());
|
||||||
break;
|
break;
|
||||||
@@ -69,7 +76,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
settings.numTrayNotifications().set(in.nextInt());
|
settings.numTrayNotifications().set(in.nextInt());
|
||||||
break;
|
break;
|
||||||
case "preferredGvfsScheme":
|
case "preferredGvfsScheme":
|
||||||
settings.preferredGvfsScheme().set(in.nextString());
|
settings.preferredGvfsScheme().set(parseWebDavUrlSchemePrefix(in.nextString()));
|
||||||
break;
|
break;
|
||||||
case "debugMode":
|
case "debugMode":
|
||||||
settings.debugMode().set(in.nextBoolean());
|
settings.debugMode().set(in.nextBoolean());
|
||||||
@@ -77,6 +84,12 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
case "preferredVolumeImpl":
|
case "preferredVolumeImpl":
|
||||||
settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
||||||
break;
|
break;
|
||||||
|
case "theme":
|
||||||
|
settings.theme().set(parseUiTheme(in.nextString()));
|
||||||
|
break;
|
||||||
|
case "uiOrientation":
|
||||||
|
settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
@@ -90,12 +103,40 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
|
|
||||||
private VolumeImpl parsePreferredVolumeImplName(String nioAdapterName) {
|
private VolumeImpl parsePreferredVolumeImplName(String nioAdapterName) {
|
||||||
try {
|
try {
|
||||||
return VolumeImpl.valueOf(nioAdapterName);
|
return VolumeImpl.valueOf(nioAdapterName.toUpperCase());
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
|
LOG.warn("Invalid volume type {}. Defaulting to {}.", nioAdapterName, Settings.DEFAULT_PREFERRED_VOLUME_IMPL);
|
||||||
return 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 {
|
private List<VaultSettings> readVaultSettingsArray(JsonReader in) throws IOException {
|
||||||
List<VaultSettings> result = new ArrayList<>();
|
List<VaultSettings> result = new ArrayList<>();
|
||||||
in.beginArray();
|
in.beginArray();
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -38,10 +37,11 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Singleton
|
@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 Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
|
||||||
private static final long SAVE_DELAY_MS = 1000;
|
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;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
@@ -23,6 +24,7 @@ import java.util.Base64;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
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_INDIVIDUAL_MOUNTPATH = false;
|
||||||
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
||||||
public static final String DEFAULT_MOUNT_FLAGS = "";
|
public static final String DEFAULT_MOUNT_FLAGS = "";
|
||||||
|
|
||||||
|
private static final Random RNG = new Random();
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
private final ObjectProperty<Path> path = new SimpleObjectProperty();
|
private final ObjectProperty<Path> path = new SimpleObjectProperty();
|
||||||
@@ -69,20 +73,9 @@ public class VaultSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String generateId() {
|
private static String generateId() {
|
||||||
return asBase64String(nineBytesFrom(UUID.randomUUID()));
|
byte[] randomBytes = new byte[9];
|
||||||
}
|
RNG.nextBytes(randomBytes);
|
||||||
|
return BaseEncoding.base64Url().encode(randomBytes);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String normalizeMountName(String mountName) {
|
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
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import javax.inject.Qualifier;
|
import javax.inject.Qualifier;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
+13
-6
@@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
@@ -30,6 +30,7 @@ public class DokanyVolume implements Volume {
|
|||||||
private final MountFactory mountFactory;
|
private final MountFactory mountFactory;
|
||||||
private final WindowsDriveLetters windowsDriveLetters;
|
private final WindowsDriveLetters windowsDriveLetters;
|
||||||
private Mount mount;
|
private Mount mount;
|
||||||
|
private Path mountPoint;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, WindowsDriveLetters windowsDriveLetters) {
|
public DokanyVolume(VaultSettings vaultSettings, ExecutorService executorService, WindowsDriveLetters windowsDriveLetters) {
|
||||||
@@ -45,19 +46,19 @@ public class DokanyVolume implements Volume {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException, IOException {
|
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException, IOException {
|
||||||
Path mountPath = getMountPoint();
|
this.mountPoint = determineMountPoint();
|
||||||
String mountName = vaultSettings.mountName().get();
|
String mountName = vaultSettings.mountName().get();
|
||||||
try {
|
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) {
|
} catch (MountFailedException e) {
|
||||||
if (vaultSettings.getIndividualMountPath().isPresent()) {
|
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);
|
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();
|
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
|
||||||
if (optionalCustomMountPoint.isPresent()) {
|
if (optionalCustomMountPoint.isPresent()) {
|
||||||
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
||||||
@@ -68,8 +69,9 @@ public class DokanyVolume implements Volume {
|
|||||||
} else {
|
} else {
|
||||||
//auto assign drive letter
|
//auto assign drive letter
|
||||||
if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
|
if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
|
||||||
return windowsDriveLetters.getAvailableDriveLetters().iterator().next();
|
return Path.of(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\");
|
||||||
} else {
|
} else {
|
||||||
|
//TODO: Error Handling
|
||||||
throw new VolumeException("No free drive letter available.");
|
throw new VolumeException("No free drive letter available.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,6 +101,11 @@ public class DokanyVolume implements Volume {
|
|||||||
mount.close();
|
mount.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Path> getMountPoint() {
|
||||||
|
return Optional.ofNullable(mountPoint);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isSupportedStatic() {
|
public static boolean isSupportedStatic() {
|
||||||
return MountFactory.isApplicable();
|
return MountFactory.isApplicable();
|
||||||
}
|
}
|
||||||
+7
-3
@@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.base.Splitter;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
@@ -100,8 +100,7 @@ public class FuseVolume implements Volume {
|
|||||||
try {
|
try {
|
||||||
Mounter mounter = FuseMountFactory.getMounter();
|
Mounter mounter = FuseMountFactory.getMounter();
|
||||||
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
||||||
.withFlags(splitFlags(mountFlags))
|
.withFlags(splitFlags(mountFlags)).withMountPoint(mountPoint) //
|
||||||
.withMountPoint(mountPoint) //
|
|
||||||
.build();
|
.build();
|
||||||
this.fuseMnt = mounter.mount(root, envVars);
|
this.fuseMnt = mounter.mount(root, envVars);
|
||||||
} catch (CommandFailedException e) {
|
} catch (CommandFailedException e) {
|
||||||
@@ -166,6 +165,11 @@ public class FuseVolume implements Volume {
|
|||||||
return FuseVolume.isSupportedStatic();
|
return FuseVolume.isSupportedStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Path> getMountPoint() {
|
||||||
|
return Optional.ofNullable(mountPoint);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isSupportedStatic() {
|
public static boolean isSupportedStatic() {
|
||||||
return (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) && FuseMountFactory.isFuseSupported();
|
return (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) && FuseMountFactory.isFuseSupported();
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import javax.inject.Scope;
|
import javax.inject.Scope;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
+136
-165
@@ -6,14 +6,16 @@
|
|||||||
* Contributors:
|
* Contributors:
|
||||||
* Sebastian Stenzel - initial API and implementation
|
* Sebastian Stenzel - initial API and implementation
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.Binding;
|
import javafx.beans.binding.Binding;
|
||||||
|
import javafx.beans.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.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
@@ -39,36 +41,51 @@ import java.nio.file.NotDirectoryException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
@PerVault
|
@PerVault
|
||||||
public class Vault {
|
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 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 VaultSettings vaultSettings;
|
||||||
private final Provider<Volume> volumeProvider;
|
private final Provider<Volume> volumeProvider;
|
||||||
private final Supplier<String> defaultMountFlags;
|
private final StringBinding defaultMountFlags;
|
||||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem = new AtomicReference<>();
|
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
||||||
private final ObjectProperty<State> state = new SimpleObjectProperty<State>(State.LOCKED);
|
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;
|
private volatile Volume volume;
|
||||||
|
|
||||||
public enum State {
|
|
||||||
LOCKED, PROCESSING, UNLOCKED
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject
|
@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.vaultSettings = vaultSettings;
|
||||||
this.volumeProvider = volumeProvider;
|
this.volumeProvider = volumeProvider;
|
||||||
this.defaultMountFlags = defaultMountFlags;
|
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 {
|
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()) {
|
if (vaultSettings.usesReadOnlyMode().get()) {
|
||||||
flags.add(FileSystemFlags.READONLY);
|
flags.add(FileSystemFlags.READONLY);
|
||||||
}
|
}
|
||||||
@@ -92,40 +109,16 @@ public class Vault {
|
|||||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
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 {
|
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
|
||||||
Platform.runLater(() -> state.set(State.PROCESSING));
|
if (vaultSettings.usesIndividualMountPath().get() && Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
|
||||||
try {
|
throw new NotDirectoryException("");
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
|
||||||
|
volume = volumeProvider.get();
|
||||||
|
volume.mount(fs, getEffectiveMountFlags());
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void lock(boolean forced) throws Volume.VolumeException {
|
public synchronized void lock(boolean forced) throws Volume.VolumeException {
|
||||||
Platform.runLater(() -> {
|
|
||||||
state.set(State.PROCESSING);
|
|
||||||
});
|
|
||||||
if (forced && volume.supportsForcedUnmount()) {
|
if (forced && volume.supportsForcedUnmount()) {
|
||||||
volume.unmountForced();
|
volume.unmountForced();
|
||||||
} else {
|
} else {
|
||||||
@@ -139,28 +132,6 @@ public class Vault {
|
|||||||
LOG.error("Error closing file system.", e);
|
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 {
|
public void reveal() throws Volume.VolumeException {
|
||||||
@@ -168,21 +139,96 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
// Getter/Setter
|
// Observable Properties
|
||||||
// *******************************************************************************/
|
// *******************************************************************************
|
||||||
|
|
||||||
public State getState() {
|
public ObjectProperty<VaultState> stateProperty() {
|
||||||
return state.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Predicate<Vault> hasState(State state) {
|
public VaultState getState() {
|
||||||
return vault -> {
|
return state.get();
|
||||||
return vault.getState() == state;
|
}
|
||||||
};
|
|
||||||
|
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() {
|
public Observable[] observables() {
|
||||||
@@ -197,106 +243,31 @@ public class Vault {
|
|||||||
return vaultSettings.path().getValue();
|
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() {
|
public boolean isHavingCustomMountFlags() {
|
||||||
return !Strings.isNullOrEmpty(vaultSettings.mountFlags().get());
|
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();
|
String mountFlags = vaultSettings.mountFlags().get();
|
||||||
if (Strings.isNullOrEmpty(mountFlags)) {
|
if (Strings.isNullOrEmpty(mountFlags)) {
|
||||||
return defaultMountFlags.get();
|
return getDefaultMountFlags();
|
||||||
} else {
|
} else {
|
||||||
return mountFlags;
|
return mountFlags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMountFlags(String mountFlags) {
|
public void setCustomMountFlags(String mountFlags) {
|
||||||
vaultSettings.mountFlags().set(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() {
|
public String getId() {
|
||||||
return vaultSettings.getId();
|
return vaultSettings.getId();
|
||||||
}
|
}
|
||||||
+4
-1
@@ -3,7 +3,7 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* are made available under the terms of the accompanying LICENSE file.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import dagger.BindsInstance;
|
import dagger.BindsInstance;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
@@ -22,6 +22,9 @@ public interface VaultComponent {
|
|||||||
@BindsInstance
|
@BindsInstance
|
||||||
Builder vaultSettings(VaultSettings vaultSettings);
|
Builder vaultSettings(VaultSettings vaultSettings);
|
||||||
|
|
||||||
|
@BindsInstance
|
||||||
|
Builder initialVaultState(VaultState vaultState);
|
||||||
|
|
||||||
VaultComponent build();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+46
-27
@@ -3,14 +3,23 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* are made available under the terms of the accompanying LICENSE file.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyStringProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -18,13 +27,25 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.function.Supplier;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class VaultModule {
|
public class VaultModule {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class);
|
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
|
@Provides
|
||||||
public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
|
public Volume provideVolume(Settings settings, WebDavVolume webDavVolume, FuseVolume fuseVolume, DokanyVolume dokanyVolume) {
|
||||||
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
|
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
|
||||||
@@ -44,33 +65,33 @@ public class VaultModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@PerVault
|
@PerVault
|
||||||
@DefaultMountFlags
|
@DefaultMountFlags
|
||||||
public Supplier<String> provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
||||||
return () -> {
|
ObjectProperty<VolumeImpl> preferredVolumeImpl = settings.preferredVolumeImpl();
|
||||||
VolumeImpl preferredImpl = settings.preferredVolumeImpl().get();
|
StringProperty mountName = vaultSettings.mountName();
|
||||||
switch (preferredImpl) {
|
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
|
||||||
case FUSE:
|
|
||||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
return Bindings.createStringBinding(() -> {
|
||||||
return getMacFuseDefaultMountFlags(settings, vaultSettings);
|
VolumeImpl v = preferredVolumeImpl.get();
|
||||||
} else if (SystemUtils.IS_OS_LINUX) {
|
if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_MAC) {
|
||||||
return getLinuxFuseDefaultMountFlags(settings, vaultSettings);
|
return getMacFuseDefaultMountFlags(mountName, readOnly);
|
||||||
}
|
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_LINUX) {
|
||||||
case DOKANY:
|
return getLinuxFuseDefaultMountFlags(readOnly);
|
||||||
return getDokanyDefaultMountFlags(settings, vaultSettings);
|
} else if (v == VolumeImpl.DOKANY && SystemUtils.IS_OS_WINDOWS) {
|
||||||
default:
|
return getDokanyDefaultMountFlags(readOnly);
|
||||||
return "--flags-supported-on-FUSE-or-DOKANY-only";
|
} else {
|
||||||
|
return "--flags-supported-on-FUSE-or-DOKANY-only";
|
||||||
}
|
}
|
||||||
};
|
}, mountName, readOnly, preferredVolumeImpl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
|
// 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;
|
assert SystemUtils.IS_OS_MAC_OSX;
|
||||||
|
|
||||||
StringBuilder flags = new StringBuilder();
|
StringBuilder flags = new StringBuilder();
|
||||||
if (vaultSettings.usesReadOnlyMode().get()) {
|
if (readOnly.get()) {
|
||||||
flags.append(" -ordonly");
|
flags.append(" -ordonly");
|
||||||
}
|
}
|
||||||
flags.append(" -ovolname=").append(vaultSettings.mountName().get());
|
flags.append(" -ovolname=").append(mountName.get());
|
||||||
flags.append(" -oatomic_o_trunc");
|
flags.append(" -oatomic_o_trunc");
|
||||||
flags.append(" -oauto_xattr");
|
flags.append(" -oauto_xattr");
|
||||||
flags.append(" -oauto_cache");
|
flags.append(" -oauto_cache");
|
||||||
@@ -92,11 +113,10 @@ public class VaultModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// see https://manpages.debian.org/testing/fuse/mount.fuse.8.en.html
|
// 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;
|
assert SystemUtils.IS_OS_LINUX;
|
||||||
|
|
||||||
StringBuilder flags = new StringBuilder();
|
StringBuilder flags = new StringBuilder();
|
||||||
if (vaultSettings.usesReadOnlyMode().get()) {
|
if (readOnly.get()) {
|
||||||
flags.append(" -oro");
|
flags.append(" -oro");
|
||||||
}
|
}
|
||||||
flags.append(" -oauto_unmount");
|
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
|
// 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;
|
assert SystemUtils.IS_OS_WINDOWS;
|
||||||
|
|
||||||
StringBuilder flags = new StringBuilder();
|
StringBuilder flags = new StringBuilder();
|
||||||
flags.append(" --options CURRENT_SESSION");
|
flags.append(" --options CURRENT_SESSION");
|
||||||
if (vaultSettings.usesReadOnlyMode().get()) {
|
if (readOnly.get()) {
|
||||||
flags.append(",WRITE_PROTECTION");
|
flags.append(",WRITE_PROTECTION");
|
||||||
}
|
}
|
||||||
flags.append(" --thread-count 5");
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-1
@@ -1,9 +1,11 @@
|
|||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,6 +30,8 @@ public interface Volume {
|
|||||||
|
|
||||||
void unmount() throws VolumeException;
|
void unmount() throws VolumeException;
|
||||||
|
|
||||||
|
Optional<Path> getMountPoint();
|
||||||
|
|
||||||
// optional forced unmounting:
|
// optional forced unmounting:
|
||||||
|
|
||||||
default boolean supportsForcedUnmount() {
|
default boolean supportsForcedUnmount() {
|
||||||
+10
-2
@@ -1,4 +1,4 @@
|
|||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
@@ -13,6 +13,8 @@ import javax.inject.Inject;
|
|||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class WebDavVolume implements Volume {
|
public class WebDavVolume implements Volume {
|
||||||
|
|
||||||
@@ -25,6 +27,7 @@ public class WebDavVolume implements Volume {
|
|||||||
private WebDavServer server;
|
private WebDavServer server;
|
||||||
private WebDavServletController servlet;
|
private WebDavServletController servlet;
|
||||||
private Mounter.Mount mount;
|
private Mounter.Mount mount;
|
||||||
|
private Path mountPoint;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings) {
|
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings) {
|
||||||
@@ -52,7 +55,7 @@ public class WebDavVolume implements Volume {
|
|||||||
}
|
}
|
||||||
MountParams mountParams = MountParams.create() //
|
MountParams mountParams = MountParams.create() //
|
||||||
.withWindowsDriveLetter(vaultSettings.winDriveLetter().get()) //
|
.withWindowsDriveLetter(vaultSettings.winDriveLetter().get()) //
|
||||||
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get())//
|
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get().getPrefix())//
|
||||||
.withWebdavHostname(getLocalhostAliasOrNull()) //
|
.withWebdavHostname(getLocalhostAliasOrNull()) //
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
@@ -93,6 +96,11 @@ public class WebDavVolume implements Volume {
|
|||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Path> getMountPoint() {
|
||||||
|
return Optional.ofNullable(mountPoint);
|
||||||
|
}
|
||||||
|
|
||||||
private String getLocalhostAliasOrNull() {
|
private String getLocalhostAliasOrNull() {
|
||||||
try {
|
try {
|
||||||
InetAddress alias = InetAddress.getByName(LOCALHOST_ALIAS);
|
InetAddress alias = InetAddress.getByName(LOCALHOST_ALIAS);
|
||||||
+15
-13
@@ -3,31 +3,31 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* are made available under the terms of the accompanying LICENSE file.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.model;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.FxApplicationScoped;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
@FxApplicationScoped
|
@Singleton
|
||||||
public final class WindowsDriveLetters {
|
public final class WindowsDriveLetters {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(WindowsDriveLetters.class);
|
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 {
|
static {
|
||||||
try (IntStream stream = IntStream.rangeClosed('D', 'Z')) {
|
try (IntStream stream = IntStream.rangeClosed('A', 'Z')) {
|
||||||
D_TO_Z = stream.mapToObj(i -> Path.of(((char) i)+":\\")).collect(Collectors.toSet());
|
A_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,20 +35,22 @@ public final class WindowsDriveLetters {
|
|||||||
public WindowsDriveLetters() {
|
public WindowsDriveLetters() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Path> getOccupiedDriveLetters() {
|
public Set<String> getAllDriveLetters() {
|
||||||
|
return A_TO_Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getOccupiedDriveLetters() {
|
||||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
if (!SystemUtils.IS_OS_WINDOWS) {
|
||||||
LOG.warn("Attempted to get occupied drive letters on non-Windows machine.");
|
LOG.warn("Attempted to get occupied drive letters on non-Windows machine.");
|
||||||
return Set.of();
|
return Set.of();
|
||||||
} else {
|
} else {
|
||||||
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
|
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() {
|
public Set<String> getAvailableDriveLetters() {
|
||||||
Set<Path> occupiedDriveLetters = getOccupiedDriveLetters();
|
return Sets.difference(A_TO_Z, getOccupiedDriveLetters());
|
||||||
Predicate<Path> isOccupiedDriveLetter = occupiedDriveLetters::contains;
|
|
||||||
return D_TO_Z.stream().filter(isOccupiedDriveLetter.negate()).collect(Collectors.toSet());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -30,7 +30,7 @@ public class SettingsJsonAdapterTest {
|
|||||||
Assertions.assertEquals(2, settings.getDirectories().size());
|
Assertions.assertEquals(2, settings.getDirectories().size());
|
||||||
Assertions.assertEquals(8080, settings.port().get());
|
Assertions.assertEquals(8080, settings.port().get());
|
||||||
Assertions.assertEquals(42, settings.numTrayNotifications().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());
|
Assertions.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,22 +8,20 @@ package org.cryptomator.common.settings;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class SettingsTest {
|
public class SettingsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAutoSave() throws IOException {
|
public void testAutoSave() {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked") Consumer<Settings> changeListener = Mockito.mock(Consumer.class);
|
||||||
Consumer<Settings> changeListener = Mockito.mock(Consumer.class);
|
|
||||||
Settings settings = new Settings();
|
Settings settings = new Settings();
|
||||||
settings.setSaveCmd(changeListener);
|
settings.setSaveCmd(changeListener);
|
||||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||||
Mockito.verify(changeListener, Mockito.times(0)).accept(settings);
|
Mockito.verify(changeListener, Mockito.times(0)).accept(settings);
|
||||||
|
|
||||||
// first change (to property):
|
// first change (to property):
|
||||||
settings.preferredGvfsScheme().set("asd");
|
settings.preferredGvfsScheme().set(WebDavUrlScheme.WEBDAV);
|
||||||
Mockito.verify(changeListener, Mockito.times(1)).accept(settings);
|
Mockito.verify(changeListener, Mockito.times(1)).accept(settings);
|
||||||
|
|
||||||
// second change (to list):
|
// 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>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.4.14</version>
|
<version>1.5.0-beta1</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>keychain</artifactId>
|
<artifactId>keychain</artifactId>
|
||||||
<name>System Keychain Access</name>
|
<name>System Keychain Access</name>
|
||||||
@@ -23,11 +23,7 @@
|
|||||||
<groupId>com.google.code.gson</groupId>
|
<groupId>com.google.code.gson</groupId>
|
||||||
<artifactId>gson</artifactId>
|
<artifactId>gson</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>jni</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Google -->
|
<!-- Google -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ interface KeychainAccessStrategy extends KeychainAccess {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return <code>true</code> if this KeychainAccessStrategy works on the current machine.
|
* @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();
|
boolean isSupported();
|
||||||
|
|
||||||
|
|||||||
@@ -5,30 +5,18 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.keychain;
|
package org.cryptomator.keychain;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.ElementsIntoSet;
|
import dagger.multibindings.ElementsIntoSet;
|
||||||
import org.cryptomator.jni.JniFunctions;
|
import org.cryptomator.common.JniModule;
|
||||||
import org.cryptomator.jni.MacFunctions;
|
|
||||||
import org.cryptomator.jni.WinFunctions;
|
|
||||||
|
|
||||||
@Module
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Module(includes = {JniModule.class})
|
||||||
public class KeychainModule {
|
public class KeychainModule {
|
||||||
|
|
||||||
@Provides
|
|
||||||
Optional<MacFunctions> provideOptionalMacFunctions() {
|
|
||||||
return JniFunctions.macFunctions();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
Optional<WinFunctions> provideOptionalWinFunctions() {
|
|
||||||
return JniFunctions.winFunctions();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@ElementsIntoSet
|
@ElementsIntoSet
|
||||||
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
|
Set<KeychainAccessStrategy> provideKeychainAccessStrategies(MacSystemKeychainAccess macKeychain, WindowsProtectedKeychainAccess winKeychain, LinuxSecretServiceKeychainAccess linKeychain) {
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ class LinuxSecretServiceKeychainAccessImpl implements KeychainAccessStrategy {
|
|||||||
try (@SuppressWarnings("unused") SimpleCollection keyring = new SimpleCollection()) {
|
try (@SuppressWarnings("unused") SimpleCollection keyring = new SimpleCollection()) {
|
||||||
// seems like we're able to access the keyring.
|
// seems like we're able to access the keyring.
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException | RuntimeException e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.4.14</version>
|
<version>1.5.0-beta1</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>launcher</artifactId>
|
<artifactId>launcher</artifactId>
|
||||||
<name>Cryptomator Launcher</name>
|
<name>Cryptomator Launcher</name>
|
||||||
|
|||||||
@@ -11,30 +11,38 @@ import java.util.concurrent.ConcurrentMap;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class CleanShutdownPerformer extends Thread {
|
class CleanShutdownPerformer extends Thread {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CleanShutdownPerformer.class);
|
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
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
LOG.debug("Running graceful shutdown tasks...");
|
LOG.debug("Running graceful shutdown tasks...");
|
||||||
SHUTDOWN_TASKS.keySet().forEach(r -> {
|
tasks.keySet().forEach(r -> {
|
||||||
try {
|
try {
|
||||||
r.run();
|
r.run();
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
LOG.error("Exception while shutting down.", e);
|
LOG.error("Exception while shutting down.", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
SHUTDOWN_TASKS.clear();
|
tasks.clear();
|
||||||
LOG.info("Goodbye.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void scheduleShutdownTask(Runnable task) {
|
void scheduleShutdownTask(Runnable task) {
|
||||||
SHUTDOWN_TASKS.put(task, Boolean.TRUE);
|
tasks.put(task, Boolean.TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerShutdownHook() {
|
void registerShutdownHook() {
|
||||||
Runtime.getRuntime().addShutdownHook(new CleanShutdownPerformer());
|
Runtime.getRuntime().addShutdownHook(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,12 +5,10 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
import javafx.application.Application;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.logging.DebugMode;
|
import org.cryptomator.logging.DebugMode;
|
||||||
import org.cryptomator.logging.LoggerConfiguration;
|
import org.cryptomator.logging.LoggerConfiguration;
|
||||||
import org.cryptomator.ui.controllers.MainController;
|
import org.cryptomator.ui.launcher.UiLauncher;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -19,6 +17,7 @@ import javax.inject.Named;
|
|||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class Cryptomator {
|
public class Cryptomator {
|
||||||
@@ -32,13 +31,19 @@ public class Cryptomator {
|
|||||||
private final DebugMode debugMode;
|
private final DebugMode debugMode;
|
||||||
private final IpcFactory ipcFactory;
|
private final IpcFactory ipcFactory;
|
||||||
private final Optional<String> applicationVersion;
|
private final Optional<String> applicationVersion;
|
||||||
|
private final CountDownLatch shutdownLatch;
|
||||||
|
private final CleanShutdownPerformer shutdownPerformer;
|
||||||
|
private final UiLauncher uiLauncher;
|
||||||
|
|
||||||
@Inject
|
@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.logConfig = logConfig;
|
||||||
this.debugMode = debugMode;
|
this.debugMode = debugMode;
|
||||||
this.ipcFactory = ipcFactory;
|
this.ipcFactory = ipcFactory;
|
||||||
this.applicationVersion = applicationVersion;
|
this.applicationVersion = applicationVersion;
|
||||||
|
this.shutdownLatch = shutdownLatch;
|
||||||
|
this.shutdownPerformer = shutdownPerformer;
|
||||||
|
this.uiLauncher = uiLauncher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
@@ -48,6 +53,7 @@ public class Cryptomator {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Main entry point of the application launcher.
|
* Main entry point of the application launcher.
|
||||||
|
*
|
||||||
* @param args The arguments passed to this program via {@link #main(String[])}.
|
* @param args The arguments passed to this program via {@link #main(String[])}.
|
||||||
* @return Nonzero exit code in case of an error.
|
* @return Nonzero exit code in case of an error.
|
||||||
*/
|
*/
|
||||||
@@ -63,6 +69,7 @@ public class Cryptomator {
|
|||||||
try (IpcFactory.IpcEndpoint endpoint = ipcFactory.create()) {
|
try (IpcFactory.IpcEndpoint endpoint = ipcFactory.create()) {
|
||||||
endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
|
endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
|
||||||
if (endpoint.isConnectedToRemote()) {
|
if (endpoint.isConnectedToRemote()) {
|
||||||
|
endpoint.getRemote().revealRunningApp();
|
||||||
LOG.info("Found running application instance. Shutting down...");
|
LOG.info("Found running application instance. Shutting down...");
|
||||||
return 2;
|
return 2;
|
||||||
} else {
|
} else {
|
||||||
@@ -77,45 +84,22 @@ public class Cryptomator {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Launches the JavaFX application and waits until shutdown is requested.
|
* Launches the JavaFX application and waits until shutdown is requested.
|
||||||
|
*
|
||||||
* @return Nonzero exit code in case of an error.
|
* @return Nonzero exit code in case of an error.
|
||||||
|
* @implNote This method blocks until {@link #shutdownLatch} reached zero.
|
||||||
*/
|
*/
|
||||||
private int runGuiApplication() {
|
private int runGuiApplication() {
|
||||||
try {
|
try {
|
||||||
CleanShutdownPerformer.registerShutdownHook();
|
shutdownPerformer.registerShutdownHook();
|
||||||
Application.launch(MainApp.class);
|
uiLauncher.launch();
|
||||||
LOG.info("Shutting down...");
|
shutdownLatch.await();
|
||||||
|
LOG.info("UI shut down");
|
||||||
return 0;
|
return 0;
|
||||||
} catch (Throwable e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.error("Terminating due to error", e);
|
Thread.currentThread().interrupt();
|
||||||
return 1;
|
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 dagger.Component;
|
||||||
import org.cryptomator.common.CommonsModule;
|
import org.cryptomator.common.CommonsModule;
|
||||||
import org.cryptomator.logging.LoggerModule;
|
import org.cryptomator.logging.LoggerModule;
|
||||||
|
import org.cryptomator.ui.launcher.UiLauncherModule;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class})
|
@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class, UiLauncherModule.class})
|
||||||
public interface CryptomatorComponent {
|
public interface CryptomatorComponent {
|
||||||
|
|
||||||
Cryptomator application();
|
Cryptomator application();
|
||||||
|
|
||||||
FxApplicationComponent.Builder fxApplicationComponent();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,31 +2,28 @@ package org.cryptomator.launcher;
|
|||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
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.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
class CryptomatorModule {
|
class CryptomatorModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
static Settings provideSettings(SettingsProvider settingsProvider) {
|
@Named("shutdownTaskScheduler")
|
||||||
return settingsProvider.get();
|
Consumer<Runnable> provideShutdownTaskScheduler(CleanShutdownPerformer shutdownPerformer) {
|
||||||
|
return shutdownPerformer::scheduleShutdownTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@Named("launchEventQueue")
|
@Named("shutdownLatch")
|
||||||
static BlockingQueue<AppLaunchEvent> provideFileOpenRequests() {
|
static CountDownLatch provideShutdownLatch() {
|
||||||
return new ArrayBlockingQueue<>(10);
|
return new CountDownLatch(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
import org.cryptomator.ui.model.AppLaunchEvent;
|
import org.cryptomator.ui.launcher.AppLaunchEvent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -34,16 +34,14 @@ class FileOpenRequestHandler {
|
|||||||
@Inject
|
@Inject
|
||||||
public FileOpenRequestHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
|
public FileOpenRequestHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
|
||||||
this.launchEventQueue = launchEventQueue;
|
this.launchEventQueue = launchEventQueue;
|
||||||
try {
|
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.APP_OPEN_FILE)) {
|
||||||
Desktop.getDesktop().setOpenFileHandler(this::openFiles);
|
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);
|
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);
|
tryToEnqueueFileOpenRequest(launchEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +59,7 @@ class FileOpenRequestHandler {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}).filter(Objects::nonNull);
|
}).filter(Objects::nonNull);
|
||||||
AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen);
|
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||||
tryToEnqueueFileOpenRequest(launchEvent);
|
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 {
|
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;
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
|
import org.cryptomator.ui.launcher.AppLaunchEvent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class IpcProtocolImpl implements IpcProtocol {
|
class IpcProtocolImpl implements IpcProtocol {
|
||||||
@@ -13,15 +17,22 @@ class IpcProtocolImpl implements IpcProtocol {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(IpcProtocolImpl.class);
|
private static final Logger LOG = LoggerFactory.getLogger(IpcProtocolImpl.class);
|
||||||
|
|
||||||
private final FileOpenRequestHandler fileOpenRequestHandler;
|
private final FileOpenRequestHandler fileOpenRequestHandler;
|
||||||
|
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler) {
|
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
|
||||||
this.fileOpenRequestHandler = fileOpenRequestHandler;
|
this.fileOpenRequestHandler = fileOpenRequestHandler;
|
||||||
|
this.launchEventQueue = launchEventQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleLaunchArgs(String[] args) {
|
public void revealRunningApp() {
|
||||||
LOG.info("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
|
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);
|
fileOpenRequestHandler.handleLaunchArgs(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
|
|
||||||
import org.cryptomator.ui.model.AppLaunchEvent;
|
import org.cryptomator.ui.launcher.AppLaunchEvent;
|
||||||
import org.hamcrest.CoreMatchers;
|
import org.hamcrest.CoreMatchers;
|
||||||
import org.hamcrest.MatcherAssert;
|
import org.hamcrest.MatcherAssert;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
@@ -64,7 +64,7 @@ public class FileOpenRequestHandlerTest {
|
|||||||
@Test
|
@Test
|
||||||
@DisplayName("./cryptomator.exe foo (with full event queue)")
|
@DisplayName("./cryptomator.exe foo (with full event queue)")
|
||||||
public void testOpenArgsWithFullQueue() throws IOException {
|
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);
|
Assumptions.assumeTrue(queue.remainingCapacity() == 0);
|
||||||
|
|
||||||
inTest.handleLaunchArgs(new String[]{"foo"});
|
inTest.handleLaunchArgs(new String[]{"foo"});
|
||||||
|
|||||||
+23
-40
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.4.14</version>
|
<version>1.5.0-beta1</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>Cryptomator</name>
|
<name>Cryptomator</name>
|
||||||
|
|
||||||
@@ -24,43 +24,31 @@
|
|||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
<!-- dependency versions -->
|
<!-- dependency versions -->
|
||||||
<cryptomator.cryptolib.version>1.2.1</cryptomator.cryptolib.version>
|
<cryptomator.cryptofs.version>1.9.0-rc2</cryptomator.cryptofs.version>
|
||||||
<cryptomator.cryptofs.version>1.8.6</cryptomator.cryptofs.version>
|
<cryptomator.jni.version>2.2.1</cryptomator.jni.version>
|
||||||
<cryptomator.jni.version>2.0.0</cryptomator.jni.version>
|
<cryptomator.fuse.version>1.2.1</cryptomator.fuse.version>
|
||||||
<cryptomator.fuse.version>1.2.0</cryptomator.fuse.version>
|
<cryptomator.dokany.version>1.1.11</cryptomator.dokany.version>
|
||||||
<cryptomator.dokany.version>1.1.10</cryptomator.dokany.version>
|
|
||||||
<cryptomator.webdav.version>1.0.10</cryptomator.webdav.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.9</commons-lang3.version>
|
||||||
<commons-lang3.version>3.8.1</commons-lang3.version>
|
|
||||||
|
|
||||||
<easybind.version>1.0.3</easybind.version>
|
<easybind.version>1.0.3</easybind.version>
|
||||||
|
|
||||||
<guava.version>27.1-jre</guava.version>
|
<guava.version>28.1-jre</guava.version>
|
||||||
<dagger.version>2.22.1</dagger.version>
|
<dagger.version>2.25.2</dagger.version>
|
||||||
<gson.version>2.8.5</gson.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>
|
<logback.version>1.2.3</logback.version>
|
||||||
|
|
||||||
<junit.jupiter.version>5.4.2</junit.jupiter.version>
|
<junit.jupiter.version>5.5.2</junit.jupiter.version>
|
||||||
<mockito.version>2.27.0</mockito.version>
|
<mockito.version>3.1.0</mockito.version>
|
||||||
<hamcrest.version>2.1</hamcrest.version>
|
<hamcrest.version>2.2</hamcrest.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<repositories>
|
<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>
|
<repository>
|
||||||
<id>jcenter</id>
|
<id>jcenter</id>
|
||||||
<url>http://jcenter.bintray.com</url>
|
<url>http://jcenter.bintray.com</url>
|
||||||
@@ -92,11 +80,6 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Cryptomator Libs -->
|
<!-- Cryptomator Libs -->
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>cryptolib</artifactId>
|
|
||||||
<version>${cryptomator.cryptolib.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>cryptofs</artifactId>
|
<artifactId>cryptofs</artifactId>
|
||||||
@@ -129,6 +112,11 @@
|
|||||||
<artifactId>javafx-base</artifactId>
|
<artifactId>javafx-base</artifactId>
|
||||||
<version>${javafx.version}</version>
|
<version>${javafx.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-graphics</artifactId>
|
||||||
|
<version>${javafx.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
<artifactId>javafx-controls</artifactId>
|
<artifactId>javafx-controls</artifactId>
|
||||||
@@ -163,11 +151,6 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Apache Commons -->
|
<!-- Apache Commons -->
|
||||||
<dependency>
|
|
||||||
<groupId>commons-io</groupId>
|
|
||||||
<artifactId>commons-io</artifactId>
|
|
||||||
<version>${commons-io.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
@@ -278,7 +261,7 @@
|
|||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-dependency-plugin</artifactId>
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
<version>3.1.0</version>
|
<version>3.1.1</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>copy-libs</id>
|
<id>copy-libs</id>
|
||||||
@@ -307,7 +290,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.jacoco</groupId>
|
<groupId>org.jacoco</groupId>
|
||||||
<artifactId>jacoco-maven-plugin</artifactId>
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
<version>0.8.2</version>
|
<version>0.8.5</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>prepare-agent</id>
|
<id>prepare-agent</id>
|
||||||
@@ -334,7 +317,7 @@
|
|||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.8.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<release>11</release>
|
<release>11</release>
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths>
|
||||||
@@ -349,7 +332,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>2.22.1</version>
|
<version>2.22.2</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|||||||
+3
-30
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.4.14</version>
|
<version>1.5.0-beta1</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>ui</artifactId>
|
<artifactId>ui</artifactId>
|
||||||
<name>Cryptomator GUI</name>
|
<name>Cryptomator GUI</name>
|
||||||
@@ -18,33 +18,10 @@
|
|||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>cryptofs</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>jni</artifactId>
|
<artifactId>jni</artifactId>
|
||||||
</dependency>
|
</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 -->
|
<!-- JavaFx -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -72,11 +49,7 @@
|
|||||||
<artifactId>gson</artifactId>
|
<artifactId>gson</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- apache commons -->
|
<!-- Apache Commons -->
|
||||||
<dependency>
|
|
||||||
<groupId>commons-io</groupId>
|
|
||||||
<artifactId>commons-io</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
@@ -92,7 +65,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.nulab-inc</groupId>
|
<groupId>com.nulab-inc</groupId>
|
||||||
<artifactId>zxcvbn</artifactId>
|
<artifactId>zxcvbn</artifactId>
|
||||||
<version>1.2.2</version>
|
<version>1.2.7</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Logging -->
|
<!-- 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+51
@@ -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);
|
||||||
|
}
|
||||||
+47
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
@@ -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 {
|
||||||
|
|
||||||
|
}
|
||||||
+73
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+183
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+106
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+223
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+113
@@ -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 {
|
||||||
|
}
|
||||||
+7
-6
@@ -3,21 +3,22 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* are made available under the terms of the accompanying LICENSE file.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.controllers;
|
package org.cryptomator.ui.common;
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.METHOD;
|
import dagger.MapKey;
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|
||||||
|
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.Target;
|
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
|
@Documented
|
||||||
@Target(METHOD)
|
@Target(METHOD)
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
@MapKey
|
@MapKey
|
||||||
public @interface ViewControllerKey {
|
public @interface FxControllerKey {
|
||||||
Class<? extends ViewController> value();
|
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 "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* are made available under the terms of the accompanying LICENSE file.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.util;
|
package org.cryptomator.ui.common;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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,581 +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.keychain.KeychainAccessException;
|
|
||||||
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()) {
|
|
||||||
try {
|
|
||||||
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, ' ');
|
|
||||||
}
|
|
||||||
} catch (KeychainAccessException e) {
|
|
||||||
LOG.error("Failed to load stored password from system keychain.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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())) {
|
|
||||||
try {
|
|
||||||
keychainAccess.get().deletePassphrase(vault.getId());
|
|
||||||
} catch (KeychainAccessException e) {
|
|
||||||
LOG.error("Failed to remove entry from system keychain.", e);
|
|
||||||
}
|
|
||||||
} else if (ButtonType.CANCEL.equals(choice.get())) {
|
|
||||||
savePassword.setSelected(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasStoredPassword() {
|
|
||||||
try {
|
|
||||||
char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
|
|
||||||
boolean hasPw = (storedPw != null);
|
|
||||||
if (storedPw != null) {
|
|
||||||
Arrays.fill(storedPw, ' ');
|
|
||||||
}
|
|
||||||
return hasPw;
|
|
||||||
} catch (KeychainAccessException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ****************************************
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,275 +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 javafx.animation.Animation;
|
|
||||||
import javafx.animation.KeyFrame;
|
|
||||||
import javafx.animation.Timeline;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.event.EventHandler;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.geometry.Side;
|
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.chart.LineChart;
|
|
||||||
import javafx.scene.chart.NumberAxis;
|
|
||||||
import javafx.scene.chart.XYChart.Data;
|
|
||||||
import javafx.scene.chart.XYChart.Series;
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.ButtonType;
|
|
||||||
import javafx.scene.control.ContextMenu;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.ToggleButton;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import javafx.stage.PopupWindow.AnchorLocation;
|
|
||||||
import javafx.util.Duration;
|
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
|
||||||
import org.cryptomator.ui.model.Vault;
|
|
||||||
import org.cryptomator.ui.util.DialogBuilderUtil;
|
|
||||||
import org.cryptomator.ui.util.Tasks;
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
|
|
||||||
import static java.lang.String.format;
|
|
||||||
|
|
||||||
public class UnlockedController implements ViewController {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(UnlockedController.class);
|
|
||||||
|
|
||||||
private static final int IO_SAMPLING_STEPS = 100;
|
|
||||||
private static final double IO_SAMPLING_INTERVAL = 0.5;
|
|
||||||
|
|
||||||
private final Localization localization;
|
|
||||||
private final ExecutorService executor;
|
|
||||||
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
|
||||||
private Optional<LockListener> listener = Optional.empty();
|
|
||||||
private Timeline ioAnimation;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label messageLabel;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private LineChart<Number, Number> ioGraph;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private NumberAxis xAxis;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ToggleButton moreOptionsButton;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ContextMenu moreOptionsMenu;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private VBox root;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public UnlockedController(Localization localization, ExecutorService executor) {
|
|
||||||
this.localization = localization;
|
|
||||||
this.executor = executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
EasyBind.subscribe(vault, this::vaultChanged);
|
|
||||||
EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Parent getRoot() {
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void vaultChanged(Vault newVault) {
|
|
||||||
if (newVault == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// (re)start throughput statistics:
|
|
||||||
stopIoSampling();
|
|
||||||
startIoSampling();
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void didClickLockVault() {
|
|
||||||
regularLockVault(this::lockVaultSucceeded);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void lockVaultSucceeded() {
|
|
||||||
listener.ifPresent(listener -> listener.didLock(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void regularLockVault(Runnable onSuccess) {
|
|
||||||
Tasks.create(() -> {
|
|
||||||
vault.get().lock(false);
|
|
||||||
}).onSuccess(() -> {
|
|
||||||
LOG.trace("Regular unmount succeeded.");
|
|
||||||
onSuccess.run();
|
|
||||||
}).onError(Exception.class, e -> {
|
|
||||||
onRegularUnmountVaultFailed(e, onSuccess);
|
|
||||||
}).runOnce(executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onRegularUnmountVaultFailed(Exception e, Runnable onSuccess) {
|
|
||||||
if (vault.get().supportsForcedUnmount()) {
|
|
||||||
LOG.trace("Regular unmount failed.", e);
|
|
||||||
Alert confirmDialog = DialogBuilderUtil.buildYesNoDialog( //
|
|
||||||
format(localization.getString("unlocked.lock.force.confirmation.title"), vault.get().name().getValue()), //
|
|
||||||
localization.getString("unlocked.lock.force.confirmation.header"), //
|
|
||||||
localization.getString("unlocked.lock.force.confirmation.content"), //
|
|
||||||
ButtonType.NO);
|
|
||||||
|
|
||||||
Optional<ButtonType> choice = confirmDialog.showAndWait();
|
|
||||||
if (ButtonType.YES.equals(choice.get())) {
|
|
||||||
forcedLockVault(onSuccess);
|
|
||||||
} else {
|
|
||||||
LOG.trace("Unmount cancelled.", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOG.error("Regular unmount failed.", e);
|
|
||||||
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void forcedLockVault(Runnable onSuccess) {
|
|
||||||
Tasks.create(() -> {
|
|
||||||
vault.get().lock(true);
|
|
||||||
}).onSuccess(() -> {
|
|
||||||
LOG.trace("Forced unmount succeeded.");
|
|
||||||
onSuccess.run();
|
|
||||||
}).onError(Exception.class, e -> {
|
|
||||||
LOG.error("Forced unmount failed.", e);
|
|
||||||
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
|
|
||||||
}).runOnce(executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void didClickMoreOptions() {
|
|
||||||
if (moreOptionsMenu.isShowing()) {
|
|
||||||
moreOptionsMenu.hide();
|
|
||||||
} else {
|
|
||||||
moreOptionsMenu.setAnchorLocation(AnchorLocation.CONTENT_TOP_RIGHT);
|
|
||||||
moreOptionsMenu.show(moreOptionsButton, Side.BOTTOM, moreOptionsButton.getWidth(), 0.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void didClickRevealVault() {
|
|
||||||
revealVault(vault.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
void revealVault(Vault vault) {
|
|
||||||
Tasks.create(() -> {
|
|
||||||
vault.reveal();
|
|
||||||
}).onSuccess(() -> {
|
|
||||||
LOG.trace("Reveal succeeded.");
|
|
||||||
messageLabel.setText(null);
|
|
||||||
}).onError(Exception.class, e -> {
|
|
||||||
LOG.error("Reveal failed.", e);
|
|
||||||
messageLabel.setText(localization.getString("unlocked.label.revealFailed"));
|
|
||||||
}).runOnce(executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ****************************************
|
|
||||||
// IO Graph
|
|
||||||
// ****************************************
|
|
||||||
|
|
||||||
private void startIoSampling() {
|
|
||||||
final Series<Number, Number> decryptedBytes = new Series<>();
|
|
||||||
decryptedBytes.setName(localization.getString("unlocked.label.statsDecrypted"));
|
|
||||||
final Series<Number, Number> encryptedBytes = new Series<>();
|
|
||||||
encryptedBytes.setName(localization.getString("unlocked.label.statsEncrypted"));
|
|
||||||
|
|
||||||
ioGraph.getData().add(decryptedBytes);
|
|
||||||
ioGraph.getData().add(encryptedBytes);
|
|
||||||
|
|
||||||
ioAnimation = new Timeline();
|
|
||||||
ioAnimation.getKeyFrames().add(new KeyFrame(Duration.seconds(IO_SAMPLING_INTERVAL), new IoSamplingAnimationHandler(decryptedBytes, encryptedBytes)));
|
|
||||||
ioAnimation.setCycleCount(Animation.INDEFINITE);
|
|
||||||
ioAnimation.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopIoSampling() {
|
|
||||||
if (ioAnimation != null) {
|
|
||||||
ioGraph.getData().clear();
|
|
||||||
ioAnimation.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class IoSamplingAnimationHandler implements EventHandler<ActionEvent> {
|
|
||||||
|
|
||||||
private static final double BYTES_TO_MEGABYTES_FACTOR = 1.0 / IO_SAMPLING_INTERVAL / 1024.0 / 1024.0;
|
|
||||||
private final Series<Number, Number> decryptedBytes;
|
|
||||||
private final Series<Number, Number> encryptedBytes;
|
|
||||||
|
|
||||||
public IoSamplingAnimationHandler(Series<Number, Number> decryptedBytes, Series<Number, Number> encryptedBytes) {
|
|
||||||
this.decryptedBytes = decryptedBytes;
|
|
||||||
this.encryptedBytes = encryptedBytes;
|
|
||||||
|
|
||||||
// initialize data once and change value of datapoints later:
|
|
||||||
for (int i = 0; i < IO_SAMPLING_STEPS; i++) {
|
|
||||||
decryptedBytes.getData().add(new Data<>(i, 0));
|
|
||||||
encryptedBytes.getData().add(new Data<>(i, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
xAxis.setLowerBound(0);
|
|
||||||
xAxis.setUpperBound(IO_SAMPLING_STEPS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(ActionEvent event) {
|
|
||||||
// move all values one step:
|
|
||||||
for (int i = 0; i < IO_SAMPLING_STEPS - 1; i++) {
|
|
||||||
int j = i + 1;
|
|
||||||
Number tmp = decryptedBytes.getData().get(j).getYValue();
|
|
||||||
decryptedBytes.getData().get(i).setYValue(tmp);
|
|
||||||
|
|
||||||
tmp = encryptedBytes.getData().get(j).getYValue();
|
|
||||||
encryptedBytes.getData().get(i).setYValue(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add latest value:
|
|
||||||
final long decBytes = vault.get().pollBytesRead();
|
|
||||||
final double decMb = decBytes * BYTES_TO_MEGABYTES_FACTOR;
|
|
||||||
final long encBytes = vault.get().pollBytesWritten();
|
|
||||||
final double encMb = encBytes * BYTES_TO_MEGABYTES_FACTOR;
|
|
||||||
decryptedBytes.getData().get(IO_SAMPLING_STEPS - 1).setYValue(decMb);
|
|
||||||
encryptedBytes.getData().get(IO_SAMPLING_STEPS - 1).setYValue(encMb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Getter/Setter */
|
|
||||||
|
|
||||||
public Vault getVault() {
|
|
||||||
return this.vault.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVault(Vault vault) {
|
|
||||||
this.vault.set(vault);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* callback */
|
|
||||||
|
|
||||||
public void setListener(LockListener listener) {
|
|
||||||
this.listener = Optional.ofNullable(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
interface LockListener {
|
|
||||||
|
|
||||||
void didLock(UnlockedController ctrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,162 +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 javax.inject.Inject;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
|
|
||||||
import javafx.beans.binding.BooleanExpression;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.CheckBox;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.ProgressIndicator;
|
|
||||||
import javafx.scene.layout.GridPane;
|
|
||||||
import org.cryptomator.ui.controls.SecPasswordField;
|
|
||||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
|
|
||||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategy;
|
|
||||||
import org.cryptomator.ui.model.upgrade.UpgradeStrategy.UpgradeFailedException;
|
|
||||||
import org.cryptomator.ui.model.Vault;
|
|
||||||
import org.cryptomator.ui.util.Tasks;
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
|
|
||||||
public class UpgradeController implements ViewController {
|
|
||||||
|
|
||||||
private final ObjectProperty<UpgradeStrategy> strategy = new SimpleObjectProperty<>();
|
|
||||||
private final UpgradeStrategies strategies;
|
|
||||||
private final ExecutorService executor;
|
|
||||||
private Optional<UpgradeListener> listener = Optional.empty();
|
|
||||||
private Vault vault;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public UpgradeController(UpgradeStrategies strategies, ExecutorService executor) {
|
|
||||||
this.strategies = strategies;
|
|
||||||
this.executor = executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label upgradeTitleLabel;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label upgradeMsgLabel;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private SecPasswordField passwordField;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private CheckBox confirmationCheckbox;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Button upgradeButton;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ProgressIndicator progressIndicator;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label errorLabel;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private GridPane root;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
upgradeTitleLabel.textProperty().bind(EasyBind.monadic(strategy).map(this::upgradeTitle).orElse(""));
|
|
||||||
upgradeMsgLabel.textProperty().bind(EasyBind.monadic(strategy).map(this::upgradeMessage).orElse(""));
|
|
||||||
|
|
||||||
BooleanExpression passwordProvided = passwordField.textProperty().isNotEmpty().and(passwordField.disabledProperty().not());
|
|
||||||
BooleanExpression syncFinished = confirmationCheckbox.selectedProperty();
|
|
||||||
upgradeButton.disableProperty().bind(passwordProvided.not().or(syncFinished.not()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Parent getRoot() {
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void focus() {
|
|
||||||
passwordField.requestFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setVault(Vault vault) {
|
|
||||||
this.vault = Objects.requireNonNull(vault);
|
|
||||||
errorLabel.setText(null);
|
|
||||||
strategy.set(strategies.getUpgradeStrategy(vault));
|
|
||||||
// trigger "default" change to refresh key bindings:
|
|
||||||
upgradeButton.setDefaultButton(false);
|
|
||||||
upgradeButton.setDefaultButton(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ****************************************
|
|
||||||
// Upgrade label
|
|
||||||
// ****************************************
|
|
||||||
|
|
||||||
private String upgradeTitle(UpgradeStrategy instruction) {
|
|
||||||
return instruction.getTitle(vault);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String upgradeMessage(UpgradeStrategy instruction) {
|
|
||||||
return instruction.getMessage(vault);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ****************************************
|
|
||||||
// Upgrade button
|
|
||||||
// ****************************************
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private void didClickUpgradeButton(ActionEvent event) {
|
|
||||||
EasyBind.monadic(strategy).ifPresent(this::upgrade);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void upgrade(UpgradeStrategy instruction) {
|
|
||||||
passwordField.setDisable(true);
|
|
||||||
progressIndicator.setVisible(true);
|
|
||||||
Tasks //
|
|
||||||
.create(() -> {
|
|
||||||
if (!instruction.isApplicable(vault)) {
|
|
||||||
throw new IllegalStateException("No ugprade needed for " + vault.getPath());
|
|
||||||
}
|
|
||||||
instruction.upgrade(vault, passwordField.getCharacters());
|
|
||||||
}) //
|
|
||||||
.onSuccess(this::showNextUpgrade) //
|
|
||||||
.onError(UpgradeFailedException.class, e -> {
|
|
||||||
errorLabel.setText(e.getLocalizedMessage());
|
|
||||||
}) //
|
|
||||||
.andFinally(() -> {
|
|
||||||
progressIndicator.setVisible(false);
|
|
||||||
passwordField.setDisable(false);
|
|
||||||
passwordField.swipe();
|
|
||||||
}).runOnce(executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showNextUpgrade() {
|
|
||||||
errorLabel.setText(null);
|
|
||||||
UpgradeStrategy nextStrategy = strategies.getUpgradeStrategy(vault);
|
|
||||||
if (nextStrategy != null) {
|
|
||||||
strategy.set(nextStrategy);
|
|
||||||
} else {
|
|
||||||
listener.ifPresent(UpgradeListener::didUpgrade);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* callback */
|
|
||||||
|
|
||||||
public void setListener(UpgradeListener listener) {
|
|
||||||
this.listener = Optional.ofNullable(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
interface UpgradeListener {
|
|
||||||
void didUpgrade();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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 java.net.URL;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
|
|
||||||
import javafx.fxml.Initializable;
|
|
||||||
import javafx.scene.Parent;
|
|
||||||
|
|
||||||
public interface ViewController extends Initializable {
|
|
||||||
|
|
||||||
Parent getRoot();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default void initialize(URL location, ResourceBundle resources) {
|
|
||||||
initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
default void initialize() {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
default void focus() {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,51 +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.FXMLLoader;
|
|
||||||
import org.cryptomator.common.FxApplicationScoped;
|
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Provider;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@FxApplicationScoped
|
|
||||||
public class ViewControllerLoader {
|
|
||||||
|
|
||||||
private final Map<Class<? extends ViewController>, Provider<ViewController>> controllerProviders;
|
|
||||||
private final Localization localization;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public ViewControllerLoader(Map<Class<? extends ViewController>, Provider<ViewController>> controllerProviders, Localization localization) {
|
|
||||||
this.controllerProviders = controllerProviders;
|
|
||||||
this.localization = localization;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T extends ViewController> T load(String resourceName) {
|
|
||||||
FXMLLoader loader = new FXMLLoader();
|
|
||||||
loader.setControllerFactory(this::constructController);
|
|
||||||
loader.setResources(localization);
|
|
||||||
try (InputStream in = getClass().getResourceAsStream(resourceName)) {
|
|
||||||
loader.load(in);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException("Error loading FXML: " + resourceName, e);
|
|
||||||
}
|
|
||||||
return loader.getController();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ViewController constructController(Class<?> clazz) {
|
|
||||||
Provider<ViewController> ctrlProvider = controllerProviders.get(clazz);
|
|
||||||
if (ctrlProvider == null) {
|
|
||||||
throw new IllegalStateException("No provider for type " + clazz.getName() + " registered.");
|
|
||||||
}
|
|
||||||
return ctrlProvider.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,78 +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 dagger.Module;
|
|
||||||
import dagger.Provides;
|
|
||||||
import dagger.multibindings.IntoMap;
|
|
||||||
|
|
||||||
@Module
|
|
||||||
public class ViewControllerModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@IntoMap
|
|
||||||
@ViewControllerKey(ChangePasswordController.class)
|
|
||||||
ViewController provideChangePasswordController(ChangePasswordController controller) {
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@IntoMap
|
|
||||||
@ViewControllerKey(InitializeController.class)
|
|
||||||
ViewController provideInitializeController(InitializeController controller) {
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@IntoMap
|
|
||||||
@ViewControllerKey(MainController.class)
|
|
||||||
ViewController provideMainController(MainController controller) {
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@IntoMap
|
|
||||||
@ViewControllerKey(NotFoundController.class)
|
|
||||||
ViewController provideNotFoundController(NotFoundController controller) {
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@IntoMap
|
|
||||||
@ViewControllerKey(SettingsController.class)
|
|
||||||
ViewController provideSettingsController(SettingsController controller) {
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@IntoMap
|
|
||||||
@ViewControllerKey(UnlockController.class)
|
|
||||||
ViewController provideUnlockController(UnlockController controller) {
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@IntoMap
|
|
||||||
@ViewControllerKey(UnlockedController.class)
|
|
||||||
ViewController provideUnlockedController(UnlockedController controller) {
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@IntoMap
|
|
||||||
@ViewControllerKey(UpgradeController.class)
|
|
||||||
ViewController provideUpgradeController(UpgradeController controller) {
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@IntoMap
|
|
||||||
@ViewControllerKey(WelcomeController.class)
|
|
||||||
ViewController provideWelcomeController(WelcomeController controller) {
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,195 +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.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import javafx.application.Application;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.control.ButtonType;
|
|
||||||
import javafx.scene.control.Hyperlink;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.ProgressIndicator;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.FxApplicationScoped;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
import org.cryptomator.ui.l10n.Localization;
|
|
||||||
import org.cryptomator.ui.util.Tasks;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static org.cryptomator.ui.util.DialogBuilderUtil.buildYesNoDialog;
|
|
||||||
|
|
||||||
@FxApplicationScoped
|
|
||||||
public class WelcomeController implements ViewController {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class);
|
|
||||||
|
|
||||||
private final Application app;
|
|
||||||
private final Optional<String> applicationVersion;
|
|
||||||
private final Localization localization;
|
|
||||||
private final Settings settings;
|
|
||||||
private final Comparator<String> semVerComparator;
|
|
||||||
private final ScheduledExecutorService executor;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public WelcomeController(Application app, @Named("applicationVersion") Optional<String> applicationVersion, Localization localization, Settings settings, @Named("SemVer") Comparator<String> semVerComparator,
|
|
||||||
ScheduledExecutorService executor) {
|
|
||||||
this.app = app;
|
|
||||||
this.applicationVersion = applicationVersion;
|
|
||||||
this.localization = localization;
|
|
||||||
this.settings = settings;
|
|
||||||
this.semVerComparator = semVerComparator;
|
|
||||||
this.executor = executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Node checkForUpdatesContainer;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label checkForUpdatesStatus;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ProgressIndicator checkForUpdatesIndicator;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Hyperlink updateLink;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private VBox root;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
|
||||||
if (areUpdatesManagedExternally()) {
|
|
||||||
checkForUpdatesContainer.setVisible(false);
|
|
||||||
} else if (!settings.askedForUpdateCheck().get()) {
|
|
||||||
this.askForUpdateCheck();
|
|
||||||
} else if (settings.checkForUpdates().get()) {
|
|
||||||
this.checkForUpdates();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Parent getRoot() {
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ****************************************
|
|
||||||
// Check for updates
|
|
||||||
// ****************************************
|
|
||||||
|
|
||||||
private boolean areUpdatesManagedExternally() {
|
|
||||||
return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void askForUpdateCheck() {
|
|
||||||
Tasks.create(() -> {}).onSuccess(() -> {
|
|
||||||
Optional<ButtonType> result = buildYesNoDialog(
|
|
||||||
localization.getString("welcome.askForUpdateCheck.dialog.title"),
|
|
||||||
localization.getString("welcome.askForUpdateCheck.dialog.header"),
|
|
||||||
localization.getString("welcome.askForUpdateCheck.dialog.content"),
|
|
||||||
ButtonType.YES).showAndWait();
|
|
||||||
if (result.isPresent()) {
|
|
||||||
settings.askedForUpdateCheck().set(true);
|
|
||||||
settings.checkForUpdates().set(result.get().equals(ButtonType.YES));
|
|
||||||
}
|
|
||||||
if (settings.checkForUpdates().get()) {
|
|
||||||
this.checkForUpdates();
|
|
||||||
}
|
|
||||||
}).scheduleOnce(executor, 1, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkForUpdates() {
|
|
||||||
checkForUpdatesStatus.setText(localization.getString("welcome.checkForUpdates.label.currentlyChecking"));
|
|
||||||
checkForUpdatesIndicator.setVisible(true);
|
|
||||||
Tasks.create(() -> {
|
|
||||||
String userAgent = String.format("Cryptomator VersionChecker/%s %s %s (%s)", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
|
||||||
URL url = URI.create("https://api.cryptomator.org/updates/latestVersion.json").toURL();
|
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
||||||
conn.addRequestProperty("User-Agent", userAgent);
|
|
||||||
conn.connect();
|
|
||||||
try {
|
|
||||||
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
|
||||||
return Optional.<byte[]>empty();
|
|
||||||
}
|
|
||||||
try (InputStream in = conn.getInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
|
||||||
in.transferTo(out);
|
|
||||||
return Optional.of(out.toByteArray());
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
conn.disconnect();
|
|
||||||
}
|
|
||||||
}).onSuccess(response -> {
|
|
||||||
response.ifPresent(bytes -> {
|
|
||||||
Gson gson = new GsonBuilder().setLenient().create();
|
|
||||||
String json = new String(bytes, StandardCharsets.UTF_8);
|
|
||||||
Map<String, String> map = gson.fromJson(json, new TypeToken<Map<String, String>>() {
|
|
||||||
}.getType());
|
|
||||||
if (map != null) {
|
|
||||||
this.compareVersions(map);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).onError(Exception.class, e -> {
|
|
||||||
LOG.warn("Error checking for updates", e);
|
|
||||||
}).andFinally(() -> {
|
|
||||||
checkForUpdatesStatus.setText("");
|
|
||||||
checkForUpdatesIndicator.setVisible(false);
|
|
||||||
}).runOnce(executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void compareVersions(final Map<String, String> latestVersions) {
|
|
||||||
assert Platform.isFxApplicationThread();
|
|
||||||
final String latestVersion;
|
|
||||||
if (SystemUtils.IS_OS_MAC_OSX) {
|
|
||||||
latestVersion = latestVersions.get("mac");
|
|
||||||
} else if (SystemUtils.IS_OS_WINDOWS) {
|
|
||||||
latestVersion = latestVersions.get("win");
|
|
||||||
} else if (SystemUtils.IS_OS_LINUX) {
|
|
||||||
latestVersion = latestVersions.get("linux");
|
|
||||||
} else {
|
|
||||||
// no version check possible on unsupported OS
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final String currentVersion = applicationVersion.orElse(null);
|
|
||||||
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
|
|
||||||
if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) {
|
|
||||||
final String msg = String.format(localization.getString("welcome.newVersionMessage"), latestVersion, currentVersion);
|
|
||||||
this.updateLink.setText(msg);
|
|
||||||
this.updateLink.setVisible(true);
|
|
||||||
this.updateLink.setDisable(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void didClickUpdateLink(ActionEvent event) {
|
|
||||||
app.getHostServices().showDocument("https://cryptomator.org/");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.cryptomator.ui.controls;
|
||||||
|
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.TextFormatter;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class AlphanumericTextField extends TextField {
|
||||||
|
|
||||||
|
private final static Pattern DIGIT_PATTERN = Pattern.compile("\\w*");
|
||||||
|
|
||||||
|
public AlphanumericTextField() {
|
||||||
|
this.setTextFormatter(new TextFormatter<>(this::filterNumericTextChange));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextFormatter.Change filterNumericTextChange(TextFormatter.Change change) {
|
||||||
|
return DIGIT_PATTERN.matcher(change.getText()).matches() ? change : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,105 +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.controls;
|
|
||||||
|
|
||||||
import org.cryptomator.ui.model.Vault;
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
|
|
||||||
import javafx.beans.binding.ObjectExpression;
|
|
||||||
import javafx.geometry.Pos;
|
|
||||||
import javafx.scene.control.ContentDisplay;
|
|
||||||
import javafx.scene.control.ContextMenu;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.OverrunStyle;
|
|
||||||
import javafx.scene.control.Tooltip;
|
|
||||||
import javafx.scene.layout.HBox;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.scene.paint.Paint;
|
|
||||||
|
|
||||||
public class DirectoryListCell extends DraggableListCell<Vault> {
|
|
||||||
|
|
||||||
private static final Color UNLOCKED_ICON_COLOR = new Color(0.901, 0.494, 0.133, 1.0);
|
|
||||||
private final Label statusText = new Label();
|
|
||||||
private final Label nameText = new Label();
|
|
||||||
private final Label pathText = new Label();
|
|
||||||
private final VBox vbox = new VBox(4.0, nameText, pathText);
|
|
||||||
private final HBox hbox = new HBox(6.0, statusText, vbox);
|
|
||||||
|
|
||||||
private ContextMenu vaultContextMenu;
|
|
||||||
|
|
||||||
public DirectoryListCell() {
|
|
||||||
ObjectExpression<Vault.State> vaultState = ObjectExpression.objectExpression(EasyBind.select(itemProperty()).selectObject(Vault::stateProperty));
|
|
||||||
|
|
||||||
hbox.setAlignment(Pos.CENTER_LEFT);
|
|
||||||
hbox.setPrefWidth(1);
|
|
||||||
vbox.setFillWidth(true);
|
|
||||||
|
|
||||||
nameText.textProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::name));
|
|
||||||
nameText.textFillProperty().bind(this.textFillProperty());
|
|
||||||
nameText.fontProperty().bind(this.fontProperty());
|
|
||||||
|
|
||||||
pathText.textProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::displayablePath));
|
|
||||||
pathText.setTextOverrun(OverrunStyle.ELLIPSIS);
|
|
||||||
pathText.getStyleClass().add("detail-label");
|
|
||||||
|
|
||||||
statusText.textProperty().bind(EasyBind.map(vaultState, this::getStatusIconText));
|
|
||||||
statusText.textFillProperty().bind(EasyBind.combine(vaultState, textFillProperty(), this::getStatusIconColor));
|
|
||||||
statusText.setMinSize(16.0, 16.0);
|
|
||||||
statusText.setAlignment(Pos.CENTER);
|
|
||||||
statusText.getStyleClass().add("fontawesome");
|
|
||||||
|
|
||||||
tooltipProperty().bind(EasyBind.monadic(itemProperty()).flatMap(Vault::displayablePath).map(p -> new Tooltip(p.toString())));
|
|
||||||
contextMenuProperty().bind(EasyBind.map(vaultState, this::getContextMenu));
|
|
||||||
|
|
||||||
setGraphic(hbox);
|
|
||||||
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getStatusIconText(Vault.State state) {
|
|
||||||
if (state == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
switch (state) {
|
|
||||||
case UNLOCKED:
|
|
||||||
case PROCESSING:
|
|
||||||
return "\uf09c";
|
|
||||||
case LOCKED:
|
|
||||||
default:
|
|
||||||
return "\uf023";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Paint getStatusIconColor(Vault.State state, Paint lockedValue) {
|
|
||||||
if (state == null) {
|
|
||||||
return lockedValue;
|
|
||||||
}
|
|
||||||
switch (state) {
|
|
||||||
case UNLOCKED:
|
|
||||||
case PROCESSING:
|
|
||||||
return UNLOCKED_ICON_COLOR;
|
|
||||||
case LOCKED:
|
|
||||||
default:
|
|
||||||
return lockedValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ContextMenu getContextMenu(Vault.State state) {
|
|
||||||
if (state == Vault.State.LOCKED) {
|
|
||||||
return vaultContextMenu;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVaultContextMenu(ContextMenu contextMenu) {
|
|
||||||
this.vaultContextMenu = contextMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -8,11 +8,8 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.controls;
|
package org.cryptomator.ui.controls;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import java.util.Collections;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javafx.geometry.Insets;
|
|
||||||
import javafx.scene.SnapshotParameters;
|
import javafx.scene.SnapshotParameters;
|
||||||
import javafx.scene.control.ListCell;
|
import javafx.scene.control.ListCell;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
@@ -21,22 +18,17 @@ import javafx.scene.input.DragEvent;
|
|||||||
import javafx.scene.input.Dragboard;
|
import javafx.scene.input.Dragboard;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.input.TransferMode;
|
import javafx.scene.input.TransferMode;
|
||||||
import javafx.scene.layout.Border;
|
import org.fxmisc.easybind.EasyBind;
|
||||||
import javafx.scene.layout.BorderImage;
|
|
||||||
import javafx.scene.layout.BorderStroke;
|
|
||||||
import javafx.scene.layout.BorderStrokeStyle;
|
|
||||||
import javafx.scene.layout.BorderWidths;
|
|
||||||
import javafx.scene.layout.CornerRadii;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.scene.paint.Paint;
|
|
||||||
|
|
||||||
class DraggableListCell<T> extends ListCell<T> {
|
import java.util.List;
|
||||||
|
|
||||||
private static final double DROP_LINE_WIDTH = 4.0;
|
public class DraggableListCell<T> extends ListCell<T> {
|
||||||
private static final Paint DROP_LINE_COLOR = Color.gray(0.0, 0.6);
|
|
||||||
|
private static final String DROP_ABOVE_CLASS = "drop-above";
|
||||||
private final List<BorderStroke> defaultBorderStrokes;
|
private static final String DROP_BELOW_CLASS = "drop-below";
|
||||||
private final List<BorderImage> defaultBorderImages;
|
|
||||||
|
private final BooleanProperty dropAbove = new SimpleBooleanProperty();
|
||||||
|
private final BooleanProperty dropBelow = new SimpleBooleanProperty();
|
||||||
|
|
||||||
public DraggableListCell() {
|
public DraggableListCell() {
|
||||||
setOnDragDetected(this::onDragDetected);
|
setOnDragDetected(this::onDragDetected);
|
||||||
@@ -45,19 +37,25 @@ class DraggableListCell<T> extends ListCell<T> {
|
|||||||
setOnDragExited(this::onDragExited);
|
setOnDragExited(this::onDragExited);
|
||||||
setOnDragDropped(this::onDragDropped);
|
setOnDragDropped(this::onDragDropped);
|
||||||
setOnDragDone(DragEvent::consume);
|
setOnDragDone(DragEvent::consume);
|
||||||
this.defaultBorderStrokes = this.getBorder() == null ? Collections.emptyList() : this.getBorder().getStrokes();
|
|
||||||
this.defaultBorderImages = this.getBorder() == null ? Collections.emptyList() : this.getBorder().getImages();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Border createDropPositionBorder(double verticalCursorPosition) {
|
EasyBind.includeWhen(getStyleClass(), DROP_ABOVE_CLASS, dropAbove);
|
||||||
|
EasyBind.includeWhen(getStyleClass(), DROP_BELOW_CLASS, dropBelow);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDropPositionStyleClass(double verticalCursorPosition) {
|
||||||
boolean isUpperHalf = verticalCursorPosition < this.getHeight() / 2.0;
|
boolean isUpperHalf = verticalCursorPosition < this.getHeight() / 2.0;
|
||||||
final double topBorder = isUpperHalf ? DROP_LINE_WIDTH : 0.0;
|
if (isUpperHalf) {
|
||||||
final double bottomBorder = !isUpperHalf ? DROP_LINE_WIDTH : 0.0;
|
this.dropAbove.set(true);
|
||||||
final BorderWidths borderWidths = new BorderWidths(topBorder, 0.0, bottomBorder, 0.0);
|
this.dropBelow.set(false);
|
||||||
final BorderStroke dragStroke = new BorderStroke(DROP_LINE_COLOR, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, borderWidths, Insets.EMPTY);
|
} else {
|
||||||
final List<BorderStroke> strokes = new ArrayList<BorderStroke>(defaultBorderStrokes);
|
this.dropAbove.set(false);
|
||||||
strokes.add(0, dragStroke);
|
this.dropBelow.set(true);
|
||||||
return new Border(strokes, defaultBorderImages);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetDropPositionStyleClasses() {
|
||||||
|
this.dropAbove.set(false);
|
||||||
|
this.dropBelow.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDragDetected(MouseEvent event) {
|
private void onDragDetected(MouseEvent event) {
|
||||||
@@ -82,7 +80,7 @@ class DraggableListCell<T> extends ListCell<T> {
|
|||||||
|
|
||||||
if (event.getGestureSource() instanceof DraggableListCell<?> && event.getGestureSource() != this && event.getDragboard().hasString()) {
|
if (event.getGestureSource() instanceof DraggableListCell<?> && event.getGestureSource() != this && event.getDragboard().hasString()) {
|
||||||
event.acceptTransferModes(TransferMode.MOVE);
|
event.acceptTransferModes(TransferMode.MOVE);
|
||||||
setBorder(createDropPositionBorder(event.getY()));
|
setDropPositionStyleClass(event.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
event.consume();
|
event.consume();
|
||||||
@@ -94,7 +92,7 @@ class DraggableListCell<T> extends ListCell<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.getGestureSource() instanceof DraggableListCell<?> && event.getGestureSource() != this && event.getDragboard().hasString()) {
|
if (event.getGestureSource() instanceof DraggableListCell<?> && event.getGestureSource() != this && event.getDragboard().hasString()) {
|
||||||
setBorder(createDropPositionBorder(event.getY()));
|
setDropPositionStyleClass(event.getY());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +102,7 @@ class DraggableListCell<T> extends ListCell<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.getGestureSource() instanceof DraggableListCell<?> && event.getGestureSource() != this && event.getDragboard().hasString()) {
|
if (event.getGestureSource() instanceof DraggableListCell<?> && event.getGestureSource() != this && event.getDragboard().hasString()) {
|
||||||
setBorder(new Border(defaultBorderStrokes, defaultBorderImages));
|
resetDropPositionStyleClasses();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user