mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 16:04:28 +00:00
Compare commits
683
Commits
1.5.6
...
1.6.0-alpha2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fce262acb | ||
|
|
1159681824 | ||
|
|
c868ac32cc | ||
|
|
f85f953668 | ||
|
|
5185df631d | ||
|
|
617feec111 | ||
|
|
eb196165c5 | ||
|
|
8c37ab3675 | ||
|
|
7eaade1555 | ||
|
|
186c129a30 | ||
|
|
f74b9d3347 | ||
|
|
6038559172 | ||
|
|
98a899c7e2 | ||
|
|
783c8718c1 | ||
|
|
b47382811f | ||
|
|
62f279b9f7 | ||
|
|
ab961f1a8b | ||
|
|
9562db7e13 | ||
|
|
1ca2e029dc | ||
|
|
944e3e6c50 | ||
|
|
e134d2f422 | ||
|
|
04e5170403 | ||
|
|
ea008df9f7 | ||
|
|
e25dfaad35 | ||
|
|
e98f92c63f | ||
|
|
85b761a9d7 | ||
|
|
be8dcd4dc4 | ||
|
|
c01c405f20 | ||
|
|
b4f268f551 | ||
|
|
21f9e72cc5 | ||
|
|
4845984a35 | ||
|
|
b6f5ddaf30 | ||
|
|
1abf0e1bfa | ||
|
|
a8364a8c1a | ||
|
|
13c95868fa | ||
|
|
85ff7542f1 | ||
|
|
432be6dd80 | ||
|
|
ca0402aaf0 | ||
|
|
c7ef0056f9 | ||
|
|
cf13311665 | ||
|
|
a7133dbebe | ||
|
|
168f9b9fb9 | ||
|
|
2b332c3831 | ||
|
|
66e3625a0b | ||
|
|
20f88e203a | ||
|
|
66a8dc9811 | ||
|
|
9d89efc98c | ||
|
|
dfbe017316 | ||
|
|
d2680b9857 | ||
|
|
8e1be4c67b | ||
|
|
749e0ac02d | ||
|
|
a2ad7d69ab | ||
|
|
a47c05c87d | ||
|
|
6b871e56c8 | ||
|
|
40dbc0ba48 | ||
|
|
2e52afed51 | ||
|
|
64d54133f8 | ||
|
|
ac289cc3f7 | ||
|
|
189699d359 | ||
|
|
8e5035f506 | ||
|
|
0ce41e7ac3 | ||
|
|
c1a249fd7f | ||
|
|
c266c7583b | ||
|
|
00331d4857 | ||
|
|
8fd484e2bb | ||
|
|
165d740acd | ||
|
|
fd4010c6c9 | ||
|
|
aa2900fa9e | ||
|
|
76d1875e01 | ||
|
|
bc83e23a34 | ||
|
|
6a326bcbce | ||
|
|
573ad03d68 | ||
|
|
07efb3e94d | ||
|
|
89e680fb45 | ||
|
|
54128a0016 | ||
|
|
e2565097cd | ||
|
|
f827b1fc89 | ||
|
|
c0baab2329 | ||
|
|
70baa4d09f | ||
|
|
d710d406d0 | ||
|
|
554f245f5d | ||
|
|
34995088ba | ||
|
|
69c63702f2 | ||
|
|
db7e731554 | ||
|
|
e08b43bf17 | ||
|
|
58b45bacd1 | ||
|
|
4f942bc23f | ||
|
|
09a8618fa0 | ||
|
|
52af530d1b | ||
|
|
86a42234c6 | ||
|
|
37c4e78b1d | ||
|
|
9c2c234bee | ||
|
|
59f91267ae | ||
|
|
b3ff9423b4 | ||
|
|
e3f3090341 | ||
|
|
83be6c0864 | ||
|
|
c654951dc1 | ||
|
|
4e177c9ea7 | ||
|
|
dc3a951a1b | ||
|
|
defa9c75eb | ||
|
|
c1f498a114 | ||
|
|
8457c50ebc | ||
|
|
0144cbb99f | ||
|
|
d6e4c7d177 | ||
|
|
3376b16b7b | ||
|
|
673fdcd095 | ||
|
|
0d00520ac1 | ||
|
|
22a0d3a9a5 | ||
|
|
24baa44e70 | ||
|
|
03886f88e8 | ||
|
|
642816b631 | ||
|
|
f3a03c71ec | ||
|
|
1fab246fcd | ||
|
|
cd5c55aad7 | ||
|
|
b066b4b045 | ||
|
|
41d2a2c77e | ||
|
|
69186b916c | ||
|
|
0840695e0a | ||
|
|
8447f105b0 | ||
|
|
c306d8df04 | ||
|
|
beba6490c3 | ||
|
|
fb1078b35b | ||
|
|
be0912e6ca | ||
|
|
13db1aa9b9 | ||
|
|
d2cd2c923d | ||
|
|
c1000d9958 | ||
|
|
2810c044ea | ||
|
|
e09bd160b7 | ||
|
|
e6d1e4697c | ||
|
|
497e5ce73f | ||
|
|
cf7cbae567 | ||
|
|
803f517c62 | ||
|
|
0afa7b8e37 | ||
|
|
1a0f70f8e8 | ||
|
|
8737eb83f0 | ||
|
|
a96239a19f | ||
|
|
2b7cfcd1dc | ||
|
|
ebccb61750 | ||
|
|
285f2aec23 | ||
|
|
217e31fbd7 | ||
|
|
f0ebf7a638 | ||
|
|
c05c5e3f90 | ||
|
|
30f0c5e697 | ||
|
|
f989b8627c | ||
|
|
ff84230566 | ||
|
|
3b4f6276b5 | ||
|
|
2be7a050a4 | ||
|
|
96a612127c | ||
|
|
0bdfb7c9f9 | ||
|
|
f556301249 | ||
|
|
f77b237e59 | ||
|
|
61b7a39aad | ||
|
|
16bd84ee62 | ||
|
|
3d0a97fcdf | ||
|
|
786d156b9f | ||
|
|
629b6fb97d | ||
|
|
17dc32bb79 | ||
|
|
c05e00d32a | ||
|
|
9dc8b2cb47 | ||
|
|
0e32e96c7d | ||
|
|
97afadd7b9 | ||
|
|
b199b65e38 | ||
|
|
999abf3c13 | ||
|
|
2cdde54db6 | ||
|
|
6941603cdd | ||
|
|
cdca4e047d | ||
|
|
31e938de6a | ||
|
|
4c10ab764a | ||
|
|
4e075ab0ca | ||
|
|
f73ae9759f | ||
|
|
f6283b2f7e | ||
|
|
b23d2e4def | ||
|
|
91a2943599 | ||
|
|
e32ce22d24 | ||
|
|
e75c415b46 | ||
|
|
0ab28602d1 | ||
|
|
62c8edff04 | ||
|
|
d01c6268f8 | ||
|
|
e083fd0bcc | ||
|
|
ff687f1bae | ||
|
|
557aaa2480 | ||
|
|
78e43d401d | ||
|
|
7b08c5d287 | ||
|
|
84d2644f19 | ||
|
|
f9a9d7b870 | ||
|
|
d75a8c4bba | ||
|
|
d326c7c990 | ||
|
|
e8e63ca4fc | ||
|
|
6878f2e94b | ||
|
|
a17b416262 | ||
|
|
fed572694f | ||
|
|
fe3d67d937 | ||
|
|
3dcdeb1033 | ||
|
|
0898158c5a | ||
|
|
81091d4bdf | ||
|
|
c9b1b1baa5 | ||
|
|
c81f3bd972 | ||
|
|
9f86b74320 | ||
|
|
2d8390a0f3 | ||
|
|
8d3671175f | ||
|
|
366011a222 | ||
|
|
c0c2e21055 | ||
|
|
63be96a151 | ||
|
|
fc1df4bd99 | ||
|
|
7657330931 | ||
|
|
4e44de3c3b | ||
|
|
8d902a72ca | ||
|
|
7378031a72 | ||
|
|
f898c07c27 | ||
|
|
c8f02d1045 | ||
|
|
4cc13b986b | ||
|
|
af7b6780f3 | ||
|
|
4a17d01004 | ||
|
|
1d68408e21 | ||
|
|
821b1ee660 | ||
|
|
c6616bd467 | ||
|
|
4bb2589f16 | ||
|
|
896c3b3565 | ||
|
|
6ee717a693 | ||
|
|
07ec858e1e | ||
|
|
a7c1e43ebc | ||
|
|
ac1cd3b073 | ||
|
|
7474519faf | ||
|
|
340e474c33 | ||
|
|
c90ab75427 | ||
|
|
848b3f77d3 | ||
|
|
fb8cdb31ca | ||
|
|
ba5f8053fb | ||
|
|
38165becca | ||
|
|
f731ac5c90 | ||
|
|
0291702736 | ||
|
|
ab63929861 | ||
|
|
ca36fb8af3 | ||
|
|
8cb29ff83b | ||
|
|
304fa67dd7 | ||
|
|
a80ec32d2c | ||
|
|
dc4f21162e | ||
|
|
3b8d7e0241 | ||
|
|
6891c68cf1 | ||
|
|
18b26fbc58 | ||
|
|
1db8da6b6b | ||
|
|
14bce7e91d | ||
|
|
2674bc6f23 | ||
|
|
74e1d6f991 | ||
|
|
25dba028f8 | ||
|
|
bf51cc13d1 | ||
|
|
24be36cccb | ||
|
|
797e15c775 | ||
|
|
2395dbcae9 | ||
|
|
efacbbe2b6 | ||
|
|
2518086e46 | ||
|
|
c733247611 | ||
|
|
4f44e71e13 | ||
|
|
02aa46ece8 | ||
|
|
635f64c847 | ||
|
|
0a8516ae9a | ||
|
|
c1edb30472 | ||
|
|
93c78b63ca | ||
|
|
3ccae93757 | ||
|
|
450dec2baf | ||
|
|
36121527ca | ||
|
|
f30451f7e1 | ||
|
|
74d7f63f64 | ||
|
|
350a4ab762 | ||
|
|
0e0e75b89c | ||
|
|
e76440d928 | ||
|
|
0cbace67a5 | ||
|
|
84a3082589 | ||
|
|
cdfcca06d1 | ||
|
|
630bfa4cee | ||
|
|
90cb7beb1f | ||
|
|
d37fa33278 | ||
|
|
eed9c906cc | ||
|
|
2b7ed5f4dd | ||
|
|
6a7a309a2b | ||
|
|
0382069995 | ||
|
|
7c5f40d7d1 | ||
|
|
09e9530660 | ||
|
|
947d25b335 | ||
|
|
80cbc6699a | ||
|
|
da0933fa92 | ||
|
|
9ad217ca55 | ||
|
|
adc9f32fb3 | ||
|
|
65369bdbff | ||
|
|
d18e618ef1 | ||
|
|
970216dd59 | ||
|
|
6840a649c7 | ||
|
|
1a0bc92bde | ||
|
|
e4709ed6fe | ||
|
|
21038212c1 | ||
|
|
b15471b4ff | ||
|
|
19ebc7e562 | ||
|
|
0fa052d2d1 | ||
|
|
402861b9c0 | ||
|
|
aca1666dea | ||
|
|
ff17b60f56 | ||
|
|
3284578445 | ||
|
|
70f6a4877c | ||
|
|
85c5dc8dfb | ||
|
|
4b670a59a3 | ||
|
|
23c113948f | ||
|
|
d86ea20c31 | ||
|
|
3f928cf958 | ||
|
|
01929c2288 | ||
|
|
2634433b2c | ||
|
|
c1f44f76b9 | ||
|
|
b2f27c0a3d | ||
|
|
b1d7cfc81b | ||
|
|
7cd5c66836 | ||
|
|
95cef34234 | ||
|
|
8977440697 | ||
|
|
d5eb84a000 | ||
|
|
81fb6b8794 | ||
|
|
92b390d5bb | ||
|
|
8867532210 | ||
|
|
77db435b4f | ||
|
|
cb2bc17283 | ||
|
|
aaa93239d0 | ||
|
|
93445e22d4 | ||
|
|
64897cfa21 | ||
|
|
7349a29831 | ||
|
|
73554b4759 | ||
|
|
0312f045aa | ||
|
|
1134c1b2ff | ||
|
|
efebbc059a | ||
|
|
6e860d293a | ||
|
|
8bb925fd93 | ||
|
|
ca5d9eba36 | ||
|
|
1a49e24d8a | ||
|
|
24b0ed2502 | ||
|
|
a226d5403a | ||
|
|
0f3085cc73 | ||
|
|
f0a040cf29 | ||
|
|
0676748dde | ||
|
|
589b8384eb | ||
|
|
9e3947f337 | ||
|
|
790cc4e772 | ||
|
|
72f4988632 | ||
|
|
b3fa6bbf32 | ||
|
|
c2dc487c79 | ||
|
|
e74dd3be9e | ||
|
|
1eeee61572 | ||
|
|
984b7a2d0e | ||
|
|
45c714a123 | ||
|
|
96bb97d50a | ||
|
|
046acb9bbf | ||
|
|
ce4ec16c62 | ||
|
|
96c8c9a9a9 | ||
|
|
44db97327d | ||
|
|
f441700c25 | ||
|
|
76b023bdbd | ||
|
|
241d9781bb | ||
|
|
ff94532560 | ||
|
|
e949023321 | ||
|
|
da31407d63 | ||
|
|
8c7c5da89a | ||
|
|
e3a0e1ec5a | ||
|
|
4cee27d8dd | ||
|
|
b0fce66d77 | ||
|
|
bf47ba0145 | ||
|
|
fc7b484c9a | ||
|
|
4402c19d90 | ||
|
|
4e89de9e47 | ||
|
|
a27c9d63d9 | ||
|
|
da6aa0f002 | ||
|
|
0bf3533f45 | ||
|
|
295cb0ef6d | ||
|
|
6fac84e9c2 | ||
|
|
77b2e67c49 | ||
|
|
c48c9b1568 | ||
|
|
6da7eae2f8 | ||
|
|
aec56c48c5 | ||
|
|
c0a9a95e4f | ||
|
|
3b73544766 | ||
|
|
fbc471635a | ||
|
|
3c5b9ed3a6 | ||
|
|
8479122561 | ||
|
|
91c883d5cc | ||
|
|
1cd4da0796 | ||
|
|
935aaccdae | ||
|
|
ab3dd779d2 | ||
|
|
1fbcf6d517 | ||
|
|
82538091c9 | ||
|
|
62ade6113b | ||
|
|
4779bbf415 | ||
|
|
c6b786a771 | ||
|
|
6381227897 | ||
|
|
e82167b5e6 | ||
|
|
dc4fd482b5 | ||
|
|
a71f8b350e | ||
|
|
c9ec8fecef | ||
|
|
e72d32f2d1 | ||
|
|
2545aa2a7e | ||
|
|
51aac15622 | ||
|
|
afa0cfeafb | ||
|
|
19e24ba12c | ||
|
|
c47ce2c730 | ||
|
|
5d00b3dd76 | ||
|
|
4db57cc0dc | ||
|
|
432a9a27f1 | ||
|
|
57bfa3276d | ||
|
|
02fc9b263a | ||
|
|
4c66f81736 | ||
|
|
2de151aebe | ||
|
|
c44911dcac | ||
|
|
bcf2a3d20c | ||
|
|
422ce4a387 | ||
|
|
21387bd76c | ||
|
|
52cd560cb2 | ||
|
|
6c440dfbbb | ||
|
|
a074450452 | ||
|
|
b781cf6f25 | ||
|
|
9b653f488b | ||
|
|
17c580267f | ||
|
|
490d1b8f87 | ||
|
|
b0ad86f16b | ||
|
|
df2b4ac086 | ||
|
|
aee4ececba | ||
|
|
b2992aa6ae | ||
|
|
4c546d281a | ||
|
|
c90e445a67 | ||
|
|
d3c2b0509e | ||
|
|
c6d1c2ca6b | ||
|
|
86dec80726 | ||
|
|
9083976989 | ||
|
|
d9c5d76417 | ||
|
|
c19c3754c6 | ||
|
|
947e0e2369 | ||
|
|
6eca8f2e0c | ||
|
|
9f61ad1941 | ||
|
|
41492a951a | ||
|
|
cbbfec5531 | ||
|
|
d7a4dff080 | ||
|
|
8e56218fbb | ||
|
|
9c40386106 | ||
|
|
c865805f55 | ||
|
|
8e352301cd | ||
|
|
52db1e8bdd | ||
|
|
1947623be8 | ||
|
|
4f8602684d | ||
|
|
75f66e40bf | ||
|
|
73587437cb | ||
|
|
18f6e70bba | ||
|
|
e067b94aca | ||
|
|
b04d5669fb | ||
|
|
ad44af338d | ||
|
|
4f4ddbc3e0 | ||
|
|
4e9af1c713 | ||
|
|
9a94b6f95e | ||
|
|
371b826aa0 | ||
|
|
d3fb90bf6d | ||
|
|
d24c478faa | ||
|
|
48d0aa423c | ||
|
|
5695fa2916 | ||
|
|
bcea1a4b32 | ||
|
|
2f8d4568fc | ||
|
|
5b55318e52 | ||
|
|
459f2ea2c3 | ||
|
|
d26147a34a | ||
|
|
afb9d4f010 | ||
|
|
db3bf9723e | ||
|
|
3a5937b35f | ||
|
|
f5cddda7a8 | ||
|
|
e9fa1d6476 | ||
|
|
084c1881ba | ||
|
|
ce131e4653 | ||
|
|
a121c7d0fe | ||
|
|
603bf99b25 | ||
|
|
9076fe5b46 | ||
|
|
6848f1a38e | ||
|
|
f4103fc917 | ||
|
|
c838da9df4 | ||
|
|
78df4e24b3 | ||
|
|
c0e2d01297 | ||
|
|
313f8b36d2 | ||
|
|
343794c1fe | ||
|
|
813c01aaed | ||
|
|
510ea8a6f6 | ||
|
|
a22fbea467 | ||
|
|
f64144d1da | ||
|
|
c01dd225c9 | ||
|
|
18b408a83a | ||
|
|
efaf107b20 | ||
|
|
86cba5e8a7 | ||
|
|
f4cee6d161 | ||
|
|
28c40b2277 | ||
|
|
8853054ed4 | ||
|
|
3eb44b06af | ||
|
|
1554437884 | ||
|
|
268bae8879 | ||
|
|
b382e436e2 | ||
|
|
6ccd248675 | ||
|
|
c760e63995 | ||
|
|
959dee153e | ||
|
|
aa22f656e8 | ||
|
|
d24734e16f | ||
|
|
01522e8c97 | ||
|
|
14e056551d | ||
|
|
82a42ea183 | ||
|
|
bfde26be79 | ||
|
|
51e43a0567 | ||
|
|
9a375cb3f6 | ||
|
|
2b8f39a149 | ||
|
|
2720a999d1 | ||
|
|
f675bd5017 | ||
|
|
16be3e7a97 | ||
|
|
551d9d1af1 | ||
|
|
0acc8ddb0a | ||
|
|
f0e1bcb899 | ||
|
|
d331c30224 | ||
|
|
d0d39a1c72 | ||
|
|
8904df91b4 | ||
|
|
786365c095 | ||
|
|
eb8f5f523d | ||
|
|
9c22adc97d | ||
|
|
265212cc0f | ||
|
|
9c860cc17c | ||
|
|
7f63153bc1 | ||
|
|
9531d44ab5 | ||
|
|
253abd4930 | ||
|
|
022ba81f2f | ||
|
|
26160e9bbc | ||
|
|
0a32de2c92 | ||
|
|
60aeb6a04b | ||
|
|
0206fc0b8f | ||
|
|
06b3343a6d | ||
|
|
598bec3050 | ||
|
|
0e5d72c22a | ||
|
|
21f4fab9ed | ||
|
|
58e2ec4742 | ||
|
|
6b6867b826 | ||
|
|
92321a7569 | ||
|
|
7781572b6d | ||
|
|
b5efe39eb8 | ||
|
|
cae86cd71f | ||
|
|
667617b480 | ||
|
|
9329311491 | ||
|
|
22438d1eba | ||
|
|
d295084312 | ||
|
|
ab538cd215 | ||
|
|
f9cb438a0b | ||
|
|
354de225ed | ||
|
|
0b7f8d71ed | ||
|
|
9657a13912 | ||
|
|
c5a3b29554 | ||
|
|
ae957c2588 | ||
|
|
da84133732 | ||
|
|
10275eb80f | ||
|
|
29a9c7dffe | ||
|
|
d5b8996a39 | ||
|
|
f012977aac | ||
|
|
8680bd1dee | ||
|
|
528510eb53 | ||
|
|
e8d17cfb7b | ||
|
|
cdbcd20241 | ||
|
|
fef3ade5c6 | ||
|
|
194d7f0da9 | ||
|
|
8ad20c27da | ||
|
|
f891a9f1a7 | ||
|
|
2a335eb7af | ||
|
|
6578ac0121 | ||
|
|
83f9514a42 | ||
|
|
c92f1aa655 | ||
|
|
688993ba37 | ||
|
|
ddfc8d5bc3 | ||
|
|
c3141e3601 | ||
|
|
f49974180b | ||
|
|
eb6ade49f6 | ||
|
|
6553c04256 | ||
|
|
a8cb015a06 | ||
|
|
ee81dca71c | ||
|
|
ce06ddf312 | ||
|
|
fee2d4bc0c | ||
|
|
a2a990bd0c | ||
|
|
cedbe6523d | ||
|
|
ec64055d93 | ||
|
|
6f542ac8b8 | ||
|
|
8a3c101764 | ||
|
|
49a8689090 | ||
|
|
8f5c4997b1 | ||
|
|
900fdd7f6f | ||
|
|
4723416d45 | ||
|
|
8483f71f57 | ||
|
|
5dac594cc9 | ||
|
|
06f4e160d6 | ||
|
|
1d38ee2fcb | ||
|
|
921b70ebaa | ||
|
|
0f56d424da | ||
|
|
4cd43a2b1c | ||
|
|
1ef359e68b | ||
|
|
58ce1e45b8 | ||
|
|
bd854e7f7a | ||
|
|
3ecc0cf906 | ||
|
|
ea6925f905 | ||
|
|
f45b78d8c0 | ||
|
|
75ca8a91f8 | ||
|
|
16462d05c8 | ||
|
|
cbef54f05c | ||
|
|
f601ff4ce3 | ||
|
|
84fbf7392b | ||
|
|
bbf9379f56 | ||
|
|
6aa14fcfb1 | ||
|
|
710c7d0da7 | ||
|
|
841d4eb9c7 | ||
|
|
8c4d35d3db | ||
|
|
4110057fa1 | ||
|
|
9b1d6d2f2f | ||
|
|
1c45455cb2 | ||
|
|
411aca8695 | ||
|
|
3de69021df | ||
|
|
8418038736 | ||
|
|
3bf2b499a7 | ||
|
|
430da2b78d | ||
|
|
51047dcbce | ||
|
|
708bcaa630 | ||
|
|
724b20c826 | ||
|
|
69f8c46f15 | ||
|
|
b7f2fb0bdf | ||
|
|
222ffd8c53 | ||
|
|
00e0a0f79a | ||
|
|
d6bbc4383b | ||
|
|
b51a2602a9 | ||
|
|
c3fbddc671 | ||
|
|
ce262b691a | ||
|
|
65de6fef9b | ||
|
|
31d95ae5b0 | ||
|
|
429098733c | ||
|
|
597899d2bf | ||
|
|
b1c66b181d | ||
|
|
100b836979 | ||
|
|
817907c25a | ||
|
|
25195fffe2 | ||
|
|
0e784a6ffc | ||
|
|
351f96fa8b | ||
|
|
703fb4fb51 | ||
|
|
a975df6f8c | ||
|
|
fb90128b63 | ||
|
|
ea4c91fb37 | ||
|
|
7c1d6973ba | ||
|
|
4f2120b729 | ||
|
|
51f7d74e4a | ||
|
|
32a810fe1d | ||
|
|
570482521a | ||
|
|
3bd2a2f911 | ||
|
|
1ac6d086bd | ||
|
|
8374599183 | ||
|
|
0091c401df | ||
|
|
00dd67e345 | ||
|
|
158677bd54 | ||
|
|
a1b375316c | ||
|
|
dae2814b0f | ||
|
|
341a98848d | ||
|
|
902e66906a | ||
|
|
c59d3cd03f | ||
|
|
ed9f16e012 | ||
|
|
ed77a4aacd | ||
|
|
ff2fa70bd6 | ||
|
|
3519346df9 | ||
|
|
45f6caa7bd | ||
|
|
e2e7755af2 | ||
|
|
4ab7bfaa20 | ||
|
|
7e0ffb43a6 | ||
|
|
526c8328f6 | ||
|
|
81bad4eb25 | ||
|
|
dcaa6e81a3 | ||
|
|
e78155396d | ||
|
|
d3063c8117 | ||
|
|
cd1ed088c3 | ||
|
|
e831debb94 | ||
|
|
8d15a6612f | ||
|
|
9c034f3be6 | ||
|
|
60dd9349a8 | ||
|
|
330b1cbf7d | ||
|
|
9ffa38d54e | ||
|
|
c925727269 | ||
|
|
7a69692999 | ||
|
|
d00d9d98dc | ||
|
|
1846c5c14c | ||
|
|
0d9db783f7 | ||
|
|
55e9cabf51 | ||
|
|
5180f2061a | ||
|
|
05154cf6aa | ||
|
|
f61e955945 |
@@ -5,14 +5,11 @@ labels: type:bug
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
Please make sure to:
|
||||||
|
- Comply with our code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
|
||||||
|
- Search for existing similar issues first: https://github.com/cryptomator/cryptomator/issues?q=
|
||||||
|
|
||||||
Do you want to ask a QUESTION? Are you looking for SUPPORT?
|
⚠️ IMPORTANT: If you don't stick to this template, the issue will get closed.
|
||||||
We're happy to help you via our support channels! Please read: https://github.com/cryptomator/cryptomator/blob/develop/SUPPORT.md
|
|
||||||
|
|
||||||
By filing an issue, you are expected to comply with our code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
|
|
||||||
|
|
||||||
Of course, we also expect you to search for existing similar issues first! ;) https://github.com/cryptomator/cryptomator/issues?q=
|
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Description
|
### Description
|
||||||
@@ -21,9 +18,9 @@ Of course, we also expect you to search for existing similar issues first! ;) ht
|
|||||||
|
|
||||||
### System Setup
|
### System Setup
|
||||||
|
|
||||||
* Operating system and version: [Windows/macOS/Linux + Version]
|
* Operating system and version: [Windows/macOS/Linux + Version ( + Desktop Environment, if Linux)]
|
||||||
* Cryptomator version: [Shown in the settings]
|
* Cryptomator version: [Shown in the settings]
|
||||||
* Drive: [Dokany/FUSE/WebDAV]
|
* Volume type: [Dokany/FUSE/WebDAV, shown in the settings]
|
||||||
|
|
||||||
### Steps to Reproduce
|
### Steps to Reproduce
|
||||||
|
|
||||||
@@ -48,7 +45,6 @@ Of course, we also expect you to search for existing similar issues first! ;) ht
|
|||||||
[Any additional information, log files, screenshots, configuration, or data that might be necessary to reproduce the issue.]
|
[Any additional information, log files, screenshots, configuration, or data that might be necessary to reproduce the issue.]
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
If you want to add the log file or screenshots, please add them as attachments. If your log file seems empty and doesn't show any errors, you may enable the debug mode first. Here is how to do that: https://community.cryptomator.org/t/how-do-i-enable-debug-mode/36
|
If you want to add the log file or screenshots, please add them as attachments. If your log file seems empty and doesn't show any errors, you may enable the debug mode first. Here is how to do that: https://community.cryptomator.org/t/how-do-i-enable-debug-mode/36
|
||||||
|
|
||||||
Then reproduce the problem to ensure all important information is contained in there. You may use test data or redact sensitive information from the log file.
|
Then reproduce the problem to ensure all important information is contained in there. You may use test data or redact sensitive information from the log file.
|
||||||
@@ -57,5 +53,4 @@ Log file location:
|
|||||||
- Windows: %appdata%/Cryptomator
|
- Windows: %appdata%/Cryptomator
|
||||||
- macOS: ~/Library/Logs/Cryptomator
|
- macOS: ~/Library/Logs/Cryptomator
|
||||||
- Linux: ~/.local/share/Cryptomator/logs
|
- Linux: ~/.local/share/Cryptomator/logs
|
||||||
|
-->
|
||||||
-->
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Help & Support
|
||||||
|
url: https://community.cryptomator.org/
|
||||||
|
about: You will find answers in our community forum
|
||||||
|
- name: User Manual
|
||||||
|
url: https://docs.cryptomator.org/
|
||||||
|
about: Read the Cryptomator documentation here
|
||||||
@@ -5,14 +5,9 @@ labels: type:feature-request
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
Please make sure to:
|
||||||
Do you want to ask a QUESTION? Are you looking for SUPPORT?
|
- Comply with our code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
|
||||||
We're happy to help you via our support channels! Please read: https://github.com/cryptomator/cryptomator/blob/develop/SUPPORT.md
|
- Search for existing similar issues first: https://github.com/cryptomator/cryptomator/issues?q=
|
||||||
|
|
||||||
By filing a feature request, you are expected to comply with our code of conduct: https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md
|
|
||||||
|
|
||||||
Of course, we also expect you to search for existing similar feature requests first! ;)
|
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
@@ -29,4 +24,4 @@ Of course, we also expect you to search for existing similar feature requests fi
|
|||||||
|
|
||||||
### Additional Context
|
### Additional Context
|
||||||
|
|
||||||
[Add any other context or screenshots about the feature request here.]
|
[Add any other context or screenshots about the feature request here.]
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
name: Build and Test
|
name: Build and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]')"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-java@v1
|
- uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 14
|
java-version: 16
|
||||||
- uses: actions/cache@v1
|
- uses: actions/cache@v1
|
||||||
with:
|
with:
|
||||||
path: ~/.m2/repository
|
path: ~/.m2/repository
|
||||||
@@ -24,27 +25,31 @@ jobs:
|
|||||||
- name: Build and Test
|
- name: Build and Test
|
||||||
run: mvn -B install --file main/pom.xml -Pcoverage
|
run: mvn -B install --file main/pom.xml -Pcoverage
|
||||||
- name: Run Codacy Coverage Reporter
|
- name: Run Codacy Coverage Reporter
|
||||||
|
if: github.repository == 'cryptomator/cryptomator'
|
||||||
run: |
|
run: |
|
||||||
curl -o ~/codacy-coverage-reporter.jar https://repo.maven.apache.org/maven2/com/codacy/codacy-coverage-reporter/7.1.0/codacy-coverage-reporter-7.1.0-assembly.jar
|
curl -o ~/codacy-coverage-reporter.jar https://repo.maven.apache.org/maven2/com/codacy/codacy-coverage-reporter/7.1.0/codacy-coverage-reporter-7.1.0-assembly.jar
|
||||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar report -l Java -r main/commons/target/site/jacoco/jacoco.xml --partial
|
$JAVA_HOME/bin/java --illegal-access=permit -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 --illegal-access=permit -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 --illegal-access=permit -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 --illegal-access=permit -jar ~/codacy-coverage-reporter.jar final
|
||||||
$JAVA_HOME/bin/java -jar ~/codacy-coverage-reporter.jar final
|
|
||||||
env:
|
env:
|
||||||
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
||||||
- name: Assemble Buildkit
|
- name: Assemble buildkit-linux.zip
|
||||||
run: mvn -B package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease
|
run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,linux
|
||||||
- name: Upload buildkit-linux.zip
|
- name: Upload buildkit-linux.zip
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: buildkit-linux.zip
|
name: buildkit-linux.zip
|
||||||
path: main/buildkit/target/buildkit-linux.zip
|
path: main/buildkit/target/buildkit-linux.zip
|
||||||
|
- name: Assemble buildkit-mac.zip
|
||||||
|
run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,mac
|
||||||
- name: Upload buildkit-mac.zip
|
- name: Upload buildkit-mac.zip
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: buildkit-mac.zip
|
name: buildkit-mac.zip
|
||||||
path: main/buildkit/target/buildkit-mac.zip
|
path: main/buildkit/target/buildkit-mac.zip
|
||||||
|
- name: Assemble buildkit-win.zip
|
||||||
|
run: mvn -B clean package -DskipTests --file main/pom.xml --resume-from=buildkit -Prelease,windows
|
||||||
- name: Upload buildkit-win.zip
|
- name: Upload buildkit-win.zip
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
@@ -55,7 +60,7 @@ jobs:
|
|||||||
name: Draft a Release on GitHub Releases
|
name: Draft a Release on GitHub Releases
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build
|
needs: build
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'cryptomator/cryptomator'
|
||||||
steps:
|
steps:
|
||||||
- name: Download buildkit-linux.zip
|
- name: Download buildkit-linux.zip
|
||||||
uses: actions/download-artifact@v1
|
uses: actions/download-artifact@v1
|
||||||
@@ -82,6 +87,21 @@ jobs:
|
|||||||
release_name: ${{ github.ref }}
|
release_name: ${{ github.ref }}
|
||||||
body: |
|
body: |
|
||||||
:construction: Work in Progress
|
:construction: Work in Progress
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
* [ ] add Linux appimage, zsync file and signature file
|
||||||
|
* [ ] add Windows installer and signature file
|
||||||
|
* [ ] add MacOs disk image and signature file
|
||||||
|
|
||||||
|
## What's new
|
||||||
|
|
||||||
|
## Bugfixes
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
:scroll: A complete list of closed issues is available [here](LINK)
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: false
|
prerelease: false
|
||||||
- name: Upload buildkit-linux.zip to GitHub Releases
|
- name: Upload buildkit-linux.zip to GitHub Releases
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: Bug Report Triage
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
closeTemplateViolation:
|
||||||
|
name: Validate bug report against issue template
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: contains(github.event.issue.labels.*.name, 'type:bug')
|
||||||
|
steps:
|
||||||
|
- name: Check "Description"
|
||||||
|
if: |
|
||||||
|
!contains(github.event.issue.body, env.MUST_CONTAIN)
|
||||||
|
|| contains(toJson(github.event.issue.body), env.MUST_NOT_CONTAIN)
|
||||||
|
run: exit 1
|
||||||
|
env:
|
||||||
|
MUST_CONTAIN: '### Description'
|
||||||
|
MUST_NOT_CONTAIN: '### Description\r\n\r\n[Summarize your problem.]\r\n\r\n### System Setup'
|
||||||
|
- name: Check "Steps to Reproduce"
|
||||||
|
if: |
|
||||||
|
!contains(github.event.issue.body, env.MUST_CONTAIN)
|
||||||
|
|| contains(toJson(github.event.issue.body), env.MUST_NOT_CONTAIN)
|
||||||
|
run: exit 1
|
||||||
|
env:
|
||||||
|
MUST_CONTAIN: '### Steps to Reproduce'
|
||||||
|
MUST_NOT_CONTAIN: '### Steps to Reproduce\r\n\r\n1. [First step]\r\n2. [Second step]\r\n3. [and so on…]\r\n\r\n#### Expected Behavior'
|
||||||
|
- name: Close issue if one of the checks failed
|
||||||
|
if: ${{ failure() }}
|
||||||
|
uses: peter-evans/close-issue@v1
|
||||||
|
with:
|
||||||
|
comment: |
|
||||||
|
This bug report did ignore our issue template. 😞
|
||||||
|
Auto-closing this issue, since it is most likely not useful.
|
||||||
|
|
||||||
|
_This decision was made by a bot. If you think the bot is wrong, let us know and we'll reopen this issue._
|
||||||
@@ -18,5 +18,9 @@ pom.xml.versionsBackup
|
|||||||
.idea/**/workspace.xml
|
.idea/**/workspace.xml
|
||||||
.idea/**/tasks.xml
|
.idea/**/tasks.xml
|
||||||
.idea/dictionaries
|
.idea/dictionaries
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/encodings.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/uiDesigner.xml
|
||||||
.idea/**/libraries/
|
.idea/**/libraries/
|
||||||
*.iml
|
*.iml
|
||||||
Generated
+28
-1
@@ -9,14 +9,36 @@
|
|||||||
<option name="RIGHT_MARGIN" value="220" />
|
<option name="RIGHT_MARGIN" value="220" />
|
||||||
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
||||||
<JavaCodeStyleSettings>
|
<JavaCodeStyleSettings>
|
||||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="30" />
|
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
|
||||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="10" />
|
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="10" />
|
||||||
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||||
<value />
|
<value />
|
||||||
</option>
|
</option>
|
||||||
|
<option name="IMPORT_LAYOUT_TABLE">
|
||||||
|
<value>
|
||||||
|
<package name="" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="javax" withSubpackages="true" static="false" />
|
||||||
|
<package name="javafx" withSubpackages="true" static="false" />
|
||||||
|
<package name="java" withSubpackages="true" static="false" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="" withSubpackages="true" static="true" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
|
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
|
||||||
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
|
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
|
||||||
</JavaCodeStyleSettings>
|
</JavaCodeStyleSettings>
|
||||||
|
<Properties>
|
||||||
|
<option name="KEEP_BLANK_LINES" value="true" />
|
||||||
|
</Properties>
|
||||||
|
<XML>
|
||||||
|
<option name="XML_ATTRIBUTE_WRAP" value="0" />
|
||||||
|
</XML>
|
||||||
|
<codeStyleSettings language="CSS">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
<codeStyleSettings language="Groovy">
|
<codeStyleSettings language="Groovy">
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="USE_TAB_CHARACTER" value="true" />
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
@@ -30,12 +52,17 @@
|
|||||||
<codeStyleSettings language="JAVA">
|
<codeStyleSettings language="JAVA">
|
||||||
<option name="KEEP_LINE_BREAKS" value="false" />
|
<option name="KEEP_LINE_BREAKS" value="false" />
|
||||||
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
|
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
|
||||||
|
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||||
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
|
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
|
||||||
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||||
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="ENUM_CONSTANTS_WRAP" value="2" />
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="USE_TAB_CHARACTER" value="true" />
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
</indentOptions>
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules />
|
||||||
|
</arrangement>
|
||||||
</codeStyleSettings>
|
</codeStyleSettings>
|
||||||
<codeStyleSettings language="JSON">
|
<codeStyleSettings language="JSON">
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
|
|||||||
Generated
-41
@@ -1,41 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CompilerConfiguration">
|
|
||||||
<annotationProcessing>
|
|
||||||
<profile name="Annotation profile for Cryptomator" enabled="true">
|
|
||||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
|
||||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
|
||||||
<outputRelativeToContentRoot value="true" />
|
|
||||||
<processorPath useClasspath="false">
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.27/dagger-compiler-2.27.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.27/dagger-2.27.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.27/dagger-producers-2.27.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/27.1-jre/guava-27.1-jre.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.1/jsr305-3.0.1.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/2.5.2/checker-qual-2.5.2.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.2.0/error_prone_annotations-2.2.0.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.3/checker-compat-qual-2.5.3.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.27/dagger-spi-2.27.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.11.1/javapoet-1.11.1.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.3.50/kotlin-stdlib-1.3.50.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.3.50/kotlin-stdlib-common-1.3.50.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.1.0/kotlinx-metadata-jvm-0.1.0.jar" />
|
|
||||||
</processorPath>
|
|
||||||
<module name="keychain" />
|
|
||||||
<module name="launcher" />
|
|
||||||
<module name="commons" />
|
|
||||||
<module name="ui" />
|
|
||||||
</profile>
|
|
||||||
</annotationProcessing>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-13
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
|
||||||
<file url="file://$PROJECT_DIR$/main" charset="UTF-8" />
|
|
||||||
<file url="file://$PROJECT_DIR$/main/commons/src/main/java" charset="UTF-8" />
|
|
||||||
<file url="file://$PROJECT_DIR$/main/keychain/src/main/java" charset="UTF-8" />
|
|
||||||
<file url="file://$PROJECT_DIR$/main/keychain/src/main/resources" charset="UTF-8" />
|
|
||||||
<file url="file://$PROJECT_DIR$/main/launcher/src/main/java" charset="UTF-8" />
|
|
||||||
<file url="file://$PROJECT_DIR$/main/ui/src/main/java" charset="UTF-8" />
|
|
||||||
<file url="file://$PROJECT_DIR$/main/ui/src/main/resources" charset="UTF-8" />
|
|
||||||
<file url="PROJECT" charset="UTF-8" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-30
@@ -1,30 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RemoteRepositoriesConfiguration">
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="bintray" />
|
|
||||||
<option name="name" value="Bintray JCenter" />
|
|
||||||
<option name="url" value="https://jcenter.bintray.com" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="central" />
|
|
||||||
<option name="name" value="Maven Central Repository" />
|
|
||||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="central" />
|
|
||||||
<option name="name" value="Maven Central repository" />
|
|
||||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="jcenter" />
|
|
||||||
<option name="name" value="jcenter" />
|
|
||||||
<option name="url" value="https://jcenter.bintray.com" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="jboss.community" />
|
|
||||||
<option name="name" value="JBoss Community repository" />
|
|
||||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
|
||||||
</remote-repository>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_14" default="false" project-jdk-name="14" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_16" project-jdk-name="16" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Cryptomator Linux" type="Application" factoryName="Application">
|
<configuration default="false" name="Cryptomator Linux" 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="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/.config/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/.local/share/Cryptomator/logs" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" -Xss20m -Xmx512m" />
|
<option name="VM_PARAMETERS" value="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/.config/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/.local/share/Cryptomator/logs" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" -Dcryptomator.showTrayIcon=true -Xss20m -Xmx512m" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Cryptomator Linux Dev" type="Application" factoryName="Application">
|
||||||
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
|
<module name="launcher" />
|
||||||
|
<option name="VM_PARAMETERS" value="-Djdk.gtk.version=2 -Duser.language=en -Dcryptomator.settingsPath="~/.config/Cryptomator-Dev/settings.json" -Dcryptomator.ipcPortPath="~/.config/Cryptomator-Dev/ipcPort.bin" -Dcryptomator.logDir="~/.local/share/Cryptomator-Dev/logs" -Dcryptomator.mountPointsDir="~/.local/share/Cryptomator-Dev/mnt" -Dcryptomator.showTrayIcon=true -Dfuse.experimental="true" -Xss20m -Xmx512m" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
|
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
|
||||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
<module name="launcher" />
|
<module name="launcher" />
|
||||||
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" -Xss2m -Xmx512m" />
|
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" -Dcryptomator.mountPointsDir="~/Cryptomator" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
|||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
|
||||||
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
|
<module name="launcher" />
|
||||||
|
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator-Dev/settings.json" -Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator-Dev/ipcPort.bin" -Dcryptomator.logDir="~/AppData/Roaming/Cryptomator-Dev" -Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator-Dev/keychain.json" -Dcryptomator.mountPointsDir="~/Cryptomator-Dev" -Dfuse.experimental="true" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
+1
-2
@@ -5,8 +5,7 @@
|
|||||||
</envs>
|
</envs>
|
||||||
<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="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.showTrayIcon=true -Xss2m -Xmx512m -ea" />
|
||||||
<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>
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Cryptomator macOS Dev" type="Application" factoryName="Application">
|
||||||
|
<envs>
|
||||||
|
<env name="LD_LIBRARY_PATH" value="/usr/local/lib" />
|
||||||
|
</envs>
|
||||||
|
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||||
|
<module name="launcher" />
|
||||||
|
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator-Dev/settings.json" -Dcryptomator.ipcPortPath="~/Library/Application Support/Cryptomator-Dev/ipcPort.bin" -Dcryptomator.logDir="~/Library/Logs/Cryptomator-Dev" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m -ea" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
|
[](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
|
||||||
[](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/gh/cryptomator/cryptomator/dashboard)
|
||||||
[](http://twitter.com/Cryptomator)
|
[](http://twitter.com/Cryptomator)
|
||||||
[](https://translate.cryptomator.org/)
|
[](https://translate.cryptomator.org/)
|
||||||
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
||||||
@@ -17,11 +17,26 @@ Cryptomator is provided free of charge as an open-source project despite the hig
|
|||||||
|
|
||||||
### Gold Sponsors
|
### Gold Sponsors
|
||||||
|
|
||||||
[<img src="https://cryptomator.org/img/sponsors/geewhiz.svg" alt="gee-whiz" height="96">](https://www.gee-whiz.de/)
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://www.gee-whiz.de/"><img src="https://cryptomator.org/img/sponsors/geewhiz.svg" alt="gee-whiz" height="80"></a></td>
|
||||||
|
<td><a href="https://proxy-hub.com/"><img src="https://cryptomator.org/img/sponsors/proxyhub.svg" alt="Proxy-Hub" height="80"></a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
### Silver Sponsors
|
### Silver Sponsors
|
||||||
|
|
||||||
[](https://thebestvpn.com/)
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><a href="https://thebestvpn.com/"><img src="https://cryptomator.org/img/sponsors/thebestvpn@2x.png" alt="TheBestVPN" height="64"></a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
- [Jameson Lopp](https://www.lopp.net/)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -33,7 +48,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Works with Dropbox, Google Drive, OneDrive, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
|
- Works with Dropbox, Google Drive, OneDrive, MEGA, pCloud, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
|
||||||
- Open Source means: No backdoors, control is better than trust
|
- Open Source means: No backdoors, control is better than trust
|
||||||
- Client-side: No accounts, no data shared with any online service
|
- Client-side: No accounts, no data shared with any online service
|
||||||
- Totally transparent: Just work on the virtual drive as if it were a USB flash drive
|
- Totally transparent: Just work on the virtual drive as if it were a USB flash drive
|
||||||
@@ -65,7 +80,7 @@ For more information on the security details visit [cryptomator.org](https://doc
|
|||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
* JDK 14 (e.g. adoptopenjdk)
|
* JDK 16 (e.g. adoptopenjdk)
|
||||||
* Maven 3
|
* Maven 3
|
||||||
* Optional: OS-dependent build tools for native packaging (see [Windows](https://github.com/cryptomator/cryptomator-win), [OS X](https://github.com/cryptomator/cryptomator-osx), [Linux](https://github.com/cryptomator/builder-containers))
|
* Optional: OS-dependent build tools for native packaging (see [Windows](https://github.com/cryptomator/cryptomator-win), [OS X](https://github.com/cryptomator/cryptomator-osx), [Linux](https://github.com/cryptomator/builder-containers))
|
||||||
|
|
||||||
@@ -73,7 +88,9 @@ For more information on the security details visit [cryptomator.org](https://doc
|
|||||||
|
|
||||||
```
|
```
|
||||||
cd main
|
cd main
|
||||||
mvn clean install -Prelease
|
mvn clean install -Prelease,windows
|
||||||
|
# or mvn clean install -Prelease,mac
|
||||||
|
# or mvn clean install -Prelease,linux
|
||||||
```
|
```
|
||||||
|
|
||||||
This will build all the jars and bundle them together with their OS-specific dependencies under `main/buildkit/target`. This can now be used to build native packages.
|
This will build all the jars and bundle them together with their OS-specific dependencies under `main/buildkit/target`. This can now be used to build native packages.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||||
<id>tarball</id>
|
<id>tarball</id>
|
||||||
<includeBaseDirectory>false</includeBaseDirectory>
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
@@ -14,13 +14,6 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>target/</directory>
|
|
||||||
<includes>
|
|
||||||
<include>ffi-version.txt</include>
|
|
||||||
</includes>
|
|
||||||
<outputDirectory>libs</outputDirectory>
|
|
||||||
</fileSet>
|
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target/</directory>
|
<directory>target/</directory>
|
||||||
<includes>
|
<includes>
|
||||||
@@ -43,12 +36,5 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>target/linux-libs</directory>
|
|
||||||
<includes>
|
|
||||||
<include>*.jar</include>
|
|
||||||
</includes>
|
|
||||||
<outputDirectory>libs</outputDirectory>
|
|
||||||
</fileSet>
|
|
||||||
</fileSets>
|
</fileSets>
|
||||||
</assembly>
|
</assembly>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||||
<id>tarball</id>
|
<id>tarball</id>
|
||||||
<includeBaseDirectory>false</includeBaseDirectory>
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
@@ -14,13 +14,6 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>target/</directory>
|
|
||||||
<includes>
|
|
||||||
<include>ffi-version.txt</include>
|
|
||||||
</includes>
|
|
||||||
<outputDirectory>libs</outputDirectory>
|
|
||||||
</fileSet>
|
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target/</directory>
|
<directory>target/</directory>
|
||||||
<includes>
|
<includes>
|
||||||
@@ -43,12 +36,5 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>target/mac-libs</directory>
|
|
||||||
<includes>
|
|
||||||
<include>*.jar</include>
|
|
||||||
</includes>
|
|
||||||
<outputDirectory>libs</outputDirectory>
|
|
||||||
</fileSet>
|
|
||||||
</fileSets>
|
</fileSets>
|
||||||
</assembly>
|
</assembly>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||||
<id>tarball</id>
|
<id>tarball</id>
|
||||||
<includeBaseDirectory>false</includeBaseDirectory>
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
@@ -14,13 +14,6 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>target/</directory>
|
|
||||||
<includes>
|
|
||||||
<include>ffi-version.txt</include>
|
|
||||||
</includes>
|
|
||||||
<outputDirectory>libs</outputDirectory>
|
|
||||||
</fileSet>
|
|
||||||
<fileSet>
|
<fileSet>
|
||||||
<directory>target/</directory>
|
<directory>target/</directory>
|
||||||
<includes>
|
<includes>
|
||||||
@@ -43,12 +36,5 @@
|
|||||||
</includes>
|
</includes>
|
||||||
<outputDirectory>libs</outputDirectory>
|
<outputDirectory>libs</outputDirectory>
|
||||||
</fileSet>
|
</fileSet>
|
||||||
<fileSet>
|
|
||||||
<directory>target/win-libs</directory>
|
|
||||||
<includes>
|
|
||||||
<include>*.jar</include>
|
|
||||||
</includes>
|
|
||||||
<outputDirectory>libs</outputDirectory>
|
|
||||||
</fileSet>
|
|
||||||
</fileSets>
|
</fileSets>
|
||||||
</assembly>
|
</assembly>
|
||||||
+144
-102
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.6</version>
|
<version>1.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>buildkit</artifactId>
|
<artifactId>buildkit</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
@@ -24,7 +24,6 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-resources-plugin</artifactId>
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
<version>3.1.0</version>
|
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>copy-resources</id>
|
<id>copy-resources</id>
|
||||||
@@ -55,8 +54,8 @@
|
|||||||
|
|
||||||
<!-- copy libraries to target/libs/: -->
|
<!-- copy libraries to target/libs/: -->
|
||||||
<plugin>
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-dependency-plugin</artifactId>
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
<version>3.1.1</version>
|
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>copy-libs</id>
|
<id>copy-libs</id>
|
||||||
@@ -65,110 +64,153 @@
|
|||||||
<goal>copy-dependencies</goal>
|
<goal>copy-dependencies</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
<includeScope>runtime</includeScope>
|
||||||
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||||
<excludeClassifiers>linux,mac,win</excludeClassifiers>
|
<excludeClassifiers>linux,mac,win</excludeClassifiers>
|
||||||
<excludeArtifactIds>dbus-java,secret-service,hkdf,java-utils</excludeArtifactIds>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>copy-linux-libs</id>
|
|
||||||
<phase>prepare-package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>copy-dependencies</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<outputDirectory>${project.build.directory}/linux-libs</outputDirectory>
|
|
||||||
<includeGroupIds>org.openjfx</includeGroupIds>
|
|
||||||
<classifier>linux</classifier>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>copy-linux-secret-service</id>
|
|
||||||
<phase>prepare-package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>copy-dependencies</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<outputDirectory>${project.build.directory}/linux-libs</outputDirectory>
|
|
||||||
<includeArtifactIds>dbus-java,secret-service,hkdf,java-utils</includeArtifactIds>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>copy-mac-libs</id>
|
|
||||||
<phase>prepare-package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>copy-dependencies</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<outputDirectory>${project.build.directory}/mac-libs</outputDirectory>
|
|
||||||
<includeGroupIds>org.openjfx</includeGroupIds>
|
|
||||||
<classifier>mac</classifier>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>copy-win-libs</id>
|
|
||||||
<phase>prepare-package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>copy-dependencies</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<outputDirectory>${project.build.directory}/win-libs</outputDirectory>
|
|
||||||
<includeGroupIds>org.openjfx</includeGroupIds>
|
|
||||||
<classifier>win</classifier>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<!-- create buildkit.zip: -->
|
|
||||||
<plugin>
|
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
|
||||||
<version>3.1.1</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>assemble-linux</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>single</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<descriptors>
|
|
||||||
<descriptor>assembly-linux.xml</descriptor>
|
|
||||||
</descriptors>
|
|
||||||
<appendAssemblyId>false</appendAssemblyId>
|
|
||||||
<finalName>buildkit-linux</finalName>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>assemble-mac</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>single</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<descriptors>
|
|
||||||
<descriptor>assembly-mac.xml</descriptor>
|
|
||||||
</descriptors>
|
|
||||||
<appendAssemblyId>false</appendAssemblyId>
|
|
||||||
<finalName>buildkit-mac</finalName>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>assemble-win</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>single</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<descriptors>
|
|
||||||
<descriptor>assembly-win.xml</descriptor>
|
|
||||||
</descriptors>
|
|
||||||
<appendAssemblyId>false</appendAssemblyId>
|
|
||||||
<finalName>buildkit-win</finalName>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>linux</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>assemble-linux</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>assembly-linux.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
|
<finalName>buildkit-linux</finalName>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-linux-libs</id>
|
||||||
|
<phase>prepare-package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||||
|
<includeGroupIds>org.openjfx</includeGroupIds>
|
||||||
|
<classifier>linux</classifier>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>mac</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>assemble-mac</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>assembly-mac.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
|
<finalName>buildkit-mac</finalName>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-mac-libs</id>
|
||||||
|
<phase>prepare-package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||||
|
<includeGroupIds>org.openjfx</includeGroupIds>
|
||||||
|
<classifier>mac</classifier>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
|
||||||
|
<profile>
|
||||||
|
<id>windows</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>assemble-win</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>assembly-win.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
|
<finalName>buildkit-win</finalName>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-win-libs</id>
|
||||||
|
<phase>prepare-package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}/libs</outputDirectory>
|
||||||
|
<includeGroupIds>org.openjfx</includeGroupIds>
|
||||||
|
<classifier>win</classifier>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
</project>
|
</project>
|
||||||
@@ -1 +0,0 @@
|
|||||||
${cryptomator.jni.version}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
cd $(dirname $0)
|
||||||
java \
|
java \
|
||||||
-cp "libs/*" \
|
-cp "libs/*" \
|
||||||
-Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" \
|
-Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" \
|
||||||
@@ -6,6 +7,6 @@ java \
|
|||||||
-Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \
|
-Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \
|
||||||
-Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \
|
-Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \
|
||||||
-Djdk.gtk.version=2 \
|
-Djdk.gtk.version=2 \
|
||||||
-Xss20m \
|
-Xss2m \
|
||||||
-Xmx512m \
|
-Xmx512m \
|
||||||
org.cryptomator.launcher.Cryptomator
|
org.cryptomator.launcher.Cryptomator
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
cd $(dirname $0)
|
||||||
java \
|
java \
|
||||||
-cp "libs/*" \
|
-cp "libs/*" \
|
||||||
-Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" \
|
-Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" \
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ java ^
|
|||||||
-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" ^
|
-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" ^
|
||||||
-Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" ^
|
-Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" ^
|
||||||
-Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" ^
|
-Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" ^
|
||||||
|
-Dcryptomator.mountPointsDir="~/Cryptomator" ^
|
||||||
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
|
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
|
||||||
-Xss20m ^
|
-Xss20m ^
|
||||||
-Xmx512m ^
|
-Xmx512m ^
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.6</version>
|
<version>1.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
<name>Cryptomator Commons</name>
|
<name>Cryptomator Commons</name>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>jni</artifactId>
|
<artifactId>integrations-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JavaFx -->
|
<!-- JavaFx -->
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<!-- EasyBind -->
|
<!-- EasyBind -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.fxmisc.easybind</groupId>
|
<groupId>com.tobiasdiez</groupId>
|
||||||
<artifactId>easybind</artifactId>
|
<artifactId>easybind</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|||||||
@@ -5,25 +5,29 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common;
|
package org.cryptomator.common;
|
||||||
|
|
||||||
|
import com.tobiasdiez.easybind.EasyBind;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import javafx.beans.binding.Binding;
|
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.keychain.KeychainModule;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.SettingsProvider;
|
import org.cryptomator.common.settings.SettingsProvider;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultComponent;
|
import org.cryptomator.common.vaults.VaultComponent;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
|
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import javafx.beans.binding.Binding;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@@ -33,7 +37,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@Module(subcomponents = {VaultComponent.class})
|
@Module(subcomponents = {VaultComponent.class}, includes = {KeychainModule.class})
|
||||||
public abstract class CommonsModule {
|
public abstract class CommonsModule {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
|
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
|
||||||
@@ -46,10 +50,28 @@ public abstract class CommonsModule {
|
|||||||
@Named("licensePublicKey")
|
@Named("licensePublicKey")
|
||||||
static String provideLicensePublicKey() {
|
static String provideLicensePublicKey() {
|
||||||
// in PEM format without the dash-escaped begin/end lines
|
// in PEM format without the dash-escaped begin/end lines
|
||||||
return "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB7NfnqiZbg2KTmoflmZ71PbXru7oW" //
|
return """
|
||||||
+ "fmnV2yv3eDjlDfGruBrqz9TtXBZV/eYWt31xu1osIqaT12lKBvZ511aaAkIBeOEV" //
|
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB7NfnqiZbg2KTmoflmZ71PbXru7oW\
|
||||||
+ "gwcBIlJr6kUw7NKzeJt7r2rrsOyQoOG2nWc/Of/NBqA3mIZRHk5Aq1YupFdD26QE" //
|
fmnV2yv3eDjlDfGruBrqz9TtXBZV/eYWt31xu1osIqaT12lKBvZ511aaAkIBeOEV\
|
||||||
+ "r0DzRyj4ixPIt38CQB8=";
|
gwcBIlJr6kUw7NKzeJt7r2rrsOyQoOG2nWc/Of/NBqA3mIZRHk5Aq1YupFdD26QE\
|
||||||
|
r0DzRyj4ixPIt38CQB8=\
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static SecureRandom provideCSPRNG() {
|
||||||
|
try {
|
||||||
|
return SecureRandom.getInstanceStrong();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException("A strong algorithm must exist in every Java platform.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static MasterkeyFileAccess provideMasterkeyFileAccess(SecureRandom csprng) {
|
||||||
|
return new MasterkeyFileAccess(Constants.PEPPER, csprng);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -3,5 +3,8 @@ package org.cryptomator.common;
|
|||||||
public interface Constants {
|
public interface Constants {
|
||||||
|
|
||||||
String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
||||||
|
String MASTERKEY_BACKUP_SUFFIX = ".bkup";
|
||||||
|
String VAULTCONFIG_FILENAME = "vault.cryptomator";
|
||||||
|
byte[] PEPPER = new byte[0];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,13 @@ import java.util.stream.StreamSupport;
|
|||||||
public class Environment {
|
public class Environment {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Environment.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Environment.class);
|
||||||
private static final String USER_HOME = System.getProperty("user.home");
|
|
||||||
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
|
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
|
||||||
private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME);
|
|
||||||
private static final char PATH_LIST_SEP = ':';
|
private static final char PATH_LIST_SEP = ':';
|
||||||
private static final int DEFAULT_MIN_PW_LENGTH = 8;
|
private static final int DEFAULT_MIN_PW_LENGTH = 8;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Environment() {
|
public Environment() {
|
||||||
|
LOG.debug("user.home: {}", System.getProperty("user.home"));
|
||||||
LOG.debug("java.library.path: {}", System.getProperty("java.library.path"));
|
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"));
|
||||||
@@ -40,6 +39,8 @@ public class Environment {
|
|||||||
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
||||||
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
|
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
|
||||||
LOG.debug("cryptomator.buildNumber: {}", System.getProperty("cryptomator.buildNumber"));
|
LOG.debug("cryptomator.buildNumber: {}", System.getProperty("cryptomator.buildNumber"));
|
||||||
|
LOG.debug("cryptomator.showTrayIcon: {}", System.getProperty("cryptomator.showTrayIcon"));
|
||||||
|
LOG.debug("fuse.experimental: {}", Boolean.getBoolean("fuse.experimental"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean useCustomLogbackConfig() {
|
public boolean useCustomLogbackConfig() {
|
||||||
@@ -74,6 +75,15 @@ public class Environment {
|
|||||||
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
|
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean showTrayIcon() {
|
||||||
|
return Boolean.getBoolean("cryptomator.showTrayIcon");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated // TODO: remove as soon as custom mount path works properly on Win+Fuse
|
||||||
|
public boolean useExperimentalFuse() {
|
||||||
|
return Boolean.getBoolean("fuse.experimental");
|
||||||
|
}
|
||||||
|
|
||||||
private int getInt(String propertyName, int defaultValue) {
|
private int getInt(String propertyName, int defaultValue) {
|
||||||
String value = System.getProperty(propertyName);
|
String value = System.getProperty(propertyName);
|
||||||
try {
|
try {
|
||||||
@@ -87,8 +97,13 @@ public class Environment {
|
|||||||
String value = System.getProperty(propertyName);
|
String value = System.getProperty(propertyName);
|
||||||
return Optional.ofNullable(value).map(Paths::get);
|
return Optional.ofNullable(value).map(Paths::get);
|
||||||
}
|
}
|
||||||
// visible for testing
|
|
||||||
|
|
||||||
|
// visible for testing
|
||||||
|
Path getHomeDir() {
|
||||||
|
return getPath("user.home").orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// visible for testing
|
||||||
Stream<Path> getPaths(String propertyName) {
|
Stream<Path> getPaths(String propertyName) {
|
||||||
Stream<String> rawSettingsPaths = getRawList(propertyName, PATH_LIST_SEP);
|
Stream<String> rawSettingsPaths = getRawList(propertyName, PATH_LIST_SEP);
|
||||||
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Paths::get).map(this::replaceHomeDir);
|
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Paths::get).map(this::replaceHomeDir);
|
||||||
@@ -96,7 +111,7 @@ public class Environment {
|
|||||||
|
|
||||||
private Path replaceHomeDir(Path path) {
|
private Path replaceHomeDir(Path path) {
|
||||||
if (path.startsWith(RELATIVE_HOME_DIR)) {
|
if (path.startsWith(RELATIVE_HOME_DIR)) {
|
||||||
return ABSOLUTE_HOME_DIR.resolve(RELATIVE_HOME_DIR.relativize(path));
|
return getHomeDir().resolve(RELATIVE_HOME_DIR.relativize(path));
|
||||||
} else {
|
} else {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2019 Skymatic GmbH.
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.common;
|
|
||||||
|
|
||||||
import dagger.Module;
|
|
||||||
import dagger.Provides;
|
|
||||||
import org.cryptomator.jni.JniFunctions;
|
|
||||||
import org.cryptomator.jni.MacFunctions;
|
|
||||||
import org.cryptomator.jni.WinFunctions;
|
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Module
|
|
||||||
public class JniModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
Optional<MacFunctions> provideOptionalMacFunctions() {
|
|
||||||
return JniFunctions.macFunctions();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
Optional<WinFunctions> provideOptionalWinFunctions() {
|
|
||||||
return JniFunctions.winFunctions();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,79 +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.common;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.function.UnaryOperator;
|
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
|
||||||
|
|
||||||
public final class LazyInitializer {
|
|
||||||
|
|
||||||
private LazyInitializer() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as {@link #initializeLazily(AtomicReference, SupplierThrowingException, Class)} except that no checked exception may be thrown by the factory function.
|
|
||||||
*
|
|
||||||
* @param <T> Type of the value
|
|
||||||
* @param reference A reference to a maybe not yet initialized value.
|
|
||||||
* @param factory A factory providing a value for the reference, if it doesn't exist yet. The factory may be invoked multiple times, but only one result will survive.
|
|
||||||
* @return The initialized value
|
|
||||||
*/
|
|
||||||
public static <T> T initializeLazily(AtomicReference<T> reference, Supplier<T> factory) {
|
|
||||||
SupplierThrowingException<T, RuntimeException> factoryThrowingRuntimeExceptions = () -> factory.get();
|
|
||||||
return initializeLazily(reference, factoryThrowingRuntimeExceptions, RuntimeException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509
|
|
||||||
*
|
|
||||||
* @param <T> Type of the value
|
|
||||||
* @param <E> Type of the any expected exception that may occur during initialization
|
|
||||||
* @param reference A reference to a maybe not yet initialized value.
|
|
||||||
* @param factory A factory providing a value for the reference, if it doesn't exist yet. The factory may be invoked multiple times, but only one result will survive.
|
|
||||||
* @param exceptionType Expected exception type.
|
|
||||||
* @return The initialized value
|
|
||||||
* @throws E Exception thrown by the factory function.
|
|
||||||
*/
|
|
||||||
public static <T, E extends Exception> T initializeLazily(AtomicReference<T> reference, SupplierThrowingException<T, E> factory, Class<E> exceptionType) throws E {
|
|
||||||
final T existing = reference.get();
|
|
||||||
if (existing != null) {
|
|
||||||
return existing;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
return reference.updateAndGet(invokeFactoryIfNull(factory));
|
|
||||||
} catch (InitializationException e) {
|
|
||||||
Throwables.throwIfUnchecked(e.getCause());
|
|
||||||
Throwables.throwIfInstanceOf(e.getCause(), exceptionType);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T, E extends Exception> UnaryOperator<T> invokeFactoryIfNull(SupplierThrowingException<T, E> factory) throws InitializationException {
|
|
||||||
return currentValue -> {
|
|
||||||
if (currentValue == null) {
|
|
||||||
try {
|
|
||||||
return factory.get();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new InitializationException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return currentValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class InitializationException extends RuntimeException {
|
|
||||||
|
|
||||||
public InitializationException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -33,8 +33,8 @@ class LicenseChecker {
|
|||||||
try {
|
try {
|
||||||
byte[] keyBytes = BaseEncoding.base64().decode(pemEncodedPublicKey);
|
byte[] keyBytes = BaseEncoding.base64().decode(pemEncodedPublicKey);
|
||||||
PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(keyBytes));
|
PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(keyBytes));
|
||||||
if (key instanceof ECPublicKey) {
|
if (key instanceof ECPublicKey k) {
|
||||||
return (ECPublicKey) key;
|
return k;
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Key not an EC public key.");
|
throw new IllegalStateException("Key not an EC public key.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
package org.cryptomator.common;
|
package org.cryptomator.common;
|
||||||
|
|
||||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.binding.StringBinding;
|
import javafx.beans.binding.StringBinding;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import org.cryptomator.common.settings.Settings;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common;
|
package org.cryptomator.common;
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares version strings according to <a href="http://semver.org/spec/v2.0.0.html">SemVer 2.0.0</a>.
|
* Compares version strings according to <a href="http://semver.org/spec/v2.0.0.html">SemVer 2.0.0</a>.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+36
-22
@@ -1,60 +1,76 @@
|
|||||||
package org.cryptomator.keychain;
|
package org.cryptomator.common.keychain;
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||||
|
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.ObjectExpression;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class KeychainManager implements KeychainAccessStrategy {
|
@Singleton
|
||||||
|
public class KeychainManager implements KeychainAccessProvider {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(KeychainManager.class);
|
private final ObjectExpression<KeychainAccessProvider> keychain;
|
||||||
|
private final LoadingCache<String, BooleanProperty> passphraseStoredProperties;
|
||||||
|
|
||||||
private final KeychainAccessStrategy keychain;
|
@Inject
|
||||||
private LoadingCache<String, BooleanProperty> passphraseStoredProperties;
|
KeychainManager(ObjectExpression<KeychainAccessProvider> selectedKeychain) {
|
||||||
|
this.keychain = selectedKeychain;
|
||||||
KeychainManager(KeychainAccessStrategy keychain) {
|
|
||||||
assert keychain.isSupported();
|
|
||||||
this.keychain = keychain;
|
|
||||||
this.passphraseStoredProperties = CacheBuilder.newBuilder() //
|
this.passphraseStoredProperties = CacheBuilder.newBuilder() //
|
||||||
.weakValues() //
|
.weakValues() //
|
||||||
.build(CacheLoader.from(this::createStoredPassphraseProperty));
|
.build(CacheLoader.from(this::createStoredPassphraseProperty));
|
||||||
|
keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeychainAccessProvider getKeychainOrFail() throws KeychainAccessException {
|
||||||
|
var result = keychain.getValue();
|
||||||
|
if (result == null) {
|
||||||
|
throw new NoKeychainAccessProviderException();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||||
keychain.storePassphrase(key, passphrase);
|
getKeychainOrFail().storePassphrase(key, passphrase);
|
||||||
setPassphraseStored(key, true);
|
setPassphraseStored(key, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public char[] loadPassphrase(String key) throws KeychainAccessException {
|
public char[] loadPassphrase(String key) throws KeychainAccessException {
|
||||||
char[] passphrase = keychain.loadPassphrase(key);
|
char[] passphrase = getKeychainOrFail().loadPassphrase(key);
|
||||||
setPassphraseStored(key, passphrase != null);
|
setPassphraseStored(key, passphrase != null);
|
||||||
return passphrase;
|
return passphrase;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deletePassphrase(String key) throws KeychainAccessException {
|
public void deletePassphrase(String key) throws KeychainAccessException {
|
||||||
keychain.deletePassphrase(key);
|
getKeychainOrFail().deletePassphrase(key);
|
||||||
setPassphraseStored(key, false);
|
setPassphraseStored(key, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||||
keychain.changePassphrase(key, passphrase);
|
getKeychainOrFail().changePassphrase(key, passphrase);
|
||||||
setPassphraseStored(key, true);
|
setPassphraseStored(key, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported() {
|
public boolean isSupported() {
|
||||||
return true;
|
return keychain.getValue() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocked() {
|
||||||
|
return keychain.getValue() == null || keychain.get().isLocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,7 +85,7 @@ public class KeychainManager implements KeychainAccessStrategy {
|
|||||||
public boolean isPassphraseStored(String key) throws KeychainAccessException {
|
public boolean isPassphraseStored(String key) throws KeychainAccessException {
|
||||||
char[] storedPw = null;
|
char[] storedPw = null;
|
||||||
try {
|
try {
|
||||||
storedPw = keychain.loadPassphrase(key);
|
storedPw = getKeychainOrFail().loadPassphrase(key);
|
||||||
return storedPw != null;
|
return storedPw != null;
|
||||||
} finally {
|
} finally {
|
||||||
if (storedPw != null) {
|
if (storedPw != null) {
|
||||||
@@ -77,14 +93,13 @@ public class KeychainManager implements KeychainAccessStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPassphraseStored(String key, boolean value) {
|
private void setPassphraseStored(String key, boolean value) {
|
||||||
BooleanProperty property = passphraseStoredProperties.getIfPresent(key);
|
BooleanProperty property = passphraseStoredProperties.getIfPresent(key);
|
||||||
if (property != null) {
|
if (property != null) {
|
||||||
if (Platform.isFxApplicationThread()) {
|
if (Platform.isFxApplicationThread()) {
|
||||||
property.set(value);
|
property.set(value);
|
||||||
} else {
|
} else {
|
||||||
LOG.warn("");
|
|
||||||
Platform.runLater(() -> property.set(value));
|
Platform.runLater(() -> property.set(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +114,7 @@ public class KeychainManager implements KeychainAccessStrategy {
|
|||||||
*
|
*
|
||||||
* @param key The key to look up
|
* @param key The key to look up
|
||||||
* @return An observable property which is <code>true</code> when it almost certain that a password for <code>key</code> is stored.
|
* @return An observable property which is <code>true</code> when it almost certain that a password for <code>key</code> is stored.
|
||||||
* @see #isPassphraseStored(String)
|
* @see #isPassphraseStored(String)
|
||||||
*/
|
*/
|
||||||
public ReadOnlyBooleanProperty getPassphraseStoredProperty(String key) {
|
public ReadOnlyBooleanProperty getPassphraseStoredProperty(String key) {
|
||||||
return passphraseStoredProperties.getUnchecked(key);
|
return passphraseStoredProperties.getUnchecked(key);
|
||||||
@@ -107,7 +122,6 @@ public class KeychainManager implements KeychainAccessStrategy {
|
|||||||
|
|
||||||
private BooleanProperty createStoredPassphraseProperty(String key) {
|
private BooleanProperty createStoredPassphraseProperty(String key) {
|
||||||
try {
|
try {
|
||||||
LOG.warn("LOAD"); // TODO remove
|
|
||||||
return new SimpleBooleanProperty(isPassphraseStored(key));
|
return new SimpleBooleanProperty(isPassphraseStored(key));
|
||||||
} catch (KeychainAccessException e) {
|
} catch (KeychainAccessException e) {
|
||||||
return new SimpleBooleanProperty(false);
|
return new SimpleBooleanProperty(false);
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.cryptomator.common.keychain;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.ObjectExpression;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class KeychainModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static Set<ServiceLoader.Provider<KeychainAccessProvider>> provideAvailableKeychainAccessProviderFactories() {
|
||||||
|
return ServiceLoader.load(KeychainAccessProvider.class).stream().collect(Collectors.toUnmodifiableSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static Set<KeychainAccessProvider> provideSupportedKeychainAccessProviders(Set<ServiceLoader.Provider<KeychainAccessProvider>> availableFactories) {
|
||||||
|
return availableFactories.stream() //
|
||||||
|
.map(ServiceLoader.Provider::get) //
|
||||||
|
.filter(KeychainAccessProvider::isSupported) //
|
||||||
|
.collect(Collectors.toUnmodifiableSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
static ObjectExpression<KeychainAccessProvider> provideKeychainAccessProvider(Settings settings, Set<KeychainAccessProvider> providers) {
|
||||||
|
return Bindings.createObjectBinding(() -> {
|
||||||
|
var selectedProviderClass = settings.keychainBackend().get().getProviderClass();
|
||||||
|
var selectedProvider = providers.stream().filter(provider -> provider.getClass().getName().equals(selectedProviderClass)).findAny();
|
||||||
|
var fallbackProvider = providers.stream().findAny().orElse(null);
|
||||||
|
return selectedProvider.orElse(fallbackProvider);
|
||||||
|
}, settings.keychainBackend());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package org.cryptomator.common.keychain;
|
||||||
|
|
||||||
|
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown by {@link KeychainManager} if attempted to access a keychain despite no supported keychain access provider being available.
|
||||||
|
*/
|
||||||
|
public class NoKeychainAccessProviderException extends KeychainAccessException {
|
||||||
|
|
||||||
|
public NoKeychainAccessProviderException() {
|
||||||
|
super("Did not find any supported keychain access provider.");
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
package org.cryptomator.common.mountpoint;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.vaults.Volume;
|
||||||
|
import org.cryptomator.common.vaults.WindowsDriveLetters;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
class AvailableDriveLetterChooser implements MountPointChooser {
|
||||||
|
|
||||||
|
private final WindowsDriveLetters windowsDriveLetters;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AvailableDriveLetterChooser(WindowsDriveLetters windowsDriveLetters) {
|
||||||
|
this.windowsDriveLetters = windowsDriveLetters;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isApplicable(Volume caller) {
|
||||||
|
return SystemUtils.IS_OS_WINDOWS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||||
|
return this.windowsDriveLetters.getAvailableDriveLetterPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package org.cryptomator.common.mountpoint;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.common.vaults.Volume;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
class CustomDriveLetterChooser implements MountPointChooser {
|
||||||
|
|
||||||
|
private final VaultSettings vaultSettings;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CustomDriveLetterChooser(VaultSettings vaultSettings) {
|
||||||
|
this.vaultSettings = vaultSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isApplicable(Volume caller) {
|
||||||
|
return SystemUtils.IS_OS_WINDOWS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||||
|
return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get);
|
||||||
|
}
|
||||||
|
}
|
||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
package org.cryptomator.common.mountpoint;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
|
import org.cryptomator.common.vaults.Volume;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.FileAlreadyExistsException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.LinkOption;
|
||||||
|
import java.nio.file.NotDirectoryException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
class CustomMountPointChooser implements MountPointChooser {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class);
|
||||||
|
|
||||||
|
private final VaultSettings vaultSettings;
|
||||||
|
private final Environment environment;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CustomMountPointChooser(VaultSettings vaultSettings, Environment environment) {
|
||||||
|
this.vaultSettings = vaultSettings;
|
||||||
|
this.environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isApplicable(Volume caller) {
|
||||||
|
//Disable if useExperimentalFuse is required (Win + Fuse), but set to false
|
||||||
|
return caller.getImplementationType() != VolumeImpl.FUSE || !SystemUtils.IS_OS_WINDOWS || environment.useExperimentalFuse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||||
|
//VaultSettings#getCustomMountPath already checks whether the saved custom mountpoint should be used
|
||||||
|
return this.vaultSettings.getCustomMountPath().map(Paths::get);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
||||||
|
switch (caller.getMountPointRequirement()) {
|
||||||
|
case PARENT_NO_MOUNT_POINT -> prepareParentNoMountPoint(mountPoint);
|
||||||
|
case EMPTY_MOUNT_POINT -> prepareEmptyMountPoint(mountPoint);
|
||||||
|
case NONE -> {
|
||||||
|
//Requirement "NONE" doesn't make any sense here.
|
||||||
|
//No need to prepare/verify a Mountpoint without requiring one...
|
||||||
|
throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
//Currently the case for "PARENT_OPT_MOUNT_POINT"
|
||||||
|
throw new InvalidMountPointException(new IllegalStateException("Not implemented"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException {
|
||||||
|
//This the case on Windows when using FUSE
|
||||||
|
//See https://github.com/billziss-gh/winfsp/issues/320
|
||||||
|
Path parent = mountPoint.getParent();
|
||||||
|
if (!Files.isDirectory(parent)) {
|
||||||
|
throw new InvalidMountPointException(new NotDirectoryException(parent.toString()));
|
||||||
|
}
|
||||||
|
//We must use #notExists() here because notExists =/= !exists (see docs)
|
||||||
|
if (!Files.notExists(mountPoint, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
|
//File exists OR can't be determined
|
||||||
|
throw new InvalidMountPointException(new FileAlreadyExistsException(mountPoint.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareEmptyMountPoint(Path mountPoint) throws InvalidMountPointException {
|
||||||
|
//This is the case for Windows when using Dokany and for Linux and Mac
|
||||||
|
if (!Files.isDirectory(mountPoint)) {
|
||||||
|
throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString()));
|
||||||
|
}
|
||||||
|
try (DirectoryStream<Path> ds = Files.newDirectoryStream(mountPoint)) {
|
||||||
|
if (ds.iterator().hasNext()) {
|
||||||
|
throw new InvalidMountPointException(new DirectoryNotEmptyException(mountPoint.toString()));
|
||||||
|
}
|
||||||
|
} catch (IOException exception) {
|
||||||
|
throw new InvalidMountPointException("IOException while checking folder content", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package org.cryptomator.common.mountpoint;
|
||||||
|
|
||||||
|
public class InvalidMountPointException extends Exception {
|
||||||
|
|
||||||
|
public InvalidMountPointException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidMountPointException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidMountPointException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
package org.cryptomator.common.mountpoint;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.common.vaults.Volume;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
class MacVolumeMountChooser implements MountPointChooser {
|
||||||
|
|
||||||
|
private static final Path VOLUME_PATH = Path.of("/Volumes");
|
||||||
|
|
||||||
|
private final VaultSettings vaultSettings;
|
||||||
|
private final MountPointHelper helper;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MacVolumeMountChooser(VaultSettings vaultSettings, MountPointHelper helper) {
|
||||||
|
this.vaultSettings = vaultSettings;
|
||||||
|
this.helper = helper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isApplicable(Volume caller) {
|
||||||
|
return SystemUtils.IS_OS_MAC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||||
|
return Optional.of(helper.chooseTemporaryMountPoint(vaultSettings, VOLUME_PATH));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean prepare(Volume caller, Path mountPoint) {
|
||||||
|
// https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592:
|
||||||
|
// In order to allow non-admin users to mount FUSE volumes in `/Volumes`,
|
||||||
|
// starting with version 3.5.0, FUSE will create non-existent mount points automatically.
|
||||||
|
// Therefore we don't need to prepare anything.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package org.cryptomator.common.mountpoint;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import org.cryptomator.common.vaults.Volume;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base interface for the Mountpoint-Choosing-Operation that results in the choice and
|
||||||
|
* preparation of a mountpoint or an exception otherwise.<br>
|
||||||
|
* <p>All <i>MountPointChoosers (MPCs)</i> need to implement this class and must be added to
|
||||||
|
* the pool of possible MPCs by the {@link MountPointChooserModule MountPointChooserModule.}
|
||||||
|
* The MountPointChooserModule will sort them according to their {@link #getPriority() priority.}
|
||||||
|
* The priority must be defined by the developer to reflect a useful execution order.<br>
|
||||||
|
* A specific priority <b>must not</b> be assigned to more than one MPC at a time;
|
||||||
|
* the result of having two MPCs with equal priority is undefined.
|
||||||
|
*
|
||||||
|
* <p>MPCs are executed by a {@link Volume} in ascending order of their priority
|
||||||
|
* (smaller priorities are tried first) to find and prepare a suitable mountpoint for the volume.
|
||||||
|
* The volume has access to a {@link SortedSet} of MPCs in this specific order,
|
||||||
|
* that is provided by the Module. The Set contains all available Choosers, even if they
|
||||||
|
* are not {@link #isApplicable(Volume) applicable} for the Vault/Volume. The Volume must
|
||||||
|
* check whether a MPC is applicable by invoking {@code #isApplicable(Volume)} on it
|
||||||
|
* <i>before</i> calling {@code #chooseMountPoint(Volume)}.
|
||||||
|
*
|
||||||
|
* <p>At execution of a MPC {@link #chooseMountPoint(Volume)} is called to choose a mountpoint
|
||||||
|
* according to the MPC's <i>strategy.</i> The <i>strategy</i> can involve reading configs,
|
||||||
|
* searching the filesystem, processing user-input or similar operations.
|
||||||
|
* If {@code #chooseMountPoint(Volume)} returns a non-null path (everything but
|
||||||
|
* {@linkplain Optional#empty()}) the MPC's {@link #prepare(Volume, Path)} method is called and the
|
||||||
|
* MountPoint is verified and/or prepared. In this case <i>no other MPC's will be called for
|
||||||
|
* this volume, even if {@code #prepare(Volume, Path)} fails.</i>
|
||||||
|
*
|
||||||
|
* <p>If {@code #chooseMountPoint(Volume)} yields no result, the next MPC is executed
|
||||||
|
* <i>without</i> first calling the {@code #prepare(Volume, Path)} method of the current MPC.
|
||||||
|
* This is repeated until<br>
|
||||||
|
* <ul>
|
||||||
|
* <li><b>either</b> a mountpoint is returned by {@code #chooseMountPoint(Volume)}
|
||||||
|
* and {@code #prepare(Volume, Path)} succeeds or fails, ending the entire operation</li>
|
||||||
|
* <li><b>or</b> no MPC remains and an {@link InvalidMountPointException} is thrown.</li>
|
||||||
|
* </ul>
|
||||||
|
* If the {@code #prepare(Volume, Path)} method of a MPC fails, the entire
|
||||||
|
* Mountpoint-Choosing-Operation is aborted and the method should do all necessary cleanup
|
||||||
|
* before throwing the exception.
|
||||||
|
* If the preparation succeeds {@link #cleanup(Volume, Path)} can be used after unmount to do any
|
||||||
|
* remaining cleanup.
|
||||||
|
*/
|
||||||
|
public interface MountPointChooser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the {@link Volume} to determine whether this MountPointChooser is
|
||||||
|
* applicable for mounting the Vault/Volume, especially with regard to the
|
||||||
|
* current system configuration and particularities of the Volume type.
|
||||||
|
*
|
||||||
|
* <p>Developers should override this method to check for system configurations
|
||||||
|
* that are unsuitable for this MPC.
|
||||||
|
*
|
||||||
|
* @param caller The Volume that is calling the method to determine applicability of the MPC
|
||||||
|
* @return a boolean flag; true if applicable, else false.
|
||||||
|
* @see #chooseMountPoint(Volume)
|
||||||
|
*/
|
||||||
|
boolean isApplicable(Volume caller);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by a {@link Volume} to choose a mountpoint according to the
|
||||||
|
* MountPointChoosers strategy.
|
||||||
|
*
|
||||||
|
* <p>This method must only be called for MPCs that were deemed
|
||||||
|
* {@link #isApplicable(Volume) applicable} by the {@link Volume Volume.}
|
||||||
|
* Developers should override this method to find or extract a mountpoint for
|
||||||
|
* the volume <b>without</b> preparing it. Preparation should be done by
|
||||||
|
* {@link #prepare(Volume, Path)} instead.
|
||||||
|
* Exceptions in this method should be handled gracefully and result in returning
|
||||||
|
* {@link Optional#empty()} instead of throwing an exception.
|
||||||
|
*
|
||||||
|
* @param caller The Volume that is calling the method to choose a mountpoint
|
||||||
|
* @return the chosen path or {@link Optional#empty()} if an exception occurred
|
||||||
|
* or no mountpoint could be found.
|
||||||
|
* @see #isApplicable(Volume)
|
||||||
|
* @see #prepare(Volume, Path)
|
||||||
|
*/
|
||||||
|
Optional<Path> chooseMountPoint(Volume caller);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by a {@link Volume} to prepare and/or verify the chosen mountpoint.<br>
|
||||||
|
* This method is only called if the {@link #chooseMountPoint(Volume)} method
|
||||||
|
* of the same MountPointChooser returned a path.
|
||||||
|
*
|
||||||
|
* <p>Developers should override this method to prepare the mountpoint for
|
||||||
|
* the volume and check for any obstacles that could hinder the mount operation.
|
||||||
|
* The mountpoint is deemed "prepared" if it can be used to mount a volume
|
||||||
|
* without any further filesystem actions or user interaction. If this is not possible,
|
||||||
|
* this method should fail. In other words: This method should not return without
|
||||||
|
* either failing or finalizing the preparation of the mountpoint.
|
||||||
|
* Generally speaking exceptions should be wrapped as
|
||||||
|
* {@link InvalidMountPointException} to allow efficient handling by the caller.
|
||||||
|
*
|
||||||
|
* <p>Often the preparation of a mountpoint involves creating files or others
|
||||||
|
* actions that require cleaning up after the volume is unmounted.
|
||||||
|
* In this case developers should override the {@link #cleanup(Volume, Path)}
|
||||||
|
* method and return {@code true} to the volume to indicate that the
|
||||||
|
* {@code #cleanup} method of this MPC should be called after unmount.
|
||||||
|
*
|
||||||
|
* <p><b>Please note:</b> If this method fails the entire
|
||||||
|
* Mountpoint-Choosing-Operation is aborted without calling
|
||||||
|
* {@link #cleanup(Volume, Path)} or any other MPCs. Therefore this method should
|
||||||
|
* do all necessary cleanup before throwing the exception.
|
||||||
|
*
|
||||||
|
* @param caller The Volume that is calling the method to prepare a mountpoint
|
||||||
|
* @param mountPoint the mountpoint chosen by {@link #chooseMountPoint(Volume)}
|
||||||
|
* @return a boolean flag; true if cleanup is needed, false otherwise
|
||||||
|
* @throws InvalidMountPointException if the preparation fails
|
||||||
|
* @see #chooseMountPoint(Volume)
|
||||||
|
* @see #cleanup(Volume, Path)
|
||||||
|
*/
|
||||||
|
default boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
||||||
|
return false; //NO-OP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by a {@link Volume} to do any cleanup needed after unmount.
|
||||||
|
*
|
||||||
|
* <p>This method is only called if the {@link #prepare(Volume, Path)} method
|
||||||
|
* of the same MountPointChooser returned {@code true}. Typically developers want to
|
||||||
|
* delete any files created prior to mount or do similar tasks.<br>
|
||||||
|
* Exceptions in this method should be handled gracefully.
|
||||||
|
*
|
||||||
|
* @param caller The Volume that is calling the method to cleanup the prepared mountpoint
|
||||||
|
* @param mountPoint the mountpoint that was prepared by {@link #prepare(Volume, Path)}
|
||||||
|
* @see #prepare(Volume, Path)
|
||||||
|
*/
|
||||||
|
default void cleanup(Volume caller, Path mountPoint) {
|
||||||
|
//NO-OP
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package org.cryptomator.common.mountpoint;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import dagger.multibindings.IntKey;
|
||||||
|
import dagger.multibindings.IntoMap;
|
||||||
|
import org.cryptomator.common.vaults.PerVault;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.SortedMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dagger-Module for {@link MountPointChooser MountPointChoosers.}<br>
|
||||||
|
* See there for additional information.
|
||||||
|
*
|
||||||
|
* @see MountPointChooser
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
public abstract class MountPointChooserModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@IntKey(0)
|
||||||
|
@PerVault
|
||||||
|
public abstract MountPointChooser bindCustomMountPointChooser(CustomMountPointChooser chooser);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@IntKey(100)
|
||||||
|
@PerVault
|
||||||
|
public abstract MountPointChooser bindCustomDriveLetterChooser(CustomDriveLetterChooser chooser);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@IntKey(101)
|
||||||
|
@PerVault
|
||||||
|
public abstract MountPointChooser bindMacVolumeMountChooser(MacVolumeMountChooser chooser);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@IntKey(200)
|
||||||
|
@PerVault
|
||||||
|
public abstract MountPointChooser bindAvailableDriveLetterChooser(AvailableDriveLetterChooser chooser);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@IntKey(999)
|
||||||
|
@PerVault
|
||||||
|
public abstract MountPointChooser bindTemporaryMountPointChooser(TemporaryMountPointChooser chooser);
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@PerVault
|
||||||
|
@Named("orderedMountPointChoosers")
|
||||||
|
public static Iterable<MountPointChooser> provideOrderedMountPointChoosers(Map<Integer, MountPointChooser> choosers) {
|
||||||
|
SortedMap<Integer, MountPointChooser> sortedChoosers = new TreeMap<>(choosers);
|
||||||
|
return Iterables.unmodifiableIterable(sortedChoosers.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package org.cryptomator.common.mountpoint;
|
||||||
|
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.LinkOption;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class MountPointHelper {
|
||||||
|
|
||||||
|
public static Logger LOG = LoggerFactory.getLogger(MountPointHelper.class);
|
||||||
|
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
|
||||||
|
|
||||||
|
private final Optional<Path> tmpMountPointDir;
|
||||||
|
private volatile boolean unmountDebrisCleared = false;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MountPointHelper(Environment env) {
|
||||||
|
this.tmpMountPointDir = env.getMountPointsDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path chooseTemporaryMountPoint(VaultSettings vaultSettings, Path parentDir) {
|
||||||
|
String basename = vaultSettings.mountName().get();
|
||||||
|
//regular
|
||||||
|
Path mountPoint = parentDir.resolve(basename);
|
||||||
|
if (Files.notExists(mountPoint)) {
|
||||||
|
return mountPoint;
|
||||||
|
}
|
||||||
|
//with id
|
||||||
|
mountPoint = parentDir.resolve(basename + " (" + vaultSettings.getId() + ")");
|
||||||
|
if (Files.notExists(mountPoint)) {
|
||||||
|
return mountPoint;
|
||||||
|
}
|
||||||
|
//with id and count
|
||||||
|
for (int i = 1; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
|
||||||
|
mountPoint = parentDir.resolve(basename + "_(" + vaultSettings.getId() + ")_" + i);
|
||||||
|
if (Files.notExists(mountPoint)) {
|
||||||
|
return mountPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.error("Failed to find feasible mountpoint at {}{}{}_x. Giving up after {} attempts.", parentDir, File.separator, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void clearIrregularUnmountDebrisIfNeeded() {
|
||||||
|
if (unmountDebrisCleared || tmpMountPointDir.isEmpty()) {
|
||||||
|
return; // nothing to do
|
||||||
|
}
|
||||||
|
if (Files.exists(tmpMountPointDir.get(), LinkOption.NOFOLLOW_LINKS)) {
|
||||||
|
clearIrregularUnmountDebris(tmpMountPointDir.get());
|
||||||
|
}
|
||||||
|
unmountDebrisCleared = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearIrregularUnmountDebris(Path dirContainingMountPoints) {
|
||||||
|
IOException cleanupFailed = new IOException("Cleanup failed");
|
||||||
|
|
||||||
|
try {
|
||||||
|
LOG.debug("Performing cleanup of mountpoint dir {}.", dirContainingMountPoints);
|
||||||
|
for (Path p : Files.newDirectoryStream(dirContainingMountPoints)) {
|
||||||
|
try {
|
||||||
|
var attr = Files.readAttributes(p, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
|
||||||
|
if (attr.isOther() && attr.isDirectory()) { // yes, this is possible with windows junction points -.-
|
||||||
|
Files.delete(p);
|
||||||
|
} else if (attr.isDirectory()) {
|
||||||
|
deleteEmptyDir(p);
|
||||||
|
} else if (attr.isSymbolicLink()) {
|
||||||
|
deleteDeadLink(p);
|
||||||
|
} else {
|
||||||
|
LOG.debug("Found non-directory element in mountpoint dir: {}", p);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
cleanupFailed.addSuppressed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanupFailed.getSuppressed().length > 0) {
|
||||||
|
throw cleanupFailed;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Unable to perform cleanup of mountpoint dir {}.", dirContainingMountPoints, e);
|
||||||
|
} finally {
|
||||||
|
unmountDebrisCleared = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteEmptyDir(Path dir) throws IOException {
|
||||||
|
assert Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS);
|
||||||
|
try {
|
||||||
|
ensureIsEmpty(dir);
|
||||||
|
Files.delete(dir); // attempt to delete dir non-recursively (will fail, if there are contents)
|
||||||
|
} catch (DirectoryNotEmptyException e) {
|
||||||
|
LOG.info("Found non-empty directory in mountpoint dir: {}", dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteDeadLink(Path symlink) throws IOException {
|
||||||
|
assert Files.isSymbolicLink(symlink);
|
||||||
|
if (Files.notExists(symlink)) { // following link: target does not exist
|
||||||
|
Files.delete(symlink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureIsEmpty(Path dir) throws IOException {
|
||||||
|
if (Files.newDirectoryStream(dir).iterator().hasNext()) {
|
||||||
|
throw new DirectoryNotEmptyException(dir.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+87
@@ -0,0 +1,87 @@
|
|||||||
|
package org.cryptomator.common.mountpoint;
|
||||||
|
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.common.vaults.Volume;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
class TemporaryMountPointChooser implements MountPointChooser {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(TemporaryMountPointChooser.class);
|
||||||
|
|
||||||
|
private final VaultSettings vaultSettings;
|
||||||
|
private final Environment environment;
|
||||||
|
private final MountPointHelper helper;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment, MountPointHelper helper) {
|
||||||
|
this.vaultSettings = vaultSettings;
|
||||||
|
this.environment = environment;
|
||||||
|
this.helper = helper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isApplicable(Volume caller) {
|
||||||
|
if (this.environment.getMountPointsDir().isEmpty()) {
|
||||||
|
LOG.warn("\"cryptomator.mountPointsDir\" is not set to a valid path!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Path> chooseMountPoint(Volume caller) {
|
||||||
|
assert environment.getMountPointsDir().isPresent();
|
||||||
|
//clean leftovers of not-regularly unmounted vaults
|
||||||
|
//see https://github.com/cryptomator/cryptomator/issues/1013 and https://github.com/cryptomator/cryptomator/issues/1061
|
||||||
|
helper.clearIrregularUnmountDebrisIfNeeded();
|
||||||
|
return this.environment.getMountPointsDir().map(dir -> this.helper.chooseTemporaryMountPoint(this.vaultSettings, dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException {
|
||||||
|
try {
|
||||||
|
switch (caller.getMountPointRequirement()) {
|
||||||
|
case PARENT_NO_MOUNT_POINT -> {
|
||||||
|
Files.createDirectories(mountPoint.getParent());
|
||||||
|
LOG.debug("Successfully created folder for mount point: {}", mountPoint);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case EMPTY_MOUNT_POINT -> {
|
||||||
|
Files.createDirectories(mountPoint);
|
||||||
|
LOG.debug("Successfully created mount point: {}", mountPoint);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case NONE -> {
|
||||||
|
//Requirement "NONE" doesn't make any sense here.
|
||||||
|
//No need to prepare/verify a Mountpoint without requiring one...
|
||||||
|
throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement"));
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
//Currently the case for "PARENT_OPT_MOUNT_POINT"
|
||||||
|
throw new InvalidMountPointException(new IllegalStateException("Not implemented"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException exception) {
|
||||||
|
throw new InvalidMountPointException("IOException while preparing mountpoint", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup(Volume caller, Path mountPoint) {
|
||||||
|
try {
|
||||||
|
Files.delete(mountPoint);
|
||||||
|
LOG.debug("Successfully deleted mount point: {}", mountPoint);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Could not delete mount point: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
public enum KeychainBackend {
|
||||||
|
GNOME("org.cryptomator.linux.keychain.SecretServiceKeychainAccess"),
|
||||||
|
KDE("org.cryptomator.linux.keychain.KDEWalletKeychainAccess"),
|
||||||
|
MAC_SYSTEM_KEYCHAIN("org.cryptomator.macos.keychain.MacSystemKeychainAccess"),
|
||||||
|
WIN_SYSTEM_KEYCHAIN("org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess");
|
||||||
|
|
||||||
|
private final String providerClass;
|
||||||
|
|
||||||
|
KeychainBackend(String providerClass) {
|
||||||
|
this.providerClass = providerClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProviderClass() {
|
||||||
|
return providerClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
@@ -20,7 +23,6 @@ import javafx.beans.property.StringProperty;
|
|||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.geometry.NodeOrientation;
|
import javafx.geometry.NodeOrientation;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class Settings {
|
public class Settings {
|
||||||
@@ -34,10 +36,12 @@ public class Settings {
|
|||||||
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
public static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||||
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
|
public static final WebDavUrlScheme DEFAULT_GVFS_SCHEME = WebDavUrlScheme.DAV;
|
||||||
public static final boolean DEFAULT_DEBUG_MODE = false;
|
public static final boolean DEFAULT_DEBUG_MODE = false;
|
||||||
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
|
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = SystemUtils.IS_OS_WINDOWS ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
|
||||||
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
||||||
|
public static final KeychainBackend DEFAULT_KEYCHAIN_BACKEND = SystemUtils.IS_OS_WINDOWS ? KeychainBackend.WIN_SYSTEM_KEYCHAIN : SystemUtils.IS_OS_MAC ? KeychainBackend.MAC_SYSTEM_KEYCHAIN : KeychainBackend.GNOME;
|
||||||
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
|
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
|
||||||
private static final String DEFAULT_LICENSE_KEY = "";
|
public static final String DEFAULT_LICENSE_KEY = "";
|
||||||
|
public static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
|
||||||
|
|
||||||
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);
|
||||||
@@ -49,15 +53,20 @@ public class Settings {
|
|||||||
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
|
||||||
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
||||||
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
||||||
|
private final ObjectProperty<KeychainBackend> keychainBackend = new SimpleObjectProperty<>(DEFAULT_KEYCHAIN_BACKEND);
|
||||||
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
||||||
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
|
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
|
||||||
|
private final BooleanProperty showMinimizeButton = new SimpleBooleanProperty(DEFAULT_SHOW_MINIMIZE_BUTTON);
|
||||||
|
private final BooleanProperty showTrayIcon;
|
||||||
|
|
||||||
private Consumer<Settings> saveCmd;
|
private Consumer<Settings> saveCmd;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Package-private constructor; use {@link SettingsProvider}.
|
* Package-private constructor; use {@link SettingsProvider}.
|
||||||
*/
|
*/
|
||||||
Settings() {
|
Settings(Environment env) {
|
||||||
|
this.showTrayIcon = new SimpleBooleanProperty(env.showTrayIcon());
|
||||||
|
|
||||||
directories.addListener(this::somethingChanged);
|
directories.addListener(this::somethingChanged);
|
||||||
askedForUpdateCheck.addListener(this::somethingChanged);
|
askedForUpdateCheck.addListener(this::somethingChanged);
|
||||||
checkForUpdates.addListener(this::somethingChanged);
|
checkForUpdates.addListener(this::somethingChanged);
|
||||||
@@ -68,14 +77,17 @@ public class Settings {
|
|||||||
debugMode.addListener(this::somethingChanged);
|
debugMode.addListener(this::somethingChanged);
|
||||||
preferredVolumeImpl.addListener(this::somethingChanged);
|
preferredVolumeImpl.addListener(this::somethingChanged);
|
||||||
theme.addListener(this::somethingChanged);
|
theme.addListener(this::somethingChanged);
|
||||||
|
keychainBackend.addListener(this::somethingChanged);
|
||||||
userInterfaceOrientation.addListener(this::somethingChanged);
|
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||||
licenseKey.addListener(this::somethingChanged);
|
licenseKey.addListener(this::somethingChanged);
|
||||||
|
showMinimizeButton.addListener(this::somethingChanged);
|
||||||
|
showTrayIcon.addListener(this::somethingChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||||
this.saveCmd = saveCmd;
|
this.saveCmd = saveCmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void somethingChanged(@SuppressWarnings("unused") Observable observable) {
|
private void somethingChanged(@SuppressWarnings("unused") Observable observable) {
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
@@ -99,7 +111,7 @@ public class Settings {
|
|||||||
public BooleanProperty checkForUpdates() {
|
public BooleanProperty checkForUpdates() {
|
||||||
return checkForUpdates;
|
return checkForUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanProperty startHidden() {
|
public BooleanProperty startHidden() {
|
||||||
return startHidden;
|
return startHidden;
|
||||||
}
|
}
|
||||||
@@ -128,6 +140,8 @@ public class Settings {
|
|||||||
return theme;
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<KeychainBackend> keychainBackend() { return keychainBackend; }
|
||||||
|
|
||||||
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
|
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
|
||||||
return userInterfaceOrientation;
|
return userInterfaceOrientation;
|
||||||
}
|
}
|
||||||
@@ -135,4 +149,12 @@ public class Settings {
|
|||||||
public StringProperty licenseKey() {
|
public StringProperty licenseKey() {
|
||||||
return licenseKey;
|
return licenseKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty showMinimizeButton() {
|
||||||
|
return showMinimizeButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty showTrayIcon() {
|
||||||
|
return showTrayIcon;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-3
@@ -9,19 +9,29 @@ 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.cryptomator.common.Environment;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javafx.geometry.NodeOrientation;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SettingsJsonAdapter.class);
|
private static final Logger LOG = LoggerFactory.getLogger(SettingsJsonAdapter.class);
|
||||||
|
|
||||||
private final VaultSettingsJsonAdapter vaultSettingsJsonAdapter = new VaultSettingsJsonAdapter();
|
private final VaultSettingsJsonAdapter vaultSettingsJsonAdapter = new VaultSettingsJsonAdapter();
|
||||||
|
private final Environment env;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SettingsJsonAdapter(Environment env) {
|
||||||
|
this.env = env;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(JsonWriter out, Settings value) throws IOException {
|
public void write(JsonWriter out, Settings value) throws IOException {
|
||||||
@@ -38,7 +48,10 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
||||||
out.name("theme").value(value.theme().get().name());
|
out.name("theme").value(value.theme().get().name());
|
||||||
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
||||||
|
out.name("keychainBackend").value(value.keychainBackend().get().name());
|
||||||
out.name("licenseKey").value(value.licenseKey().get());
|
out.name("licenseKey").value(value.licenseKey().get());
|
||||||
|
out.name("showMinimizeButton").value(value.showMinimizeButton().get());
|
||||||
|
out.name("showTrayIcon").value(value.showTrayIcon().get());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +65,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Settings read(JsonReader in) throws IOException {
|
public Settings read(JsonReader in) throws IOException {
|
||||||
Settings settings = new Settings();
|
Settings settings = new Settings(env);
|
||||||
|
|
||||||
in.beginObject();
|
in.beginObject();
|
||||||
while (in.hasNext()) {
|
while (in.hasNext()) {
|
||||||
@@ -69,7 +82,10 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
case "preferredVolumeImpl" -> settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
case "preferredVolumeImpl" -> settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
||||||
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
|
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
|
||||||
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||||
|
case "keychainBackend" -> settings.keychainBackend().set(parseKeychainBackend(in.nextString()));
|
||||||
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
||||||
|
case "showMinimizeButton" -> settings.showMinimizeButton().set(in.nextBoolean());
|
||||||
|
case "showTrayIcon" -> settings.showTrayIcon().set(in.nextBoolean());
|
||||||
default -> {
|
default -> {
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
@@ -108,6 +124,15 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private KeychainBackend parseKeychainBackend(String backendName) {
|
||||||
|
try {
|
||||||
|
return KeychainBackend.valueOf(backendName.toUpperCase());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LOG.warn("Invalid keychain backend {}. Defaulting to {}.", backendName, Settings.DEFAULT_KEYCHAIN_BACKEND);
|
||||||
|
return Settings.DEFAULT_KEYCHAIN_BACKEND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private NodeOrientation parseUiOrientation(String uiOrientationName) {
|
private NodeOrientation parseUiOrientation(String uiOrientationName) {
|
||||||
try {
|
try {
|
||||||
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());
|
return NodeOrientation.valueOf(uiOrientationName.toUpperCase());
|
||||||
@@ -126,5 +151,4 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
in.endArray();
|
in.endArray();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -8,13 +8,13 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
import org.cryptomator.common.Environment;
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.common.LazyInitializer;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -48,14 +48,15 @@ public class SettingsProvider implements Supplier<Settings> {
|
|||||||
private static final long SAVE_DELAY_MS = 1000;
|
private static final long SAVE_DELAY_MS = 1000;
|
||||||
|
|
||||||
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
|
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
|
||||||
private final AtomicReference<Settings> settings = new AtomicReference<>();
|
private final Supplier<Settings> settings = Suppliers.memoize(this::load);
|
||||||
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter();
|
private final SettingsJsonAdapter settingsJsonAdapter;
|
||||||
private final Environment env;
|
private final Environment env;
|
||||||
private final ScheduledExecutorService scheduler;
|
private final ScheduledExecutorService scheduler;
|
||||||
private final Gson gson;
|
private final Gson gson;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SettingsProvider(Environment env, ScheduledExecutorService scheduler) {
|
public SettingsProvider(SettingsJsonAdapter settingsJsonAdapter, Environment env, ScheduledExecutorService scheduler) {
|
||||||
|
this.settingsJsonAdapter = settingsJsonAdapter;
|
||||||
this.env = env;
|
this.env = env;
|
||||||
this.scheduler = scheduler;
|
this.scheduler = scheduler;
|
||||||
this.gson = new GsonBuilder() //
|
this.gson = new GsonBuilder() //
|
||||||
@@ -66,11 +67,11 @@ public class SettingsProvider implements Supplier<Settings> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Settings get() {
|
public Settings get() {
|
||||||
return LazyInitializer.initializeLazily(settings, this::load);
|
return settings.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Settings load() {
|
private Settings load() {
|
||||||
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElse(new Settings());
|
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElse(new Settings(env));
|
||||||
settings.setSaveCmd(this::scheduleSave);
|
settings.setSaveCmd(this::scheduleSave);
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
@@ -90,7 +91,7 @@ public class SettingsProvider implements Supplier<Settings> {
|
|||||||
}
|
}
|
||||||
} catch (NoSuchFileException e) {
|
} catch (NoSuchFileException e) {
|
||||||
return Stream.empty();
|
return Stream.empty();
|
||||||
} catch (IOException e) {
|
} catch (IOException | JsonParseException e) {
|
||||||
LOG.warn("Exception while loading settings from " + path, e);
|
LOG.warn("Exception while loading settings from " + path, e);
|
||||||
return Stream.empty();
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
public enum UiTheme {
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
LIGHT("preferences.general.theme.light"),
|
|
||||||
DARK("preferences.general.theme.dark");
|
|
||||||
// CUSTOM("Custom (%s)");
|
|
||||||
|
|
||||||
private String displayName;
|
public enum UiTheme {
|
||||||
|
LIGHT("preferences.general.theme.light"), //
|
||||||
|
DARK("preferences.general.theme.dark"), //
|
||||||
|
AUTOMATIC("preferences.general.theme.automatic");
|
||||||
|
|
||||||
|
public static UiTheme[] applicableValues() {
|
||||||
|
if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
return values();
|
||||||
|
} else {
|
||||||
|
return new UiTheme[]{LIGHT, DARK};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String displayName;
|
||||||
|
|
||||||
UiTheme(String displayName) {
|
UiTheme(String displayName) {
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
|
|||||||
@@ -5,9 +5,13 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import com.google.common.base.CharMatcher;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
|
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
@@ -16,9 +20,6 @@ import javafx.beans.property.SimpleIntegerProperty;
|
|||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -34,15 +35,14 @@ 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 = "";
|
||||||
public static final String DEFAULT_MOUNT_NAME = "Vault";
|
public static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
|
||||||
public static final int DEFAULT_FILENAME_LENGTH_LIMIT = -1;
|
|
||||||
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
|
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
|
||||||
|
|
||||||
private static final Random RNG = new Random();
|
private static final Random RNG = new Random();
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
private final ObjectProperty<Path> path = new SimpleObjectProperty();
|
private final ObjectProperty<Path> path = new SimpleObjectProperty();
|
||||||
private final StringProperty mountName = new SimpleStringProperty();
|
private final StringProperty displayName = new SimpleStringProperty();
|
||||||
private final StringProperty winDriveLetter = new SimpleStringProperty();
|
private final StringProperty winDriveLetter = new SimpleStringProperty();
|
||||||
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
||||||
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
|
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
|
||||||
@@ -50,27 +50,18 @@ public class VaultSettings {
|
|||||||
private final StringProperty customMountPath = new SimpleStringProperty();
|
private final StringProperty customMountPath = new SimpleStringProperty();
|
||||||
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
||||||
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
||||||
private final IntegerProperty filenameLengthLimit = new SimpleIntegerProperty(DEFAULT_FILENAME_LENGTH_LIMIT);
|
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
|
||||||
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
||||||
|
|
||||||
|
private final StringBinding mountName;
|
||||||
|
|
||||||
public VaultSettings(String id) {
|
public VaultSettings(String id) {
|
||||||
this.id = Objects.requireNonNull(id);
|
this.id = Objects.requireNonNull(id);
|
||||||
|
this.mountName = Bindings.createStringBinding(this::normalizeDisplayName, displayName);
|
||||||
EasyBind.subscribe(path, this::deriveMountNameFromPathOrUseDefault);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Observable[] observables() {
|
Observable[] observables() {
|
||||||
return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit, actionAfterUnlock};
|
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, maxCleartextFilenameLength, actionAfterUnlock};
|
||||||
}
|
|
||||||
|
|
||||||
private void deriveMountNameFromPathOrUseDefault(Path path) {
|
|
||||||
if (StringUtils.isBlank(mountName.get())) {
|
|
||||||
if (path != null && path.getFileName() != null) {
|
|
||||||
mountName.set(normalizeMountName(path.getFileName().toString()));
|
|
||||||
} else {
|
|
||||||
mountName.set(DEFAULT_MOUNT_NAME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VaultSettings withRandomId() {
|
public static VaultSettings withRandomId() {
|
||||||
@@ -83,23 +74,18 @@ public class VaultSettings {
|
|||||||
return BaseEncoding.base64Url().encode(randomBytes);
|
return BaseEncoding.base64Url().encode(randomBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String normalizeMountName(String mountName) {
|
//visible for testing
|
||||||
String normalizedMountName = StringUtils.stripAccents(mountName);
|
String normalizeDisplayName() {
|
||||||
StringBuilder builder = new StringBuilder();
|
var original = displayName.getValueSafe();
|
||||||
for (char c : normalizedMountName.toCharArray()) {
|
if (original.isBlank() || ".".equals(original) || "..".equals(original)) {
|
||||||
if (Character.isWhitespace(c)) {
|
return "_";
|
||||||
if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '_') {
|
|
||||||
builder.append('_');
|
|
||||||
}
|
|
||||||
} else if (c < 127 && Character.isLetterOrDigit(c)) {
|
|
||||||
builder.append(c);
|
|
||||||
} else {
|
|
||||||
if (builder.length() == 0 || builder.charAt(builder.length() - 1) != '_') {
|
|
||||||
builder.append('_');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return builder.toString();
|
|
||||||
|
// replace whitespaces (tabs, linebreaks, ...) by simple space (0x20)
|
||||||
|
var withoutFancyWhitespaces = CharMatcher.whitespace().collapseFrom(original, ' ');
|
||||||
|
|
||||||
|
// replace control chars as well as chars that aren't allowed in file names on standard file systems by underscore
|
||||||
|
return CharMatcher.anyOf("<>:\"/\\|?*").or(CharMatcher.javaIsoControl()).collapseFrom(withoutFancyWhitespaces, '_');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
@@ -112,7 +98,11 @@ public class VaultSettings {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty mountName() {
|
public StringProperty displayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBinding mountName() {
|
||||||
return mountName;
|
return mountName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +110,14 @@ public class VaultSettings {
|
|||||||
return winDriveLetter;
|
return winDriveLetter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<String> getWinDriveLetter() {
|
||||||
|
String current = this.winDriveLetter.get();
|
||||||
|
if (!Strings.isNullOrEmpty(current)) {
|
||||||
|
return Optional.of(current);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
public BooleanProperty unlockAfterStartup() {
|
public BooleanProperty unlockAfterStartup() {
|
||||||
return unlockAfterStartup;
|
return unlockAfterStartup;
|
||||||
}
|
}
|
||||||
@@ -151,9 +149,9 @@ public class VaultSettings {
|
|||||||
public StringProperty mountFlags() {
|
public StringProperty mountFlags() {
|
||||||
return mountFlags;
|
return mountFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntegerProperty filenameLengthLimit() {
|
public IntegerProperty maxCleartextFilenameLength() {
|
||||||
return filenameLengthLimit;
|
return maxCleartextFilenameLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<WhenUnlocked> actionAfterUnlock() {
|
public ObjectProperty<WhenUnlocked> actionAfterUnlock() {
|
||||||
@@ -173,8 +171,7 @@ public class VaultSettings {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof VaultSettings && obj.getClass().equals(this.getClass())) {
|
if (obj instanceof VaultSettings other && obj.getClass().equals(this.getClass())) {
|
||||||
VaultSettings other = (VaultSettings) obj;
|
|
||||||
return Objects.equals(this.id, other.id);
|
return Objects.equals(this.id, other.id);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+14
-8
@@ -21,7 +21,7 @@ class VaultSettingsJsonAdapter {
|
|||||||
out.beginObject();
|
out.beginObject();
|
||||||
out.name("id").value(value.getId());
|
out.name("id").value(value.getId());
|
||||||
out.name("path").value(value.path().get().toString());
|
out.name("path").value(value.path().get().toString());
|
||||||
out.name("mountName").value(value.mountName().get());
|
out.name("displayName").value(value.displayName().get());
|
||||||
out.name("winDriveLetter").value(value.winDriveLetter().get());
|
out.name("winDriveLetter").value(value.winDriveLetter().get());
|
||||||
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
|
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
|
||||||
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
||||||
@@ -29,7 +29,7 @@ class VaultSettingsJsonAdapter {
|
|||||||
out.name("customMountPath").value(value.customMountPath().get());
|
out.name("customMountPath").value(value.customMountPath().get());
|
||||||
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
||||||
out.name("mountFlags").value(value.mountFlags().get());
|
out.name("mountFlags").value(value.mountFlags().get());
|
||||||
out.name("filenameLengthLimit").value(value.filenameLengthLimit().get());
|
out.name("maxCleartextFilenameLength").value(value.maxCleartextFilenameLength().get());
|
||||||
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
|
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,8 @@ class VaultSettingsJsonAdapter {
|
|||||||
public VaultSettings read(JsonReader in) throws IOException {
|
public VaultSettings read(JsonReader in) throws IOException {
|
||||||
String id = null;
|
String id = null;
|
||||||
String path = null;
|
String path = null;
|
||||||
String mountName = null;
|
String mountName = null; //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||||
|
String displayName = null;
|
||||||
String customMountPath = null;
|
String customMountPath = null;
|
||||||
String winDriveLetter = null;
|
String winDriveLetter = null;
|
||||||
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
||||||
@@ -45,7 +46,7 @@ class VaultSettingsJsonAdapter {
|
|||||||
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
||||||
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||||
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
||||||
int filenameLengthLimit = VaultSettings.DEFAULT_FILENAME_LENGTH_LIMIT;
|
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
|
||||||
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||||
|
|
||||||
in.beginObject();
|
in.beginObject();
|
||||||
@@ -54,7 +55,8 @@ class VaultSettingsJsonAdapter {
|
|||||||
switch (name) {
|
switch (name) {
|
||||||
case "id" -> id = in.nextString();
|
case "id" -> id = in.nextString();
|
||||||
case "path" -> path = in.nextString();
|
case "path" -> path = in.nextString();
|
||||||
case "mountName" -> mountName = in.nextString();
|
case "mountName" -> mountName = in.nextString(); //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||||
|
case "displayName" -> displayName = in.nextString();
|
||||||
case "winDriveLetter" -> winDriveLetter = in.nextString();
|
case "winDriveLetter" -> winDriveLetter = in.nextString();
|
||||||
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
|
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
|
||||||
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
|
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
|
||||||
@@ -62,7 +64,7 @@ class VaultSettingsJsonAdapter {
|
|||||||
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
|
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
|
||||||
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
|
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
|
||||||
case "mountFlags" -> mountFlags = in.nextString();
|
case "mountFlags" -> mountFlags = in.nextString();
|
||||||
case "filenameLengthLimit" -> filenameLengthLimit = in.nextInt();
|
case "maxCleartextFilenameLength" -> maxCleartextFilenameLength = in.nextInt();
|
||||||
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
||||||
default -> {
|
default -> {
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||||
@@ -73,7 +75,11 @@ class VaultSettingsJsonAdapter {
|
|||||||
in.endObject();
|
in.endObject();
|
||||||
|
|
||||||
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id);
|
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id);
|
||||||
vaultSettings.mountName().set(mountName);
|
if (displayName != null) { //see https://github.com/cryptomator/cryptomator/pull/1318
|
||||||
|
vaultSettings.displayName().set(displayName);
|
||||||
|
} else {
|
||||||
|
vaultSettings.displayName().set(mountName);
|
||||||
|
}
|
||||||
vaultSettings.path().set(Paths.get(path));
|
vaultSettings.path().set(Paths.get(path));
|
||||||
vaultSettings.winDriveLetter().set(winDriveLetter);
|
vaultSettings.winDriveLetter().set(winDriveLetter);
|
||||||
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
|
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
|
||||||
@@ -82,7 +88,7 @@ class VaultSettingsJsonAdapter {
|
|||||||
vaultSettings.customMountPath().set(customMountPath);
|
vaultSettings.customMountPath().set(customMountPath);
|
||||||
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
||||||
vaultSettings.mountFlags().set(mountFlags);
|
vaultSettings.mountFlags().set(mountFlags);
|
||||||
vaultSettings.filenameLengthLimit().set(filenameLengthLimit);
|
vaultSettings.maxCleartextFilenameLength().set(maxCleartextFilenameLength);
|
||||||
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
||||||
return vaultSettings;
|
return vaultSettings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ public enum WebDavUrlScheme {
|
|||||||
private final String prefix;
|
private final String prefix;
|
||||||
private final String displayName;
|
private final String displayName;
|
||||||
|
|
||||||
WebDavUrlScheme(String prefix, String displayName) {this.prefix = prefix;
|
WebDavUrlScheme(String prefix, String displayName) {
|
||||||
|
this.prefix = prefix;
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||||
|
import org.cryptomator.common.mountpoint.MountPointChooser;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public abstract class AbstractVolume implements Volume {
|
||||||
|
|
||||||
|
private final Iterable<MountPointChooser> choosers;
|
||||||
|
|
||||||
|
protected Path mountPoint;
|
||||||
|
private boolean cleanupRequired;
|
||||||
|
private MountPointChooser usedChooser;
|
||||||
|
|
||||||
|
public AbstractVolume(Iterable<MountPointChooser> choosers) {
|
||||||
|
this.choosers = choosers;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Path determineMountPoint() throws InvalidMountPointException {
|
||||||
|
var applicableChoosers = Iterables.filter(choosers, c -> c.isApplicable(this));
|
||||||
|
for (var chooser : applicableChoosers) {
|
||||||
|
Optional<Path> chosenPath = chooser.chooseMountPoint(this);
|
||||||
|
if (chosenPath.isEmpty()) { // chooser couldn't find a feasible mountpoint
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.cleanupRequired = chooser.prepare(this, chosenPath.get());
|
||||||
|
this.usedChooser = chooser;
|
||||||
|
return chosenPath.get();
|
||||||
|
}
|
||||||
|
throw new InvalidMountPointException(String.format("No feasible MountPoint found by choosers: %s", applicableChoosers));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void cleanupMountPoint() {
|
||||||
|
if (this.cleanupRequired) {
|
||||||
|
this.usedChooser.cleanup(this, this.mountPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Path> getMountPoint() {
|
||||||
|
return Optional.ofNullable(mountPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,4 +10,5 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||||||
@Documented
|
@Documented
|
||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
public @interface DefaultMountFlags {
|
public @interface DefaultMountFlags {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,82 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||||
|
import org.cryptomator.common.mountpoint.MountPointChooser;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
|
import org.cryptomator.frontend.dokany.DokanyMountFailedException;
|
||||||
import org.cryptomator.frontend.dokany.Mount;
|
import org.cryptomator.frontend.dokany.Mount;
|
||||||
import org.cryptomator.frontend.dokany.MountFactory;
|
import org.cryptomator.frontend.dokany.MountFactory;
|
||||||
import org.cryptomator.frontend.dokany.MountFailedException;
|
|
||||||
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 java.io.IOException;
|
import javax.inject.Named;
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
import java.util.function.Consumer;
|
||||||
import java.nio.file.DirectoryStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.NotDirectoryException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
|
|
||||||
public class DokanyVolume implements Volume {
|
public class DokanyVolume extends AbstractVolume {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DokanyVolume.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DokanyVolume.class);
|
||||||
|
|
||||||
private static final String FS_TYPE_NAME = "Cryptomator File System";
|
private static final String FS_TYPE_NAME = "CryptomatorFS";
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
private final MountFactory mountFactory;
|
|
||||||
private final 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, @Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
|
||||||
|
super(choosers);
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.mountFactory = new MountFactory(executorService);
|
}
|
||||||
this.windowsDriveLetters = windowsDriveLetters;
|
|
||||||
|
@Override
|
||||||
|
public VolumeImpl getImplementationType() {
|
||||||
|
return VolumeImpl.DOKANY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws InvalidMountPointException, VolumeException {
|
||||||
|
this.mountPoint = determineMountPoint();
|
||||||
|
try {
|
||||||
|
this.mount = MountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip(), onExitAction);
|
||||||
|
} catch (DokanyMountFailedException e) {
|
||||||
|
if (vaultSettings.getCustomMountPath().isPresent()) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reveal(Revealer revealer) throws VolumeException {
|
||||||
|
try {
|
||||||
|
mount.reveal(revealer::reveal);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new VolumeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unmount() throws VolumeException {
|
||||||
|
try {
|
||||||
|
mount.unmount();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
throw new VolumeException("Unmount Failed.", e);
|
||||||
|
}
|
||||||
|
cleanupMountPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unmountForced() {
|
||||||
|
mount.unmountForced();
|
||||||
|
cleanupMountPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsForcedUnmount() {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -45,65 +85,8 @@ public class DokanyVolume implements Volume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException, IOException {
|
public MountPointRequirement getMountPointRequirement() {
|
||||||
this.mountPoint = determineMountPoint();
|
return MountPointRequirement.EMPTY_MOUNT_POINT;
|
||||||
String mountName = vaultSettings.mountName().get();
|
|
||||||
try {
|
|
||||||
this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, mountName, FS_TYPE_NAME, mountFlags.strip());
|
|
||||||
} catch (MountFailedException e) {
|
|
||||||
if (vaultSettings.getCustomMountPath().isPresent()) {
|
|
||||||
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPoint);
|
|
||||||
}
|
|
||||||
throw new VolumeException("Unable to mount Filesystem", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path determineMountPoint() throws VolumeException, IOException {
|
|
||||||
Optional<String> optionalCustomMountPoint = vaultSettings.getCustomMountPath();
|
|
||||||
if (optionalCustomMountPoint.isPresent()) {
|
|
||||||
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
|
||||||
checkProvidedMountPoint(customMountPoint);
|
|
||||||
return customMountPoint;
|
|
||||||
} else if (!Strings.isNullOrEmpty(vaultSettings.winDriveLetter().get())) {
|
|
||||||
return Path.of(vaultSettings.winDriveLetter().get().charAt(0) + ":\\");
|
|
||||||
} else {
|
|
||||||
//auto assign drive letter
|
|
||||||
if (!windowsDriveLetters.getAvailableDriveLetters().isEmpty()) {
|
|
||||||
return Path.of(windowsDriveLetters.getAvailableDriveLetters().iterator().next() + ":\\");
|
|
||||||
} else {
|
|
||||||
//TODO: Error Handling
|
|
||||||
throw new VolumeException("No free drive letter available.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkProvidedMountPoint(Path mountPoint) throws IOException {
|
|
||||||
if (!Files.isDirectory(mountPoint)) {
|
|
||||||
throw new NotDirectoryException(mountPoint.toString());
|
|
||||||
}
|
|
||||||
try (DirectoryStream<Path> ds = Files.newDirectoryStream(mountPoint)) {
|
|
||||||
if (ds.iterator().hasNext()) {
|
|
||||||
throw new DirectoryNotEmptyException(mountPoint.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reveal() throws VolumeException {
|
|
||||||
boolean success = mount.reveal();
|
|
||||||
if (!success) {
|
|
||||||
throw new VolumeException("Reveal failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unmount() {
|
|
||||||
mount.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Path> getMountPoint() {
|
|
||||||
return Optional.ofNullable(mountPoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSupportedStatic() {
|
public static boolean isSupportedStatic() {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import com.google.common.base.Splitter;
|
import com.google.common.collect.Iterators;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.Environment;
|
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.mountpoint.MountPointChooser;
|
||||||
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
|
|
||||||
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
|
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
|
||||||
|
import org.cryptomator.frontend.fuse.mount.FuseMountException;
|
||||||
import org.cryptomator.frontend.fuse.mount.FuseMountFactory;
|
import org.cryptomator.frontend.fuse.mount.FuseMountFactory;
|
||||||
import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException;
|
import org.cryptomator.frontend.fuse.mount.FuseNotSupportedException;
|
||||||
import org.cryptomator.frontend.fuse.mount.Mount;
|
import org.cryptomator.frontend.fuse.mount.Mount;
|
||||||
@@ -15,109 +16,68 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.io.IOException;
|
import javax.inject.Named;
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
|
||||||
import java.nio.file.DirectoryStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.NotDirectoryException;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.util.ArrayList;
|
||||||
import java.util.Optional;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class FuseVolume implements Volume {
|
public class FuseVolume extends AbstractVolume {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
|
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
|
||||||
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
|
private static final Pattern NON_WHITESPACE_OR_QUOTED = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'"); // Thanks to https://stackoverflow.com/a/366532
|
||||||
private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase().contains("mac");
|
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
private Mount mount;
|
||||||
private final Environment environment;
|
|
||||||
|
|
||||||
private Mount fuseMnt;
|
|
||||||
private Path mountPoint;
|
|
||||||
private boolean createdTemporaryMountPoint;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FuseVolume(VaultSettings vaultSettings, Environment environment) {
|
public FuseVolume(@Named("orderedMountPointChoosers") Iterable<MountPointChooser> choosers) {
|
||||||
this.vaultSettings = vaultSettings;
|
super(choosers);
|
||||||
this.environment = environment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags) throws IOException, FuseNotSupportedException, VolumeException {
|
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws InvalidMountPointException, VolumeException {
|
||||||
Optional<String> optionalCustomMountPoint = vaultSettings.getCustomMountPath();
|
this.mountPoint = determineMountPoint();
|
||||||
if (optionalCustomMountPoint.isPresent()) {
|
mount(fs.getPath("/"), mountFlags, onExitAction);
|
||||||
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
|
||||||
checkProvidedMountPoint(customMountPoint);
|
|
||||||
this.mountPoint = customMountPoint;
|
|
||||||
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
|
|
||||||
} else {
|
|
||||||
this.mountPoint = prepareTemporaryMountPoint();
|
|
||||||
LOG.debug("Successfully created mount point: {}", mountPoint);
|
|
||||||
}
|
|
||||||
mount(fs.getPath("/"), mountFlags);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkProvidedMountPoint(Path mountPoint) throws IOException {
|
private void mount(Path root, String mountFlags, Consumer<Throwable> onExitAction) throws VolumeException {
|
||||||
if (!Files.isDirectory(mountPoint)) {
|
|
||||||
throw new NotDirectoryException(mountPoint.toString());
|
|
||||||
}
|
|
||||||
try (DirectoryStream<Path> ds = Files.newDirectoryStream(mountPoint)) {
|
|
||||||
if (ds.iterator().hasNext()) {
|
|
||||||
throw new DirectoryNotEmptyException(mountPoint.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path prepareTemporaryMountPoint() throws IOException, VolumeException {
|
|
||||||
Path mountPoint = chooseNonExistingTemporaryMountPoint();
|
|
||||||
// https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592:
|
|
||||||
// In order to allow non-admin users to mount FUSE volumes in `/Volumes`,
|
|
||||||
// starting with version 3.5.0, FUSE will create non-existent mount points automatically.
|
|
||||||
if (IS_MAC && mountPoint.getParent().equals(Paths.get("/Volumes"))) {
|
|
||||||
return mountPoint;
|
|
||||||
} else {
|
|
||||||
Files.createDirectories(mountPoint);
|
|
||||||
this.createdTemporaryMountPoint = true;
|
|
||||||
return mountPoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path chooseNonExistingTemporaryMountPoint() throws VolumeException {
|
|
||||||
Path parent = environment.getMountPointsDir().orElseThrow();
|
|
||||||
String basename = vaultSettings.getId();
|
|
||||||
for (int i = 0; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
|
|
||||||
Path mountPoint = parent.resolve(basename + "_" + i);
|
|
||||||
if (Files.notExists(mountPoint)) {
|
|
||||||
return mountPoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG.error("Failed to find feasible mountpoint at {}/{}_x. Giving up after {} attempts.", parent, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
|
|
||||||
throw new VolumeException("Did not find feasible mount point.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void mount(Path root, String mountFlags) throws VolumeException {
|
|
||||||
try {
|
try {
|
||||||
Mounter mounter = FuseMountFactory.getMounter();
|
Mounter mounter = FuseMountFactory.getMounter();
|
||||||
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
EnvironmentVariables envVars = EnvironmentVariables.create() //
|
||||||
.withFlags(splitFlags(mountFlags)).withMountPoint(mountPoint) //
|
.withFlags(splitFlags(mountFlags)) //
|
||||||
|
.withMountPoint(mountPoint) //
|
||||||
|
.withFileNameTranscoder(mounter.defaultFileNameTranscoder()) //
|
||||||
.build();
|
.build();
|
||||||
this.fuseMnt = mounter.mount(root, envVars);
|
this.mount = mounter.mount(root, envVars, onExitAction);
|
||||||
} catch (CommandFailedException e) {
|
} catch ( FuseMountException | FuseNotSupportedException e) {
|
||||||
throw new VolumeException("Unable to mount Filesystem", e);
|
throw new VolumeException("Unable to mount Filesystem", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] splitFlags(String str) {
|
private String[] splitFlags(String str) {
|
||||||
return Splitter.on(' ').splitToList(str).toArray(String[]::new);
|
List<String> flags = new ArrayList<>();
|
||||||
|
var matches = Iterators.peekingIterator(NON_WHITESPACE_OR_QUOTED.matcher(str).results().iterator());
|
||||||
|
while (matches.hasNext()) {
|
||||||
|
String flag = matches.next().group();
|
||||||
|
// check if flag is missing its argument:
|
||||||
|
if (flag.endsWith("=") && matches.hasNext() && matches.peek().group(1) != null) { // next is "double quoted"
|
||||||
|
// next is "double quoted" and flag is missing its argument
|
||||||
|
flag += matches.next().group(1);
|
||||||
|
} else if (flag.endsWith("=") && matches.hasNext() && matches.peek().group(2) != null) {
|
||||||
|
// next is 'single quoted' and flag is missing its argument
|
||||||
|
flag += matches.next().group(2);
|
||||||
|
}
|
||||||
|
flags.add(flag);
|
||||||
|
}
|
||||||
|
return flags.toArray(String[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reveal() throws VolumeException {
|
public void reveal(Revealer revealer) throws VolumeException {
|
||||||
try {
|
try {
|
||||||
fuseMnt.revealInFileManager();
|
mount.reveal(revealer::reveal);
|
||||||
} catch (CommandFailedException e) {
|
} catch (Exception e) {
|
||||||
LOG.debug("Revealing the vault in file manger failed: " + e.getMessage());
|
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,34 +90,21 @@ public class FuseVolume implements Volume {
|
|||||||
@Override
|
@Override
|
||||||
public synchronized void unmountForced() throws VolumeException {
|
public synchronized void unmountForced() throws VolumeException {
|
||||||
try {
|
try {
|
||||||
fuseMnt.unmountForced();
|
mount.unmountForced();
|
||||||
fuseMnt.close();
|
} catch (FuseMountException e) {
|
||||||
} catch (CommandFailedException e) {
|
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
cleanupTemporaryMountPoint();
|
cleanupMountPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void unmount() throws VolumeException {
|
public synchronized void unmount() throws VolumeException {
|
||||||
try {
|
try {
|
||||||
fuseMnt.unmount();
|
mount.unmount();
|
||||||
fuseMnt.close();
|
} catch (FuseMountException e) {
|
||||||
} catch (CommandFailedException e) {
|
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
cleanupTemporaryMountPoint();
|
cleanupMountPoint();
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanupTemporaryMountPoint() {
|
|
||||||
if (createdTemporaryMountPoint) {
|
|
||||||
try {
|
|
||||||
Files.delete(mountPoint);
|
|
||||||
LOG.debug("Successfully deleted mount point: {}", mountPoint);
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.warn("Could not delete mount point: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -166,12 +113,17 @@ public class FuseVolume implements Volume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Path> getMountPoint() {
|
public VolumeImpl getImplementationType() {
|
||||||
return Optional.ofNullable(mountPoint);
|
return VolumeImpl.FUSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MountPointRequirement getMountPointRequirement() {
|
||||||
|
return SystemUtils.IS_OS_WINDOWS ? MountPointRequirement.PARENT_NO_MOUNT_POINT : MountPointRequirement.EMPTY_MOUNT_POINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isSupportedStatic() {
|
public static boolean isSupportedStatic() {
|
||||||
return (SystemUtils.IS_OS_MAC_OSX || SystemUtils.IS_OS_LINUX) && FuseMountFactory.isFuseSupported();
|
return FuseMountFactory.isFuseSupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
public class LockNotCompletedException extends Exception {
|
||||||
|
|
||||||
|
public LockNotCompletedException(String reason) {
|
||||||
|
super(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LockNotCompletedException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration used to indicate the requirements for mounting a vault
|
||||||
|
* using a specific {@link Volume VolumeProvider}, e.g. {@link FuseVolume}.
|
||||||
|
*/
|
||||||
|
public enum MountPointRequirement {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No Mountpoint on the local filesystem required. (e.g. WebDAV)
|
||||||
|
*/
|
||||||
|
NONE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parent folder is required, but the actual Mountpoint must not exist.
|
||||||
|
*/
|
||||||
|
PARENT_NO_MOUNT_POINT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parent folder is required, but the actual Mountpoint may exist.
|
||||||
|
*/
|
||||||
|
PARENT_OPT_MOUNT_POINT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual Mountpoint must exist and must be empty.
|
||||||
|
*/
|
||||||
|
EMPTY_MOUNT_POINT;
|
||||||
|
}
|
||||||
@@ -8,6 +8,6 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
@Scope
|
@Scope
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@interface PerVault {
|
public @interface PerVault {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,54 +9,60 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import javafx.beans.Observable;
|
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.binding.BooleanBinding;
|
|
||||||
import javafx.beans.binding.StringBinding;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.LazyInitializer;
|
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.common.vaults.Volume.VolumeException;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
import org.cryptomator.cryptofs.common.Constants;
|
import org.cryptomator.cryptofs.VaultConfig;
|
||||||
|
import org.cryptomator.cryptofs.VaultConfig.UnverifiedVaultConfig;
|
||||||
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
||||||
import org.cryptomator.cryptolib.api.CryptoException;
|
import org.cryptomator.cryptolib.api.CryptoException;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||||
|
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||||
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.Named;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
|
import javafx.beans.Observable;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.NotDirectoryException;
|
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.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
|
||||||
|
|
||||||
@PerVault
|
@PerVault
|
||||||
public class Vault {
|
public class Vault {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
|
||||||
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
|
private static final Path HOME_DIR = Paths.get(SystemUtils.USER_HOME);
|
||||||
|
private static final int UNLIMITED_FILENAME_LENGTH = Integer.MAX_VALUE;
|
||||||
|
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
private final Provider<Volume> volumeProvider;
|
private final Provider<Volume> volumeProvider;
|
||||||
private final StringBinding defaultMountFlags;
|
private final StringBinding defaultMountFlags;
|
||||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
||||||
private final ObjectProperty<VaultState> state;
|
private final VaultState state;
|
||||||
private final ObjectProperty<Exception> lastKnownException;
|
private final ObjectProperty<Exception> lastKnownException;
|
||||||
private final VaultStats stats;
|
private final VaultStats stats;
|
||||||
private final StringBinding displayableName;
|
private final StringBinding displayName;
|
||||||
private final StringBinding displayablePath;
|
private final StringBinding displayablePath;
|
||||||
private final BooleanBinding locked;
|
private final BooleanBinding locked;
|
||||||
private final BooleanBinding processing;
|
private final BooleanBinding processing;
|
||||||
@@ -66,11 +72,12 @@ public class Vault {
|
|||||||
private final BooleanBinding unknownError;
|
private final BooleanBinding unknownError;
|
||||||
private final StringBinding accessPoint;
|
private final StringBinding accessPoint;
|
||||||
private final BooleanBinding accessPointPresent;
|
private final BooleanBinding accessPointPresent;
|
||||||
|
private final BooleanProperty showingStats;
|
||||||
|
|
||||||
private volatile Volume volume;
|
private volatile Volume volume;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, ObjectProperty<VaultState> state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats) {
|
Vault(VaultSettings vaultSettings, Provider<Volume> volumeProvider, @DefaultMountFlags StringBinding defaultMountFlags, AtomicReference<CryptoFileSystem> cryptoFileSystem, VaultState state, @Named("lastKnownException") ObjectProperty<Exception> lastKnownException, VaultStats stats) {
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.volumeProvider = volumeProvider;
|
this.volumeProvider = volumeProvider;
|
||||||
this.defaultMountFlags = defaultMountFlags;
|
this.defaultMountFlags = defaultMountFlags;
|
||||||
@@ -78,7 +85,7 @@ public class Vault {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
this.lastKnownException = lastKnownException;
|
this.lastKnownException = lastKnownException;
|
||||||
this.stats = stats;
|
this.stats = stats;
|
||||||
this.displayableName = Bindings.createStringBinding(this::getDisplayableName, vaultSettings.path());
|
this.displayName = Bindings.createStringBinding(this::getDisplayName, vaultSettings.displayName());
|
||||||
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
||||||
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
||||||
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
|
||||||
@@ -88,53 +95,44 @@ public class Vault {
|
|||||||
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
||||||
this.accessPoint = Bindings.createStringBinding(this::getAccessPoint, state);
|
this.accessPoint = Bindings.createStringBinding(this::getAccessPoint, state);
|
||||||
this.accessPointPresent = this.accessPoint.isNotEmpty();
|
this.accessPointPresent = this.accessPoint.isNotEmpty();
|
||||||
|
this.showingStats = new SimpleBooleanProperty(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
// Commands
|
// Commands
|
||||||
// ********************************************************************************/
|
// ********************************************************************************/
|
||||||
|
|
||||||
private CryptoFileSystem getCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
|
private CryptoFileSystem createCryptoFileSystem(MasterkeyLoader keyLoader) throws IOException, MasterkeyLoadingFailedException {
|
||||||
return LazyInitializer.initializeLazily(cryptoFileSystem, () -> unlockCryptoFileSystem(passphrase), IOException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CryptoFileSystem unlockCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
|
|
||||||
Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class);
|
Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class);
|
||||||
if (vaultSettings.usesReadOnlyMode().get()) {
|
if (vaultSettings.usesReadOnlyMode().get()) {
|
||||||
flags.add(FileSystemFlags.READONLY);
|
flags.add(FileSystemFlags.READONLY);
|
||||||
|
} else if(vaultSettings.maxCleartextFilenameLength().get() == -1) {
|
||||||
|
LOG.debug("Determining cleartext filename length limitations...");
|
||||||
|
var checker = new FileSystemCapabilityChecker();
|
||||||
|
int shorteningThreshold = getUnverifiedVaultConfig().allegedShorteningThreshold();
|
||||||
|
int ciphertextLimit = checker.determineSupportedCiphertextFileNameLength(getPath());
|
||||||
|
if (ciphertextLimit < shorteningThreshold) {
|
||||||
|
int cleartextLimit = checker.determineSupportedCleartextFileNameLength(getPath());
|
||||||
|
vaultSettings.maxCleartextFilenameLength().set(cleartextLimit);
|
||||||
|
} else {
|
||||||
|
vaultSettings.maxCleartextFilenameLength().setValue(UNLIMITED_FILENAME_LENGTH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (vaultSettings.filenameLengthLimit().get() == -1) {
|
|
||||||
LOG.debug("Determining file name length limitations...");
|
if (vaultSettings.maxCleartextFilenameLength().get() < UNLIMITED_FILENAME_LENGTH) {
|
||||||
int limit = new FileSystemCapabilityChecker().determineSupportedFileNameLength(getPath());
|
LOG.warn("Limiting cleartext filename length on this device to {}.", vaultSettings.maxCleartextFilenameLength().get());
|
||||||
vaultSettings.filenameLengthLimit().set(limit);
|
|
||||||
LOG.info("Storing file name length limit of {}", limit);
|
|
||||||
}
|
}
|
||||||
assert vaultSettings.filenameLengthLimit().get() > 0;
|
|
||||||
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
|
||||||
.withPassphrase(passphrase) //
|
.withKeyLoader(keyLoader) //
|
||||||
.withFlags(flags) //
|
.withFlags(flags) //
|
||||||
.withMasterkeyFilename(MASTERKEY_FILENAME) //
|
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength().get()) //
|
||||||
.withMaxPathLength(vaultSettings.filenameLengthLimit().get() + Constants.MAX_ADDITIONAL_PATH_LENGTH) //
|
|
||||||
.withMaxNameLength(vaultSettings.filenameLengthLimit().get()) //
|
|
||||||
.build();
|
.build();
|
||||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
|
private void destroyCryptoFileSystem() {
|
||||||
if (vaultSettings.useCustomMountPath().get() && Strings.isNullOrEmpty(vaultSettings.customMountPath().get())) {
|
LOG.trace("Trying to close associated CryptoFS...");
|
||||||
throw new NotDirectoryException("");
|
|
||||||
}
|
|
||||||
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
|
|
||||||
volume = volumeProvider.get();
|
|
||||||
volume.mount(fs, getEffectiveMountFlags());
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void lock(boolean forced) throws Volume.VolumeException {
|
|
||||||
if (forced && volume.supportsForcedUnmount()) {
|
|
||||||
volume.unmountForced();
|
|
||||||
} else {
|
|
||||||
volume.unmount();
|
|
||||||
}
|
|
||||||
CryptoFileSystem fs = cryptoFileSystem.getAndSet(null);
|
CryptoFileSystem fs = cryptoFileSystem.getAndSet(null);
|
||||||
if (fs != null) {
|
if (fs != null) {
|
||||||
try {
|
try {
|
||||||
@@ -145,24 +143,67 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reveal() throws Volume.VolumeException {
|
public synchronized void unlock(MasterkeyLoader keyLoader) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
|
||||||
volume.reveal();
|
if (cryptoFileSystem.get() != null) {
|
||||||
|
throw new IllegalStateException("Already unlocked.");
|
||||||
|
}
|
||||||
|
CryptoFileSystem fs = createCryptoFileSystem(keyLoader);
|
||||||
|
boolean success = false;
|
||||||
|
try {
|
||||||
|
cryptoFileSystem.set(fs);
|
||||||
|
volume = volumeProvider.get();
|
||||||
|
volume.mount(fs, getEffectiveMountFlags(), this::lockOnVolumeExit);
|
||||||
|
success = true;
|
||||||
|
} finally {
|
||||||
|
if (!success) {
|
||||||
|
destroyCryptoFileSystem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lockOnVolumeExit(Throwable t) {
|
||||||
|
LOG.info("Unmounted vault '{}'", getDisplayName());
|
||||||
|
destroyCryptoFileSystem();
|
||||||
|
state.set(VaultState.Value.LOCKED);
|
||||||
|
if (t != null) {
|
||||||
|
LOG.warn("Unexpected unmount and lock of vault " + getDisplayName(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void lock(boolean forced) throws VolumeException, LockNotCompletedException {
|
||||||
|
//initiate unmount
|
||||||
|
if (forced && volume.supportsForcedUnmount()) {
|
||||||
|
volume.unmountForced();
|
||||||
|
} else {
|
||||||
|
volume.unmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
//wait for lockOnVolumeExit to be executed
|
||||||
|
try {
|
||||||
|
boolean locked = state.awaitState(VaultState.Value.LOCKED, 3000, TimeUnit.MILLISECONDS);
|
||||||
|
if (!locked) {
|
||||||
|
throw new LockNotCompletedException("Locking of vault " + this.getDisplayName() + " still in progress.");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new LockNotCompletedException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reveal(Volume.Revealer vaultRevealer) throws VolumeException {
|
||||||
|
volume.reveal(vaultRevealer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
// Observable Properties
|
// Observable Properties
|
||||||
// *******************************************************************************
|
// *******************************************************************************
|
||||||
|
|
||||||
public ObjectProperty<VaultState> stateProperty() {
|
public VaultState stateProperty() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public VaultState getState() {
|
public VaultState.Value getState() {
|
||||||
return state.get();
|
return state.getValue();
|
||||||
}
|
|
||||||
|
|
||||||
public void setState(VaultState value) {
|
|
||||||
state.setValue(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectProperty<Exception> lastKnownExceptionProperty() {
|
public ObjectProperty<Exception> lastKnownExceptionProperty() {
|
||||||
@@ -182,7 +223,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLocked() {
|
public boolean isLocked() {
|
||||||
return state.get() == VaultState.LOCKED;
|
return state.get() == VaultState.Value.LOCKED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding processingProperty() {
|
public BooleanBinding processingProperty() {
|
||||||
@@ -190,7 +231,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isProcessing() {
|
public boolean isProcessing() {
|
||||||
return state.get() == VaultState.PROCESSING;
|
return state.get() == VaultState.Value.PROCESSING;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding unlockedProperty() {
|
public BooleanBinding unlockedProperty() {
|
||||||
@@ -198,7 +239,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUnlocked() {
|
public boolean isUnlocked() {
|
||||||
return state.get() == VaultState.UNLOCKED;
|
return state.get() == VaultState.Value.UNLOCKED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding missingProperty() {
|
public BooleanBinding missingProperty() {
|
||||||
@@ -206,7 +247,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMissing() {
|
public boolean isMissing() {
|
||||||
return state.get() == VaultState.MISSING;
|
return state.get() == VaultState.Value.MISSING;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding needsMigrationProperty() {
|
public BooleanBinding needsMigrationProperty() {
|
||||||
@@ -214,7 +255,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNeedsMigration() {
|
public boolean isNeedsMigration() {
|
||||||
return state.get() == VaultState.NEEDS_MIGRATION;
|
return state.get() == VaultState.Value.NEEDS_MIGRATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding unknownErrorProperty() {
|
public BooleanBinding unknownErrorProperty() {
|
||||||
@@ -222,16 +263,15 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUnknownError() {
|
public boolean isUnknownError() {
|
||||||
return state.get() == VaultState.ERROR;
|
return state.get() == VaultState.Value.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding displayableNameProperty() {
|
public StringBinding displayNameProperty() {
|
||||||
return displayableName;
|
return displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDisplayableName() {
|
public String getDisplayName() {
|
||||||
Path p = vaultSettings.path().get();
|
return vaultSettings.displayName().get();
|
||||||
return p.getFileName().toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBinding accessPointProperty() {
|
public StringBinding accessPointProperty() {
|
||||||
@@ -239,7 +279,7 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getAccessPoint() {
|
public String getAccessPoint() {
|
||||||
if (state.get() == VaultState.UNLOCKED) {
|
if (state.getValue() == VaultState.Value.UNLOCKED) {
|
||||||
assert volume != null;
|
assert volume != null;
|
||||||
return volume.getMountPoint().orElse(Path.of("")).toString();
|
return volume.getMountPoint().orElse(Path.of("")).toString();
|
||||||
} else {
|
} else {
|
||||||
@@ -270,6 +310,15 @@ public class Vault {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty showingStatsProperty() {
|
||||||
|
return showingStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isShowingStats() {
|
||||||
|
return accessPointPresent.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
// Getter/Setter
|
// Getter/Setter
|
||||||
// *******************************************************************************/
|
// *******************************************************************************/
|
||||||
@@ -278,6 +327,12 @@ public class Vault {
|
|||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UnverifiedVaultConfig getUnverifiedVaultConfig() throws IOException {
|
||||||
|
Path configPath = getPath().resolve(org.cryptomator.common.Constants.VAULTCONFIG_FILENAME);
|
||||||
|
String token = Files.readString(configPath, StandardCharsets.US_ASCII);
|
||||||
|
return VaultConfig.decode(token);
|
||||||
|
}
|
||||||
|
|
||||||
public Observable[] observables() {
|
public Observable[] observables() {
|
||||||
return new Observable[]{state};
|
return new Observable[]{state};
|
||||||
}
|
}
|
||||||
@@ -319,6 +374,10 @@ public class Vault {
|
|||||||
return vaultSettings.getId();
|
return vaultSettings.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<Volume> getVolume() {
|
||||||
|
return Optional.ofNullable(this.volume);
|
||||||
|
}
|
||||||
|
|
||||||
// ******************************************************************************
|
// ******************************************************************************
|
||||||
// Hashcode / Equals
|
// Hashcode / Equals
|
||||||
// *******************************************************************************/
|
// *******************************************************************************/
|
||||||
@@ -330,8 +389,7 @@ public class Vault {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof Vault && obj.getClass().equals(this.getClass())) {
|
if (obj instanceof Vault other && obj.getClass().equals(this.getClass())) {
|
||||||
final Vault other = (Vault) obj;
|
|
||||||
return Objects.equals(this.vaultSettings, other.vaultSettings);
|
return Objects.equals(this.vaultSettings, other.vaultSettings);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -6,15 +6,15 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import dagger.BindsInstance;
|
import dagger.BindsInstance;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
|
|
||||||
import dagger.Subcomponent;
|
import dagger.Subcomponent;
|
||||||
|
import org.cryptomator.common.mountpoint.MountPointChooserModule;
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
@PerVault
|
@PerVault
|
||||||
@Subcomponent(modules = {VaultModule.class})
|
@Subcomponent(modules = {VaultModule.class, MountPointChooserModule.class})
|
||||||
public interface VaultComponent {
|
public interface VaultComponent {
|
||||||
|
|
||||||
Vault vault();
|
Vault vault();
|
||||||
@@ -26,7 +26,7 @@ public interface VaultComponent {
|
|||||||
Builder vaultSettings(VaultSettings vaultSettings);
|
Builder vaultSettings(VaultSettings vaultSettings);
|
||||||
|
|
||||||
@BindsInstance
|
@BindsInstance
|
||||||
Builder initialVaultState(VaultState vaultState);
|
Builder initialVaultState(VaultState.Value vaultState);
|
||||||
|
|
||||||
@BindsInstance
|
@BindsInstance
|
||||||
Builder initialErrorCause(@Nullable @Named("lastKnownException") Exception initialErrorCause);
|
Builder initialErrorCause(@Nullable @Named("lastKnownException") Exception initialErrorCause);
|
||||||
|
|||||||
+5
-5
@@ -1,9 +1,9 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -20,12 +20,12 @@ class VaultListChangeListener implements ListChangeListener<Vault> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChanged(Change<? extends Vault> c) {
|
public void onChanged(Change<? extends Vault> c) {
|
||||||
while(c.next()) {
|
while (c.next()) {
|
||||||
if (c.wasAdded()) {
|
if (c.wasAdded()) {
|
||||||
List<VaultSettings> addedSettings = c.getAddedSubList().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
|
List<VaultSettings> addedSettings = c.getAddedSubList().stream().map(Vault::getVaultSettings).toList();
|
||||||
vaultSettingsList.addAll(c.getFrom(), addedSettings);
|
vaultSettingsList.addAll(c.getFrom(), addedSettings);
|
||||||
} else if (c.wasRemoved()) {
|
} else if (c.wasRemoved()) {
|
||||||
List<VaultSettings> removedSettings = c.getRemoved().stream().map(Vault::getVaultSettings).collect(Collectors.toList());
|
List<VaultSettings> removedSettings = c.getRemoved().stream().map(Vault::getVaultSettings).toList();
|
||||||
vaultSettingsList.removeAll(removedSettings);
|
vaultSettingsList.removeAll(removedSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,26 +8,29 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||||
|
import org.cryptomator.cryptofs.DirStructure;
|
||||||
import org.cryptomator.cryptofs.migration.Migrators;
|
import org.cryptomator.cryptofs.migration.Migrators;
|
||||||
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 javax.inject.Singleton;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||||
|
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
|
||||||
|
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class VaultListManager {
|
public class VaultListManager {
|
||||||
@@ -36,10 +39,12 @@ public class VaultListManager {
|
|||||||
|
|
||||||
private final VaultComponent.Builder vaultComponentBuilder;
|
private final VaultComponent.Builder vaultComponentBuilder;
|
||||||
private final ObservableList<Vault> vaultList;
|
private final ObservableList<Vault> vaultList;
|
||||||
|
private final String defaultVaultName;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VaultListManager(VaultComponent.Builder vaultComponentBuilder, Settings settings) {
|
public VaultListManager(VaultComponent.Builder vaultComponentBuilder, ResourceBundle resourceBundle, Settings settings) {
|
||||||
this.vaultComponentBuilder = vaultComponentBuilder;
|
this.vaultComponentBuilder = vaultComponentBuilder;
|
||||||
|
this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName");
|
||||||
this.vaultList = FXCollections.observableArrayList(Vault::observables);
|
this.vaultList = FXCollections.observableArrayList(Vault::observables);
|
||||||
|
|
||||||
addAll(settings.getDirectories());
|
addAll(settings.getDirectories());
|
||||||
@@ -50,25 +55,33 @@ public class VaultListManager {
|
|||||||
return vaultList;
|
return vaultList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vault add(Path pathToVault) throws NoSuchFileException {
|
public Vault add(Path pathToVault) throws IOException {
|
||||||
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
|
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
|
||||||
if (!CryptoFileSystemProvider.containsVault(normalizedPathToVault, MASTERKEY_FILENAME)) {
|
if (CryptoFileSystemProvider.checkDirStructureForVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) {
|
||||||
throw new NoSuchFileException(normalizedPathToVault.toString(), null, "Not a vault directory");
|
throw new NoSuchFileException(normalizedPathToVault.toString(), null, "Not a vault directory");
|
||||||
}
|
}
|
||||||
Optional<Vault> alreadyExistingVault = get(normalizedPathToVault);
|
|
||||||
if (alreadyExistingVault.isPresent()) {
|
return get(normalizedPathToVault) //
|
||||||
return alreadyExistingVault.get();
|
.orElseGet(() -> {
|
||||||
|
Vault newVault = create(newVaultSettings(normalizedPathToVault));
|
||||||
|
vaultList.add(newVault);
|
||||||
|
return newVault;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private VaultSettings newVaultSettings(Path path) {
|
||||||
|
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
||||||
|
vaultSettings.path().set(path);
|
||||||
|
if (path.getFileName() != null) {
|
||||||
|
vaultSettings.displayName().set(path.getFileName().toString());
|
||||||
} else {
|
} else {
|
||||||
VaultSettings vaultSettings = VaultSettings.withRandomId();
|
vaultSettings.displayName().set(defaultVaultName);
|
||||||
vaultSettings.path().set(normalizedPathToVault);
|
|
||||||
Vault newVault = create(vaultSettings);
|
|
||||||
vaultList.add(newVault);
|
|
||||||
return newVault;
|
|
||||||
}
|
}
|
||||||
|
return vaultSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAll(Collection<VaultSettings> vaultSettings) {
|
private void addAll(Collection<VaultSettings> vaultSettings) {
|
||||||
Collection<Vault> vaults = vaultSettings.stream().map(this::create).collect(Collectors.toList());
|
Collection<Vault> vaults = vaultSettings.stream().map(this::create).toList();
|
||||||
vaultList.addAll(vaults);
|
vaultList.addAll(vaults);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,43 +96,45 @@ public class VaultListManager {
|
|||||||
private Vault create(VaultSettings vaultSettings) {
|
private Vault create(VaultSettings vaultSettings) {
|
||||||
VaultComponent.Builder compBuilder = vaultComponentBuilder.vaultSettings(vaultSettings);
|
VaultComponent.Builder compBuilder = vaultComponentBuilder.vaultSettings(vaultSettings);
|
||||||
try {
|
try {
|
||||||
VaultState vaultState = determineVaultState(vaultSettings.path().get());
|
VaultState.Value vaultState = determineVaultState(vaultSettings.path().get());
|
||||||
compBuilder.initialVaultState(vaultState);
|
compBuilder.initialVaultState(vaultState);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Failed to determine vault state for " + vaultSettings.path().get(), e);
|
LOG.warn("Failed to determine vault state for " + vaultSettings.path().get(), e);
|
||||||
compBuilder.initialVaultState(VaultState.ERROR);
|
compBuilder.initialVaultState(ERROR);
|
||||||
compBuilder.initialErrorCause(e);
|
compBuilder.initialErrorCause(e);
|
||||||
}
|
}
|
||||||
return compBuilder.build().vault();
|
return compBuilder.build().vault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VaultState redetermineVaultState(Vault vault) {
|
public static VaultState.Value redetermineVaultState(Vault vault) {
|
||||||
VaultState previousState = vault.getState();
|
VaultState state = vault.stateProperty();
|
||||||
|
VaultState.Value previousState = state.getValue();
|
||||||
return switch (previousState) {
|
return switch (previousState) {
|
||||||
case LOCKED, NEEDS_MIGRATION, MISSING -> {
|
case LOCKED, NEEDS_MIGRATION, MISSING -> {
|
||||||
try {
|
try {
|
||||||
VaultState determinedState = determineVaultState(vault.getPath());
|
var determinedState = determineVaultState(vault.getPath());
|
||||||
vault.setState(determinedState);
|
state.set(determinedState);
|
||||||
yield determinedState;
|
yield determinedState;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
|
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
|
||||||
vault.setState(VaultState.ERROR);
|
state.set(ERROR);
|
||||||
vault.setLastKnownException(e);
|
vault.setLastKnownException(e);
|
||||||
yield VaultState.ERROR;
|
yield ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ERROR, UNLOCKED, PROCESSING -> previousState;
|
case ERROR, UNLOCKED, PROCESSING -> previousState;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static VaultState determineVaultState(Path pathToVault) throws IOException {
|
private static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
|
||||||
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
|
if (!Files.exists(pathToVault)) {
|
||||||
return VaultState.MISSING;
|
return VaultState.Value.MISSING;
|
||||||
} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {
|
|
||||||
return VaultState.NEEDS_MIGRATION;
|
|
||||||
} else {
|
|
||||||
return VaultState.LOCKED;
|
|
||||||
}
|
}
|
||||||
|
return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
|
||||||
|
case VAULT -> VaultState.Value.LOCKED;
|
||||||
|
case UNRELATED -> VaultState.Value.MISSING;
|
||||||
|
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? VaultState.Value.NEEDS_MIGRATION : VaultState.Value.MISSING;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,6 @@ package org.cryptomator.common.vaults;
|
|||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.binding.StringBinding;
|
|
||||||
import javafx.beans.property.BooleanProperty;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
@@ -25,6 +17,12 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -42,12 +40,6 @@ public class VaultModule {
|
|||||||
return new AtomicReference<>();
|
return new AtomicReference<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@PerVault
|
|
||||||
public ObjectProperty<VaultState> provideVaultState(VaultState initialState) {
|
|
||||||
return new SimpleObjectProperty<>(initialState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Named("lastKnownException")
|
@Named("lastKnownException")
|
||||||
@PerVault
|
@PerVault
|
||||||
@@ -55,7 +47,6 @@ public class VaultModule {
|
|||||||
return new SimpleObjectProperty<>(initialErrorCause);
|
return new SimpleObjectProperty<>(initialErrorCause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@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();
|
||||||
@@ -77,7 +68,7 @@ public class VaultModule {
|
|||||||
@DefaultMountFlags
|
@DefaultMountFlags
|
||||||
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
public StringBinding provideDefaultMountFlags(Settings settings, VaultSettings vaultSettings) {
|
||||||
ObjectProperty<VolumeImpl> preferredVolumeImpl = settings.preferredVolumeImpl();
|
ObjectProperty<VolumeImpl> preferredVolumeImpl = settings.preferredVolumeImpl();
|
||||||
StringProperty mountName = vaultSettings.mountName();
|
StringBinding mountName = vaultSettings.mountName();
|
||||||
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
|
BooleanProperty readOnly = vaultSettings.usesReadOnlyMode();
|
||||||
|
|
||||||
return Bindings.createStringBinding(() -> {
|
return Bindings.createStringBinding(() -> {
|
||||||
@@ -86,6 +77,8 @@ public class VaultModule {
|
|||||||
return getMacFuseDefaultMountFlags(mountName, readOnly);
|
return getMacFuseDefaultMountFlags(mountName, readOnly);
|
||||||
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_LINUX) {
|
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_LINUX) {
|
||||||
return getLinuxFuseDefaultMountFlags(readOnly);
|
return getLinuxFuseDefaultMountFlags(readOnly);
|
||||||
|
} else if (v == VolumeImpl.FUSE && SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
return getWindowsFuseDefaultMountFlags(mountName, readOnly);
|
||||||
} else if (v == VolumeImpl.DOKANY && SystemUtils.IS_OS_WINDOWS) {
|
} else if (v == VolumeImpl.DOKANY && SystemUtils.IS_OS_WINDOWS) {
|
||||||
return getDokanyDefaultMountFlags(readOnly);
|
return getDokanyDefaultMountFlags(readOnly);
|
||||||
} else {
|
} else {
|
||||||
@@ -95,17 +88,16 @@ public class VaultModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
|
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
|
||||||
private String getMacFuseDefaultMountFlags(ReadOnlyStringProperty mountName, ReadOnlyBooleanProperty readOnly) {
|
private String getMacFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
|
||||||
assert SystemUtils.IS_OS_MAC_OSX;
|
assert SystemUtils.IS_OS_MAC_OSX;
|
||||||
StringBuilder flags = new StringBuilder();
|
StringBuilder flags = new StringBuilder();
|
||||||
if (readOnly.get()) {
|
if (readOnly.get()) {
|
||||||
flags.append(" -ordonly");
|
flags.append(" -ordonly");
|
||||||
}
|
}
|
||||||
flags.append(" -ovolname=").append(mountName.get());
|
flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
|
||||||
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");
|
||||||
flags.append(" -omodules=iconv,from_code=UTF-8,to_code=UTF-8-MAC"); // show files names in Unicode NFD encoding
|
|
||||||
flags.append(" -onoappledouble"); // vastly impacts performance for some reason...
|
flags.append(" -onoappledouble"); // vastly impacts performance for some reason...
|
||||||
flags.append(" -odefault_permissions"); // let the kernel assume permissions based on file attributes etc
|
flags.append(" -odefault_permissions"); // let the kernel assume permissions based on file attributes etc
|
||||||
|
|
||||||
@@ -144,6 +136,28 @@ public class VaultModule {
|
|||||||
return flags.toString().strip();
|
return flags.toString().strip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse_main.c#L53-L62 for syntax guide
|
||||||
|
// see https://github.com/billziss-gh/winfsp/blob/5d0b10d0b643652c00ebb4704dc2bb28e7244973/src/dll/fuse/fuse.c#L295-L319 for options (-o <...>)
|
||||||
|
// see https://github.com/billziss-gh/winfsp/wiki/Frequently-Asked-Questions/5ba00e4be4f5e938eaae6ef1500b331de12dee77 (FUSE 4.) on why the given defaults were choosen
|
||||||
|
private String getWindowsFuseDefaultMountFlags(StringBinding mountName, ReadOnlyBooleanProperty readOnly) {
|
||||||
|
assert SystemUtils.IS_OS_WINDOWS;
|
||||||
|
StringBuilder flags = new StringBuilder();
|
||||||
|
|
||||||
|
//WinFSP has no explicit "readonly"-option, nut not setting the group/user-id has the same effect, tho.
|
||||||
|
//So for the time being not setting them is the way to go...
|
||||||
|
//See: https://github.com/billziss-gh/winfsp/issues/319
|
||||||
|
if (!readOnly.get()) {
|
||||||
|
flags.append(" -ouid=-1");
|
||||||
|
flags.append(" -ogid=11");
|
||||||
|
}
|
||||||
|
flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
|
||||||
|
//Dokany requires this option to be set, WinFSP doesn't seem to share this peculiarity,
|
||||||
|
//but the option exists. Let's keep this here in case we need it.
|
||||||
|
// flags.append(" -oThreadCount=").append(5);
|
||||||
|
|
||||||
|
return flags.toString().strip();
|
||||||
|
}
|
||||||
|
|
||||||
// see https://github.com/cryptomator/dokany-nio-adapter/blob/develop/src/main/java/org/cryptomator/frontend/dokany/MountUtil.java#L30-L34
|
// see https://github.com/cryptomator/dokany-nio-adapter/blob/develop/src/main/java/org/cryptomator/frontend/dokany/MountUtil.java#L30-L34
|
||||||
private String getDokanyDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
|
private String getDokanyDefaultMountFlags(ReadOnlyBooleanProperty readOnly) {
|
||||||
assert SystemUtils.IS_OS_WINDOWS;
|
assert SystemUtils.IS_OS_WINDOWS;
|
||||||
|
|||||||
@@ -1,34 +1,141 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
public enum VaultState {
|
import com.google.common.base.Preconditions;
|
||||||
/**
|
import org.slf4j.Logger;
|
||||||
* No vault found at the provided path
|
import org.slf4j.LoggerFactory;
|
||||||
*/
|
|
||||||
MISSING,
|
import javax.inject.Inject;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.value.ObservableObjectValue;
|
||||||
|
import javafx.beans.value.ObservableValueBase;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.concurrent.locks.Condition;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
@PerVault
|
||||||
|
public class VaultState extends ObservableValueBase<VaultState.Value> implements ObservableObjectValue<VaultState.Value> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(VaultState.class);
|
||||||
|
|
||||||
|
public enum Value {
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final AtomicReference<Value> value;
|
||||||
|
private final Lock lock = new ReentrantLock();
|
||||||
|
private final Condition valueChanged = lock.newCondition();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public VaultState(VaultState.Value initialValue) {
|
||||||
|
this.value = new AtomicReference<>(initialValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Value get() {
|
||||||
|
return getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Value getValue() {
|
||||||
|
return value.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vault requires migration to a newer vault format
|
* Transitions from <code>fromState</code> to <code>toState</code>.
|
||||||
|
*
|
||||||
|
* @param fromState Previous state
|
||||||
|
* @param toState New state
|
||||||
|
* @return <code>true</code> if successful
|
||||||
*/
|
*/
|
||||||
NEEDS_MIGRATION,
|
public boolean transition(Value fromState, Value toState) {
|
||||||
|
Preconditions.checkArgument(fromState != toState, "fromState must be different than toState");
|
||||||
|
boolean success = value.compareAndSet(fromState, toState);
|
||||||
|
if (success) {
|
||||||
|
fireValueChangedEvent();
|
||||||
|
} else {
|
||||||
|
LOG.debug("Failed transiting into state {}: Expected state was not{}.", fromState, toState);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(Value newState) {
|
||||||
|
var oldState = value.getAndSet(newState);
|
||||||
|
if (oldState != newState) {
|
||||||
|
fireValueChangedEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vault ready to be unlocked
|
* Waits for the specified time, until the desired state is reached.
|
||||||
|
*
|
||||||
|
* @param desiredState what state to wait for
|
||||||
|
* @param time the maximum time to wait
|
||||||
|
* @param unit the time unit of the {@code time} argument
|
||||||
|
* @return {@code false} if the waiting time detectably elapsed before reaching {@code desiredState}
|
||||||
|
* @throws InterruptedException if the current thread is interrupted
|
||||||
*/
|
*/
|
||||||
LOCKED,
|
public boolean awaitState(Value desiredState, long time, TimeUnit unit) throws InterruptedException {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
long remaining = TimeUnit.NANOSECONDS.convert(time, unit);
|
||||||
|
while (value.get() != desiredState) {
|
||||||
|
if (remaining <= 0L) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
remaining = valueChanged.awaitNanos(remaining);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
private void signal() {
|
||||||
* Vault in transition between two other states
|
lock.lock();
|
||||||
*/
|
try {
|
||||||
PROCESSING,
|
valueChanged.signalAll();
|
||||||
|
} finally {
|
||||||
/**
|
lock.unlock();
|
||||||
* Vault is unlocked
|
}
|
||||||
*/
|
}
|
||||||
UNLOCKED,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unknown state due to preceeding unrecoverable exceptions.
|
|
||||||
*/
|
|
||||||
ERROR;
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void fireValueChangedEvent() {
|
||||||
|
signal();
|
||||||
|
if (Platform.isFxApplicationThread()) {
|
||||||
|
super.fireValueChangedEvent();
|
||||||
|
} else {
|
||||||
|
Platform.runLater(super::fireValueChangedEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.Observable;
|
|
||||||
import javafx.beans.property.LongProperty;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleLongProperty;
|
|
||||||
import javafx.concurrent.ScheduledService;
|
|
||||||
import javafx.concurrent.Task;
|
|
||||||
import javafx.util.Duration;
|
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystemStats;
|
import org.cryptomator.cryptofs.CryptoFileSystemStats;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.Observable;
|
||||||
|
import javafx.beans.property.DoubleProperty;
|
||||||
|
import javafx.beans.property.LongProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
|
import javafx.beans.property.SimpleLongProperty;
|
||||||
|
import javafx.concurrent.ScheduledService;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import javafx.util.Duration;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
@@ -24,13 +26,22 @@ public class VaultStats {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(VaultStats.class);
|
private static final Logger LOG = LoggerFactory.getLogger(VaultStats.class);
|
||||||
|
|
||||||
private final AtomicReference<CryptoFileSystem> fs;
|
private final AtomicReference<CryptoFileSystem> fs;
|
||||||
private final ObjectProperty<VaultState> state;
|
private final VaultState state;
|
||||||
private final ScheduledService<Optional<CryptoFileSystemStats>> updateService;
|
private final ScheduledService<Optional<CryptoFileSystemStats>> updateService;
|
||||||
private final LongProperty bytesPerSecondRead = new SimpleLongProperty();
|
private final LongProperty bytesPerSecondRead = new SimpleLongProperty();
|
||||||
private final LongProperty bytesPerSecondWritten = new SimpleLongProperty();
|
private final LongProperty bytesPerSecondWritten = new SimpleLongProperty();
|
||||||
|
private final LongProperty bytesPerSecondEncrypted = new SimpleLongProperty();
|
||||||
|
private final LongProperty bytesPerSecondDecrypted = new SimpleLongProperty();
|
||||||
|
private final DoubleProperty cacheHitRate = new SimpleDoubleProperty();
|
||||||
|
private final LongProperty toalBytesRead = new SimpleLongProperty();
|
||||||
|
private final LongProperty toalBytesWritten = new SimpleLongProperty();
|
||||||
|
private final LongProperty totalBytesEncrypted = new SimpleLongProperty();
|
||||||
|
private final LongProperty totalBytesDecrypted = new SimpleLongProperty();
|
||||||
|
private final LongProperty filesRead = new SimpleLongProperty();
|
||||||
|
private final LongProperty filesWritten = new SimpleLongProperty();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VaultStats(AtomicReference<CryptoFileSystem> fs, ObjectProperty<VaultState> state, ExecutorService executor) {
|
VaultStats(AtomicReference<CryptoFileSystem> fs, VaultState state, ExecutorService executor) {
|
||||||
this.fs = fs;
|
this.fs = fs;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.updateService = new UpdateStatsService();
|
this.updateService = new UpdateStatsService();
|
||||||
@@ -41,24 +52,48 @@ public class VaultStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void vaultStateChanged(@SuppressWarnings("unused") Observable observable) {
|
private void vaultStateChanged(@SuppressWarnings("unused") Observable observable) {
|
||||||
if (VaultState.UNLOCKED.equals(state.get())) {
|
if (VaultState.Value.UNLOCKED == state.get()) {
|
||||||
assert fs.get() != null;
|
assert fs.get() != null;
|
||||||
LOG.debug("start recording stats");
|
LOG.debug("start recording stats");
|
||||||
updateService.restart();
|
Platform.runLater(() -> updateService.restart());
|
||||||
} else {
|
} else {
|
||||||
LOG.debug("stop recording stats");
|
LOG.debug("stop recording stats");
|
||||||
updateService.cancel();
|
Platform.runLater(() -> updateService.cancel());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStats(Optional<CryptoFileSystemStats> stats) {
|
private void updateStats(Optional<CryptoFileSystemStats> stats) {
|
||||||
assert Platform.isFxApplicationThread();
|
assert Platform.isFxApplicationThread();
|
||||||
bytesPerSecondRead.set(stats.map(CryptoFileSystemStats::pollBytesRead).orElse(0l));
|
bytesPerSecondRead.set(stats.map(CryptoFileSystemStats::pollBytesRead).orElse(0L));
|
||||||
bytesPerSecondWritten.set(stats.map(CryptoFileSystemStats::pollBytesWritten).orElse(0l));
|
bytesPerSecondWritten.set(stats.map(CryptoFileSystemStats::pollBytesWritten).orElse(0L));
|
||||||
|
cacheHitRate.set(stats.map(this::getCacheHitRate).orElse(0.0));
|
||||||
|
bytesPerSecondDecrypted.set(stats.map(CryptoFileSystemStats::pollBytesDecrypted).orElse(0L));
|
||||||
|
bytesPerSecondEncrypted.set(stats.map(CryptoFileSystemStats::pollBytesEncrypted).orElse(0L));
|
||||||
|
toalBytesRead.set(stats.map(CryptoFileSystemStats::pollTotalBytesRead).orElse(0L));
|
||||||
|
toalBytesWritten.set(stats.map(CryptoFileSystemStats::pollTotalBytesWritten).orElse(0L));
|
||||||
|
totalBytesEncrypted.set(stats.map(CryptoFileSystemStats::pollTotalBytesEncrypted).orElse(0L));
|
||||||
|
totalBytesDecrypted.set(stats.map(CryptoFileSystemStats::pollTotalBytesDecrypted).orElse(0L));
|
||||||
|
filesRead.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesRead).orElse(0L));
|
||||||
|
filesWritten.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesWritten).orElse(0L));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private double getCacheHitRate(CryptoFileSystemStats stats) {
|
||||||
|
long accesses = stats.pollChunkCacheAccesses();
|
||||||
|
long hits = stats.pollChunkCacheHits();
|
||||||
|
if (accesses == 0) {
|
||||||
|
return 0.0;
|
||||||
|
} else {
|
||||||
|
return hits / (double) accesses;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UpdateStatsService extends ScheduledService<Optional<CryptoFileSystemStats>> {
|
private class UpdateStatsService extends ScheduledService<Optional<CryptoFileSystemStats>> {
|
||||||
|
|
||||||
|
private UpdateStatsService() {
|
||||||
|
setOnFailed(event -> LOG.error("Error in UpdateStateService.", getException()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Task<Optional<CryptoFileSystemStats>> createTask() {
|
protected Task<Optional<CryptoFileSystemStats>> createTask() {
|
||||||
return new Task<>() {
|
return new Task<>() {
|
||||||
@@ -94,4 +129,50 @@ public class VaultStats {
|
|||||||
public long getBytesPerSecondWritten() {
|
public long getBytesPerSecondWritten() {
|
||||||
return bytesPerSecondWritten.get();
|
return bytesPerSecondWritten.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LongProperty bytesPerSecondEncryptedProperty() {
|
||||||
|
return bytesPerSecondEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getBytesPerSecondEnrypted() {
|
||||||
|
return bytesPerSecondEncrypted.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongProperty bytesPerSecondDecryptedProperty() {
|
||||||
|
return bytesPerSecondDecrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getBytesPerSecondDecrypted() {
|
||||||
|
return bytesPerSecondDecrypted.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DoubleProperty cacheHitRateProperty() { return cacheHitRate; }
|
||||||
|
|
||||||
|
public double getCacheHitRate() {
|
||||||
|
return cacheHitRate.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LongProperty toalBytesReadProperty() {return toalBytesRead;}
|
||||||
|
|
||||||
|
public long getTotalBytesRead() { return toalBytesRead.get();}
|
||||||
|
|
||||||
|
public LongProperty toalBytesWrittenProperty() {return toalBytesWritten;}
|
||||||
|
|
||||||
|
public long getTotalBytesWritten() { return toalBytesWritten.get();}
|
||||||
|
|
||||||
|
public LongProperty totalBytesEncryptedProperty() {return totalBytesEncrypted;}
|
||||||
|
|
||||||
|
public long getTotalBytesEncrypted() { return totalBytesEncrypted.get();}
|
||||||
|
|
||||||
|
public LongProperty totalBytesDecryptedProperty() {return totalBytesDecrypted;}
|
||||||
|
|
||||||
|
public long getTotalBytesDecrypted() { return totalBytesDecrypted.get();}
|
||||||
|
|
||||||
|
public LongProperty filesRead() { return filesRead;}
|
||||||
|
|
||||||
|
public long getFilesRead() { return filesRead.get();}
|
||||||
|
|
||||||
|
public LongProperty filesWritten() {return filesWritten;}
|
||||||
|
|
||||||
|
public long getFilesWritten() {return filesWritten.get();}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
import org.cryptomator.common.mountpoint.InvalidMountPointException;
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,18 +23,35 @@ public interface Volume {
|
|||||||
*/
|
*/
|
||||||
boolean isSupported();
|
boolean isSupported();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the coresponding enum type of the {@link VolumeImpl volume implementation ("VolumeImpl")} that is implemented by this Volume.
|
||||||
|
*
|
||||||
|
* @return the type of implementation as defined by the {@link VolumeImpl VolumeImpl enum}
|
||||||
|
*/
|
||||||
|
VolumeImpl getImplementationType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param fs
|
* @param fs
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
void mount(CryptoFileSystem fs, String mountFlags) throws IOException, VolumeException;
|
void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws IOException, VolumeException, InvalidMountPointException;
|
||||||
|
|
||||||
void reveal() throws VolumeException;
|
/**
|
||||||
|
* Reveals the mounted volume.
|
||||||
|
* <p>
|
||||||
|
* The given {@code revealer} might be used to do it, but not necessarily.
|
||||||
|
*
|
||||||
|
* @param revealer An object capable of revealing the location of the mounted vault to view the content (e.g. in the default file browser).
|
||||||
|
* @throws VolumeException
|
||||||
|
*/
|
||||||
|
void reveal(Revealer revealer) throws VolumeException;
|
||||||
|
|
||||||
void unmount() throws VolumeException;
|
void unmount() throws VolumeException;
|
||||||
|
|
||||||
Optional<Path> getMountPoint();
|
Optional<Path> getMountPoint();
|
||||||
|
|
||||||
|
MountPointRequirement getMountPointRequirement();
|
||||||
|
|
||||||
// optional forced unmounting:
|
// optional forced unmounting:
|
||||||
|
|
||||||
default boolean supportsForcedUnmount() {
|
default boolean supportsForcedUnmount() {
|
||||||
@@ -69,4 +89,14 @@ public interface Volume {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides and unifies the different Revealer implementations in the different nio-adapters.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
interface Revealer {
|
||||||
|
|
||||||
|
void reveal(Path p) throws VolumeException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.common.base.CharMatcher;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||||
import org.cryptomator.frontend.webdav.mount.MountParams;
|
import org.cryptomator.frontend.webdav.mount.MountParams;
|
||||||
@@ -15,6 +17,8 @@ import java.net.InetAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class WebDavVolume implements Volume {
|
public class WebDavVolume implements Volume {
|
||||||
|
|
||||||
@@ -23,55 +27,71 @@ public class WebDavVolume implements Volume {
|
|||||||
private final Provider<WebDavServer> serverProvider;
|
private final Provider<WebDavServer> serverProvider;
|
||||||
private final VaultSettings vaultSettings;
|
private final VaultSettings vaultSettings;
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
|
private final WindowsDriveLetters windowsDriveLetters;
|
||||||
|
|
||||||
private WebDavServer server;
|
private WebDavServer server;
|
||||||
private WebDavServletController servlet;
|
private WebDavServletController servlet;
|
||||||
private Mounter.Mount mount;
|
private Mounter.Mount mount;
|
||||||
private Path mountPoint;
|
private Consumer<Throwable> onExitAction;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings) {
|
public WebDavVolume(Provider<WebDavServer> serverProvider, VaultSettings vaultSettings, Settings settings, WindowsDriveLetters windowsDriveLetters) {
|
||||||
this.serverProvider = serverProvider;
|
this.serverProvider = serverProvider;
|
||||||
this.vaultSettings = vaultSettings;
|
this.vaultSettings = vaultSettings;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
this.windowsDriveLetters = windowsDriveLetters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mount(CryptoFileSystem fs, String mountFlags) throws VolumeException {
|
public void mount(CryptoFileSystem fs, String mountFlags, Consumer<Throwable> onExitAction) throws VolumeException {
|
||||||
|
startServlet(fs);
|
||||||
|
mountServlet();
|
||||||
|
this.onExitAction = onExitAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startServlet(CryptoFileSystem fs) {
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
server = serverProvider.get();
|
server = serverProvider.get();
|
||||||
}
|
}
|
||||||
if (!server.isRunning()) {
|
if (!server.isRunning()) {
|
||||||
server.start();
|
server.start();
|
||||||
}
|
}
|
||||||
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + vaultSettings.mountName().get());
|
CharMatcher acceptable = CharMatcher.inRange('0', '9').or(CharMatcher.inRange('A', 'Z')).or(CharMatcher.inRange('a', 'z'));
|
||||||
|
String urlConformMountName = acceptable.negate().collapseFrom(vaultSettings.mountName().get(), '_');
|
||||||
|
servlet = server.createWebDavServlet(fs.getPath("/"), vaultSettings.getId() + "/" + urlConformMountName);
|
||||||
servlet.start();
|
servlet.start();
|
||||||
mount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mount() throws VolumeException {
|
private void mountServlet() throws VolumeException {
|
||||||
if (servlet == null) {
|
if (servlet == null) {
|
||||||
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet.");
|
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//on windows, prevent an automatic drive letter selection in the upstream library. Either we choose already a specifc one or there is no free.
|
||||||
|
Supplier<String> driveLetterSupplier;
|
||||||
|
if (System.getProperty("os.name").toLowerCase().contains("windows") && vaultSettings.winDriveLetter().isEmpty().get()) {
|
||||||
|
driveLetterSupplier = () -> windowsDriveLetters.getAvailableDriveLetter().orElse(null);
|
||||||
|
} else {
|
||||||
|
driveLetterSupplier = () -> vaultSettings.winDriveLetter().get();
|
||||||
|
}
|
||||||
|
|
||||||
MountParams mountParams = MountParams.create() //
|
MountParams mountParams = MountParams.create() //
|
||||||
.withWindowsDriveLetter(vaultSettings.winDriveLetter().get()) //
|
.withWindowsDriveLetter(driveLetterSupplier.get()) //
|
||||||
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get().getPrefix())//
|
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get().getPrefix())//
|
||||||
.withWebdavHostname(getLocalhostAliasOrNull()) //
|
.withWebdavHostname(getLocalhostAliasOrNull()) //
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
this.mount = servlet.mount(mountParams); // might block this thread for a while
|
this.mount = servlet.mount(mountParams); // might block this thread for a while
|
||||||
} catch (Mounter.CommandFailedException e) {
|
} catch (Mounter.CommandFailedException e) {
|
||||||
e.printStackTrace();
|
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reveal() throws VolumeException {
|
public void reveal(Revealer revealer) throws VolumeException {
|
||||||
try {
|
try {
|
||||||
mount.reveal();
|
mount.reveal(revealer::reveal);
|
||||||
} catch (Mounter.CommandFailedException e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
|
||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,6 +104,7 @@ public class WebDavVolume implements Volume {
|
|||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
cleanup();
|
cleanup();
|
||||||
|
onExitAction.accept(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -94,11 +115,17 @@ public class WebDavVolume implements Volume {
|
|||||||
throw new VolumeException(e);
|
throw new VolumeException(e);
|
||||||
}
|
}
|
||||||
cleanup();
|
cleanup();
|
||||||
|
onExitAction.accept(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Path> getMountPoint() {
|
public Optional<Path> getMountPoint() {
|
||||||
return Optional.ofNullable(mountPoint);
|
return mount.getMountPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MountPointRequirement getMountPointRequirement() {
|
||||||
|
return MountPointRequirement.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getLocalhostAliasOrNull() {
|
private String getLocalhostAliasOrNull() {
|
||||||
@@ -126,6 +153,11 @@ public class WebDavVolume implements Volume {
|
|||||||
return WebDavVolume.isSupportedStatic();
|
return WebDavVolume.isSupportedStatic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VolumeImpl getImplementationType() {
|
||||||
|
return VolumeImpl.WEBDAV;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsForcedUnmount() {
|
public boolean supportsForcedUnmount() {
|
||||||
return mount != null && mount.forced().isPresent();
|
return mount != null && mount.forced().isPresent();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ import javax.inject.Inject;
|
|||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
@@ -24,7 +26,7 @@ public final class WindowsDriveLetters {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
try (IntStream stream = IntStream.rangeClosed('C', 'Z')) {
|
try (IntStream stream = IntStream.rangeClosed('C', 'Z')) {
|
||||||
C_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(Collectors.toSet());
|
C_TO_Z = stream.mapToObj(i -> String.valueOf((char) i)).collect(ImmutableSet.toImmutableSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +48,18 @@ public final class WindowsDriveLetters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getAvailableDriveLetters() {
|
public Set<String> getAvailableDriveLetters() {
|
||||||
return Sets.difference(C_TO_Z, getOccupiedDriveLetters());
|
return Sets.difference(getAllDriveLetters(), getOccupiedDriveLetters());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<String> getAvailableDriveLetter() {
|
||||||
|
return getAvailableDriveLetters().stream().findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Path> getAvailableDriveLetterPath() {
|
||||||
|
return getAvailableDriveLetter().map(this::toPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path toPath(String driveLetter) {
|
||||||
|
return Path.of(driveLetter + ":\\");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
@@ -20,14 +21,10 @@ class EnvironmentTest {
|
|||||||
|
|
||||||
private Environment env;
|
private Environment env;
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
static void init() {
|
|
||||||
System.setProperty("user.home", "/home/testuser");
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void initEach() {
|
void init() {
|
||||||
env = new Environment();
|
env = Mockito.spy(new Environment());
|
||||||
|
Mockito.when(env.getHomeDir()).thenReturn(Path.of("/home/testuser"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -35,9 +32,9 @@ class EnvironmentTest {
|
|||||||
public void testSettingsPath() {
|
public void testSettingsPath() {
|
||||||
System.setProperty("cryptomator.settingsPath", "~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json");
|
System.setProperty("cryptomator.settingsPath", "~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json");
|
||||||
|
|
||||||
List<Path> result = env.getSettingsPath().collect(Collectors.toList());
|
List<Path> result = env.getSettingsPath().toList();
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"),
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"), //
|
||||||
Paths.get("/home/testuser/.Cryptomator/settings.json")));
|
Paths.get("/home/testuser/.Cryptomator/settings.json")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,9 +43,9 @@ class EnvironmentTest {
|
|||||||
public void testIpcPortPath() {
|
public void testIpcPortPath() {
|
||||||
System.setProperty("cryptomator.ipcPortPath", "~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin");
|
System.setProperty("cryptomator.ipcPortPath", "~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin");
|
||||||
|
|
||||||
List<Path> result = env.getIpcPortPath().collect(Collectors.toList());
|
List<Path> result = env.getIpcPortPath().toList();
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
MatcherAssert.assertThat(result, Matchers.hasSize(2));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"),
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"), //
|
||||||
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
|
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +54,7 @@ class EnvironmentTest {
|
|||||||
public void testKeychainPath() {
|
public void testKeychainPath() {
|
||||||
System.setProperty("cryptomator.keychainPath", "~/AppData/Roaming/Cryptomator/keychain.json");
|
System.setProperty("cryptomator.keychainPath", "~/AppData/Roaming/Cryptomator/keychain.json");
|
||||||
|
|
||||||
List<Path> result = env.getKeychainPath().collect(Collectors.toList());
|
List<Path> result = env.getKeychainPath().toList();
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/AppData/Roaming/Cryptomator/keychain.json")));
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/AppData/Roaming/Cryptomator/keychain.json")));
|
||||||
}
|
}
|
||||||
@@ -91,7 +88,7 @@ class EnvironmentTest {
|
|||||||
@DisplayName("test.path.property=")
|
@DisplayName("test.path.property=")
|
||||||
public void testEmptyList() {
|
public void testEmptyList() {
|
||||||
System.setProperty("test.path.property", "");
|
System.setProperty("test.path.property", "");
|
||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").toList();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(0));
|
MatcherAssert.assertThat(result, Matchers.hasSize(0));
|
||||||
}
|
}
|
||||||
@@ -100,7 +97,7 @@ class EnvironmentTest {
|
|||||||
@DisplayName("test.path.property=/foo/bar/test")
|
@DisplayName("test.path.property=/foo/bar/test")
|
||||||
public void testSingleAbsolutePath() {
|
public void testSingleAbsolutePath() {
|
||||||
System.setProperty("test.path.property", "/foo/bar/test");
|
System.setProperty("test.path.property", "/foo/bar/test");
|
||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").toList();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||||
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/foo/bar/test")));
|
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/foo/bar/test")));
|
||||||
@@ -110,7 +107,7 @@ class EnvironmentTest {
|
|||||||
@DisplayName("test.path.property=~/test")
|
@DisplayName("test.path.property=~/test")
|
||||||
public void testSingleHomeRelativePath() {
|
public void testSingleHomeRelativePath() {
|
||||||
System.setProperty("test.path.property", "~/test");
|
System.setProperty("test.path.property", "~/test");
|
||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").toList();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
MatcherAssert.assertThat(result, Matchers.hasSize(1));
|
||||||
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/home/testuser/test")));
|
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/home/testuser/test")));
|
||||||
@@ -120,11 +117,11 @@ class EnvironmentTest {
|
|||||||
@DisplayName("test.path.property=~/test:~/test2:/foo/bar/test")
|
@DisplayName("test.path.property=~/test:~/test2:/foo/bar/test")
|
||||||
public void testMultiplePaths() {
|
public void testMultiplePaths() {
|
||||||
System.setProperty("test.path.property", "~/test:~/test2:/foo/bar/test");
|
System.setProperty("test.path.property", "~/test:~/test2:/foo/bar/test");
|
||||||
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
|
List<Path> result = env.getPaths("test.path.property").toList();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, Matchers.hasSize(3));
|
MatcherAssert.assertThat(result, Matchers.hasSize(3));
|
||||||
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"),
|
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"), //
|
||||||
Paths.get("/home/testuser/test2"),
|
Paths.get("/home/testuser/test2"), //
|
||||||
Paths.get("/foo/bar/test")));
|
Paths.get("/foo/bar/test")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.cryptomator.common;
|
package org.cryptomator.common;
|
||||||
|
|
||||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
import org.cryptomator.common.LicenseChecker;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -10,11 +9,13 @@ import java.util.Optional;
|
|||||||
|
|
||||||
class LicenseCheckerTest {
|
class LicenseCheckerTest {
|
||||||
|
|
||||||
private static final String PUBLIC_KEY = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ" //
|
private static final String PUBLIC_KEY = """
|
||||||
+ "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" //
|
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\
|
||||||
+ "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" //
|
PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\
|
||||||
+ "Al8G7CqwoJOsW7Kddns=";
|
6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\
|
||||||
|
Al8G7CqwoJOsW7Kddns=\
|
||||||
|
""";
|
||||||
|
|
||||||
private LicenseChecker licenseChecker;
|
private LicenseChecker licenseChecker;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@@ -25,9 +26,9 @@ class LicenseCheckerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testCheckValidLicense() {
|
public void testCheckValidLicense() {
|
||||||
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
||||||
|
|
||||||
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
||||||
|
|
||||||
Assertions.assertTrue(decoded.isPresent());
|
Assertions.assertTrue(decoded.isPresent());
|
||||||
Assertions.assertEquals("cryptobot@example.com", decoded.get().getSubject());
|
Assertions.assertEquals("cryptobot@example.com", decoded.get().getSubject());
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-12
@@ -1,27 +1,30 @@
|
|||||||
package org.cryptomator.keychain;
|
package org.cryptomator.common.keychain;
|
||||||
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
|
||||||
class KeychainManagerTest {
|
public class KeychainManagerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStoreAndLoad() throws KeychainAccessException {
|
public void testStoreAndLoad() throws KeychainAccessException {
|
||||||
KeychainManager keychainManager = new KeychainManager(new MapKeychainAccess());
|
KeychainManager keychainManager = new KeychainManager(new SimpleObjectProperty<>(new MapKeychainAccess()));
|
||||||
keychainManager.storePassphrase("test", "asd");
|
keychainManager.storePassphrase("test", "asd");
|
||||||
Assertions.assertArrayEquals("asd".toCharArray(), keychainManager.loadPassphrase("test"));
|
Assertions.assertArrayEquals("asd".toCharArray(), keychainManager.loadPassphrase("test"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
public static class WhenObservingProperties {
|
public static class WhenObservingProperties {
|
||||||
|
|
||||||
@@ -31,15 +34,15 @@ class KeychainManagerTest {
|
|||||||
Platform.startup(latch::countDown);
|
Platform.startup(latch::countDown);
|
||||||
latch.await(5, TimeUnit.SECONDS);
|
latch.await(5, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPropertyChangesWhenStoringPassword() throws KeychainAccessException, InterruptedException {
|
public void testPropertyChangesWhenStoringPassword() throws KeychainAccessException, InterruptedException {
|
||||||
KeychainManager keychainManager = new KeychainManager(new MapKeychainAccess());
|
KeychainManager keychainManager = new KeychainManager(new SimpleObjectProperty<>(new MapKeychainAccess()));
|
||||||
ReadOnlyBooleanProperty property = keychainManager.getPassphraseStoredProperty("test");
|
ReadOnlyBooleanProperty property = keychainManager.getPassphraseStoredProperty("test");
|
||||||
Assertions.assertEquals(false, property.get());
|
Assertions.assertEquals(false, property.get());
|
||||||
|
|
||||||
keychainManager.storePassphrase("test", "bar");
|
keychainManager.storePassphrase("test", "bar");
|
||||||
|
|
||||||
AtomicBoolean result = new AtomicBoolean(false);
|
AtomicBoolean result = new AtomicBoolean(false);
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
@@ -49,7 +52,7 @@ class KeychainManagerTest {
|
|||||||
latch.await(1, TimeUnit.SECONDS);
|
latch.await(1, TimeUnit.SECONDS);
|
||||||
Assertions.assertEquals(true, result.get());
|
Assertions.assertEquals(true, result.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+9
-2
@@ -3,12 +3,14 @@
|
|||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
* are made available under the terms of the accompanying LICENSE file.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.keychain;
|
package org.cryptomator.common.keychain;
|
||||||
|
|
||||||
|
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
class MapKeychainAccess implements KeychainAccessStrategy {
|
class MapKeychainAccess implements KeychainAccessProvider {
|
||||||
|
|
||||||
private final Map<String, char[]> map = new HashMap<>();
|
private final Map<String, char[]> map = new HashMap<>();
|
||||||
|
|
||||||
@@ -42,4 +44,9 @@ class MapKeychainAccess implements KeychainAccessStrategy {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocked() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+20
-12
@@ -5,26 +5,34 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class SettingsJsonAdapterTest {
|
public class SettingsJsonAdapterTest {
|
||||||
|
|
||||||
private final SettingsJsonAdapter adapter = new SettingsJsonAdapter();
|
private final Environment env = Mockito.mock(Environment.class);
|
||||||
|
private final SettingsJsonAdapter adapter = new SettingsJsonAdapter(env);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeserialize() throws IOException {
|
public void testDeserialize() throws IOException {
|
||||||
String vault1Json = "{\"id\": \"1\", \"path\": \"/vault1\", \"mountName\": \"vault1\", \"winDriveLetter\": \"X\"}";
|
String json = """
|
||||||
String vault2Json = "{\"id\": \"2\", \"path\": \"/vault2\", \"mountName\": \"vault2\", \"winDriveLetter\": \"Y\"}";
|
{
|
||||||
String json = "{\"directories\": [" + vault1Json + "," + vault2Json + "]," //
|
"directories": [
|
||||||
+ "\"checkForUpdatesEnabled\": true,"//
|
{"id": "1", "path": "/vault1", "mountName": "vault1", "winDriveLetter": "X"},
|
||||||
+ "\"port\": 8080,"//
|
{"id": "2", "path": "/vault2", "mountName": "vault2", "winDriveLetter": "Y"}
|
||||||
+ "\"numTrayNotifications\": 42,"//
|
],
|
||||||
+ "\"preferredVolumeImpl\": \"FUSE\"}";
|
"checkForUpdatesEnabled": true,
|
||||||
|
"port": 8080,
|
||||||
|
"numTrayNotifications": 42,
|
||||||
|
"preferredVolumeImpl": "FUSE"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
Settings settings = adapter.fromJson(json);
|
Settings settings = adapter.fromJson(json);
|
||||||
|
|
||||||
@@ -37,10 +45,10 @@ public class SettingsJsonAdapterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest(name = "fromJson() should throw IOException for input: {0}")
|
@ParameterizedTest(name = "fromJson() should throw IOException for input: {0}")
|
||||||
@ValueSource(strings = {
|
@ValueSource(strings = { //
|
||||||
"",
|
"", //
|
||||||
"<html>",
|
"<html>", //
|
||||||
"{invalidjson}"
|
"{invalidjson}" //
|
||||||
})
|
})
|
||||||
public void testDeserializeMalformed(String input) {
|
public void testDeserializeMalformed(String input) {
|
||||||
Assertions.assertThrows(IOException.class, () -> {
|
Assertions.assertThrows(IOException.class, () -> {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
@@ -14,8 +15,10 @@ public class SettingsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAutoSave() {
|
public void testAutoSave() {
|
||||||
|
Environment env = Mockito.mock(Environment.class);
|
||||||
@SuppressWarnings("unchecked") Consumer<Settings> changeListener = Mockito.mock(Consumer.class);
|
@SuppressWarnings("unchecked") Consumer<Settings> changeListener = Mockito.mock(Consumer.class);
|
||||||
Settings settings = new Settings();
|
|
||||||
|
Settings settings = new Settings(env);
|
||||||
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);
|
||||||
@@ -29,7 +32,7 @@ public class SettingsTest {
|
|||||||
Mockito.verify(changeListener, Mockito.times(2)).accept(settings);
|
Mockito.verify(changeListener, Mockito.times(2)).accept(settings);
|
||||||
|
|
||||||
// third change (to property of list item):
|
// third change (to property of list item):
|
||||||
vaultSettings.mountName().set("asd");
|
vaultSettings.displayName().set("asd");
|
||||||
Mockito.verify(changeListener, Mockito.times(3)).accept(settings);
|
Mockito.verify(changeListener, Mockito.times(3)).accept(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -23,13 +23,13 @@ public class VaultSettingsJsonAdapterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeserialize() throws IOException {
|
public void testDeserialize() throws IOException {
|
||||||
String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":\"--foo --bar\"}";
|
String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"displayName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true, \"individualMountPath\": \"/home/test/crypto\", \"mountFlags\":\"--foo --bar\"}";
|
||||||
JsonReader jsonReader = new JsonReader(new StringReader(json));
|
JsonReader jsonReader = new JsonReader(new StringReader(json));
|
||||||
|
|
||||||
VaultSettings vaultSettings = adapter.read(jsonReader);
|
VaultSettings vaultSettings = adapter.read(jsonReader);
|
||||||
Assertions.assertEquals("foo", vaultSettings.getId());
|
Assertions.assertEquals("foo", vaultSettings.getId());
|
||||||
Assertions.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
Assertions.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
||||||
Assertions.assertEquals("test", vaultSettings.mountName().get());
|
Assertions.assertEquals("test", vaultSettings.displayName().get());
|
||||||
Assertions.assertEquals("X", vaultSettings.winDriveLetter().get());
|
Assertions.assertEquals("X", vaultSettings.winDriveLetter().get());
|
||||||
Assertions.assertEquals("/home/test/crypto", vaultSettings.customMountPath().get());
|
Assertions.assertEquals("/home/test/crypto", vaultSettings.customMountPath().get());
|
||||||
Assertions.assertEquals("--foo --bar", vaultSettings.mountFlags().get());
|
Assertions.assertEquals("--foo --bar", vaultSettings.mountFlags().get());
|
||||||
@@ -41,7 +41,7 @@ public class VaultSettingsJsonAdapterTest {
|
|||||||
public void testSerialize() throws IOException {
|
public void testSerialize() throws IOException {
|
||||||
VaultSettings vaultSettings = new VaultSettings("test");
|
VaultSettings vaultSettings = new VaultSettings("test");
|
||||||
vaultSettings.path().set(Paths.get("/foo/bar"));
|
vaultSettings.path().set(Paths.get("/foo/bar"));
|
||||||
vaultSettings.mountName().set("mountyMcMountFace");
|
vaultSettings.displayName().set("mountyMcMountFace");
|
||||||
vaultSettings.mountFlags().set("--foo --bar");
|
vaultSettings.mountFlags().set("--foo --bar");
|
||||||
|
|
||||||
StringWriter buf = new StringWriter();
|
StringWriter buf = new StringWriter();
|
||||||
@@ -50,12 +50,12 @@ public class VaultSettingsJsonAdapterTest {
|
|||||||
String result = buf.toString();
|
String result = buf.toString();
|
||||||
|
|
||||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"id\":\"test\""));
|
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"id\":\"test\""));
|
||||||
if(System.getProperty("os.name").contains("Windows")){
|
if (System.getProperty("os.name").contains("Windows")) {
|
||||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"\\\\foo\\\\bar\""));
|
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"\\\\foo\\\\bar\""));
|
||||||
} else {
|
} else {
|
||||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"/foo/bar\""));
|
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"path\":\"/foo/bar\""));
|
||||||
}
|
}
|
||||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountName\":\"mountyMcMountFace\""));
|
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"displayName\":\"mountyMcMountFace\""));
|
||||||
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountFlags\":\"--foo --bar\""));
|
MatcherAssert.assertThat(result, CoreMatchers.containsString("\"mountFlags\":\"--foo --bar\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,19 +8,19 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.common.settings;
|
package org.cryptomator.common.settings;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
public class VaultSettingsTest {
|
public class VaultSettingsTest {
|
||||||
|
|
||||||
@Test
|
@ParameterizedTest
|
||||||
public void testNormalize() throws Exception {
|
@CsvSource({"a\u000Fa,a_a", ": \\,_ _", "汉语,汉语", "..,_", "a\ta,a\u0020a", "\t\n\r,_"})
|
||||||
assertEquals("_", VaultSettings.normalizeMountName(" "));
|
public void testNormalize(String test, String expected) {
|
||||||
assertEquals("a", VaultSettings.normalizeMountName("ä"));
|
VaultSettings settings = new VaultSettings("id");
|
||||||
assertEquals("C", VaultSettings.normalizeMountName("Ĉ"));
|
settings.displayName().setValue(test);
|
||||||
assertEquals("_", VaultSettings.normalizeMountName(":"));
|
assertEquals(expected, settings.normalizeDisplayName());
|
||||||
assertEquals("_", VaultSettings.normalizeMountName("汉语"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
package org.cryptomator.common.vaults;
|
package org.cryptomator.common.vaults;
|
||||||
|
|
||||||
import javafx.beans.binding.StringBinding;
|
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.VaultSettings;
|
import org.cryptomator.common.settings.VaultSettings;
|
||||||
import org.cryptomator.common.settings.VolumeImpl;
|
import org.cryptomator.common.settings.VolumeImpl;
|
||||||
@@ -17,6 +13,11 @@ import org.junit.jupiter.api.condition.OS;
|
|||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public class VaultModuleTest {
|
public class VaultModuleTest {
|
||||||
@@ -28,8 +29,9 @@ public class VaultModuleTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setup(@TempDir Path tmpDir) {
|
public void setup(@TempDir Path tmpDir) {
|
||||||
Mockito.when(vaultSettings.mountName()).thenReturn(new SimpleStringProperty("TEST"));
|
Mockito.when(vaultSettings.mountName()).thenReturn(Bindings.createStringBinding(() -> "TEST"));
|
||||||
Mockito.when(vaultSettings.usesReadOnlyMode()).thenReturn(new SimpleBooleanProperty(true));
|
Mockito.when(vaultSettings.usesReadOnlyMode()).thenReturn(new SimpleBooleanProperty(true));
|
||||||
|
Mockito.when(vaultSettings.displayName()).thenReturn(new SimpleStringProperty("Vault"));
|
||||||
System.setProperty("user.home", tmpDir.toString());
|
System.setProperty("user.home", tmpDir.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ public class VaultModuleTest {
|
|||||||
|
|
||||||
StringBinding result = module.provideDefaultMountFlags(settings, vaultSettings);
|
StringBinding result = module.provideDefaultMountFlags(settings, vaultSettings);
|
||||||
|
|
||||||
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ovolname=TEST"));
|
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ovolname=\"TEST\""));
|
||||||
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ordonly"));
|
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ordonly"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>main</artifactId>
|
|
||||||
<version>1.5.6</version>
|
|
||||||
</parent>
|
|
||||||
<artifactId>keychain</artifactId>
|
|
||||||
<name>System Keychain Access</name>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.cryptomator</groupId>
|
|
||||||
<artifactId>commons</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- JavaFx -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.openjfx</groupId>
|
|
||||||
<artifactId>javafx-base</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.openjfx</groupId>
|
|
||||||
<artifactId>javafx-graphics</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Apache -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.commons</groupId>
|
|
||||||
<artifactId>commons-lang3</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Google -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.code.gson</groupId>
|
|
||||||
<artifactId>gson</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.guava</groupId>
|
|
||||||
<artifactId>guava</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- DI -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.dagger</groupId>
|
|
||||||
<artifactId>dagger</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- secret-service lib -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>de.swiesend</groupId>
|
|
||||||
<artifactId>secret-service</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Logging -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-simple</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package org.cryptomator.keychain;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates an error during communication with the operating system's keychain.
|
|
||||||
*/
|
|
||||||
public class KeychainAccessException extends Exception {
|
|
||||||
|
|
||||||
KeychainAccessException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.keychain;
|
|
||||||
|
|
||||||
interface KeychainAccessStrategy {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Associates a passphrase with a given key.
|
|
||||||
*
|
|
||||||
* @param key Key used to retrieve the passphrase via {@link #loadPassphrase(String)}.
|
|
||||||
* @param passphrase The secret to store in this keychain.
|
|
||||||
*/
|
|
||||||
void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
|
|
||||||
* @return The stored passphrase for the given key or <code>null</code> if no value for the given key could be found.
|
|
||||||
*/
|
|
||||||
char[] loadPassphrase(String key) throws KeychainAccessException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a passphrase with a given key.
|
|
||||||
*
|
|
||||||
* @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
|
|
||||||
*/
|
|
||||||
void deletePassphrase(String key) throws KeychainAccessException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a passphrase with a given key. Noop, if there is no item for the given key.
|
|
||||||
*
|
|
||||||
* @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
|
|
||||||
* @param passphrase The secret to be updated in this keychain.
|
|
||||||
*/
|
|
||||||
void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return <code>true</code> if this KeychainAccessStrategy works on the current machine.
|
|
||||||
* @implNote This method must not throw any exceptions and should fail fast
|
|
||||||
* returning <code>false</code> if it can't determine availability of the checked strategy
|
|
||||||
*/
|
|
||||||
boolean isSupported();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.keychain;
|
|
||||||
|
|
||||||
import dagger.Binds;
|
|
||||||
import dagger.Module;
|
|
||||||
import dagger.Provides;
|
|
||||||
import dagger.multibindings.IntoSet;
|
|
||||||
import org.cryptomator.common.JniModule;
|
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Module(includes = {JniModule.class})
|
|
||||||
public abstract class KeychainModule {
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoSet
|
|
||||||
abstract KeychainAccessStrategy bindMacSystemKeychainAccess(MacSystemKeychainAccess keychainAccessStrategy);
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoSet
|
|
||||||
abstract KeychainAccessStrategy bindWindowsProtectedKeychainAccess(WindowsProtectedKeychainAccess keychainAccessStrategy);
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoSet
|
|
||||||
abstract KeychainAccessStrategy bindLinuxSecretServiceKeychainAccess(LinuxSecretServiceKeychainAccess keychainAccessStrategy);
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
static Optional<KeychainAccessStrategy> provideSupportedKeychain(Set<KeychainAccessStrategy> keychainAccessStrategies) {
|
|
||||||
return keychainAccessStrategies.stream().filter(KeychainAccessStrategy::isSupported).findFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
public static Optional<KeychainManager> provideKeychainManager(Optional<KeychainAccessStrategy> keychainAccess) {
|
|
||||||
return keychainAccess.map(KeychainManager::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-58
@@ -1,58 +0,0 @@
|
|||||||
package org.cryptomator.keychain;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A facade to LinuxSecretServiceKeychainAccessImpl that doesn't depend on libraries that are unavailable on Mac and Windows.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class LinuxSecretServiceKeychainAccess implements KeychainAccessStrategy {
|
|
||||||
|
|
||||||
// the actual implementation is hidden in this delegate object which is loaded via reflection,
|
|
||||||
// as it depends on libraries that aren't necessarily available:
|
|
||||||
private final Optional<KeychainAccessStrategy> delegate;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public LinuxSecretServiceKeychainAccess() {
|
|
||||||
this.delegate = constructGnomeKeyringKeychainAccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<KeychainAccessStrategy> constructGnomeKeyringKeychainAccess() {
|
|
||||||
try {
|
|
||||||
Class<?> clazz = Class.forName("org.cryptomator.keychain.LinuxSecretServiceKeychainAccessImpl");
|
|
||||||
KeychainAccessStrategy instance = (KeychainAccessStrategy) clazz.getDeclaredConstructor().newInstance();
|
|
||||||
return Optional.of(instance);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SystemUtils.IS_OS_LINUX && delegate.map(KeychainAccessStrategy::isSupported).orElse(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
|
||||||
delegate.orElseThrow(IllegalStateException::new).storePassphrase(key, passphrase);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char[] loadPassphrase(String key) throws KeychainAccessException {
|
|
||||||
return delegate.orElseThrow(IllegalStateException::new).loadPassphrase(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deletePassphrase(String key) throws KeychainAccessException {
|
|
||||||
delegate.orElseThrow(IllegalStateException::new).deletePassphrase(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
|
||||||
delegate.orElseThrow(IllegalStateException::new).changePassphrase(key, passphrase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-79
@@ -1,79 +0,0 @@
|
|||||||
package org.cryptomator.keychain;
|
|
||||||
|
|
||||||
import org.freedesktop.secret.simple.SimpleCollection;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
class LinuxSecretServiceKeychainAccessImpl implements KeychainAccessStrategy {
|
|
||||||
|
|
||||||
private final String LABEL_FOR_SECRET_IN_KEYRING = "Cryptomator";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
try (@SuppressWarnings("unused") SimpleCollection keyring = new SimpleCollection()) {
|
|
||||||
// seems like we're able to access the keyring.
|
|
||||||
return true;
|
|
||||||
} catch (IOException | RuntimeException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void storePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
|
||||||
try (SimpleCollection keyring = new SimpleCollection()) {
|
|
||||||
List<String> list = keyring.getItems(createAttributes(key));
|
|
||||||
if (list == null) {
|
|
||||||
keyring.createItem(LABEL_FOR_SECRET_IN_KEYRING, passphrase, createAttributes(key));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new KeychainAccessException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char[] loadPassphrase(String key) throws KeychainAccessException {
|
|
||||||
try (SimpleCollection keyring = new SimpleCollection()) {
|
|
||||||
List<String> list = keyring.getItems(createAttributes(key));
|
|
||||||
if (list != null) {
|
|
||||||
return keyring.getSecret(list.get(0));
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new KeychainAccessException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deletePassphrase(String key) throws KeychainAccessException {
|
|
||||||
try (SimpleCollection keyring = new SimpleCollection()) {
|
|
||||||
List<String> list = keyring.getItems(createAttributes(key));
|
|
||||||
if (list != null) {
|
|
||||||
keyring.deleteItem(list.get(0));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new KeychainAccessException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
|
||||||
try (SimpleCollection keyring = new SimpleCollection()) {
|
|
||||||
List<String> list = keyring.getItems(createAttributes(key));
|
|
||||||
if (list != null) {
|
|
||||||
keyring.updateItem(list.get(0), LABEL_FOR_SECRET_IN_KEYRING, passphrase, createAttributes(key));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new KeychainAccessException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, String> createAttributes(String key) {
|
|
||||||
Map<String, String> attributes = new HashMap();
|
|
||||||
attributes.put("Vault", key);
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.keychain;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.jni.MacFunctions;
|
|
||||||
import org.cryptomator.jni.MacKeychainAccess;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class MacSystemKeychainAccess implements KeychainAccessStrategy {
|
|
||||||
|
|
||||||
private final Optional<MacFunctions> macFunctions;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public MacSystemKeychainAccess(Optional<MacFunctions> macFunctions) {
|
|
||||||
this.macFunctions = macFunctions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MacKeychainAccess keychain() {
|
|
||||||
return macFunctions.orElseThrow(IllegalStateException::new).keychainAccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void storePassphrase(String key, CharSequence passphrase) {
|
|
||||||
keychain().storePassword(key, passphrase);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char[] loadPassphrase(String key) {
|
|
||||||
return keychain().loadPassword(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SystemUtils.IS_OS_MAC_OSX && macFunctions.isPresent();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deletePassphrase(String key) {
|
|
||||||
keychain().deletePassword(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void changePassphrase(String key, CharSequence passphrase) {
|
|
||||||
if (keychain().deletePassword(key)) {
|
|
||||||
keychain().storePassword(key, passphrase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-209
@@ -1,209 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.keychain;
|
|
||||||
|
|
||||||
import com.google.common.io.BaseEncoding;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.google.gson.JsonDeserializationContext;
|
|
||||||
import com.google.gson.JsonDeserializer;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
import com.google.gson.JsonSerializationContext;
|
|
||||||
import com.google.gson.JsonSerializer;
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import org.apache.commons.lang3.SystemUtils;
|
|
||||||
import org.cryptomator.common.Environment;
|
|
||||||
import org.cryptomator.jni.WinDataProtection;
|
|
||||||
import org.cryptomator.jni.WinFunctions;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.io.Writer;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.CharBuffer;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.NoSuchFileException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.StandardOpenOption;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(WindowsProtectedKeychainAccess.class);
|
|
||||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting() //
|
|
||||||
.registerTypeHierarchyAdapter(byte[].class, new ByteArrayJsonAdapter()) //
|
|
||||||
.disableHtmlEscaping().create();
|
|
||||||
|
|
||||||
private final Optional<WinFunctions> winFunctions;
|
|
||||||
private final List<Path> keychainPaths;
|
|
||||||
private Map<String, KeychainEntry> keychainEntries;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions, Environment environment) {
|
|
||||||
this.winFunctions = winFunctions;
|
|
||||||
this.keychainPaths = environment.getKeychainPath().collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private WinDataProtection dataProtection() {
|
|
||||||
return winFunctions.orElseThrow(IllegalStateException::new).dataProtection();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void storePassphrase(String key, CharSequence passphrase) {
|
|
||||||
loadKeychainEntriesIfNeeded();
|
|
||||||
ByteBuffer buf = UTF_8.encode(CharBuffer.wrap(passphrase));
|
|
||||||
byte[] cleartext = new byte[buf.remaining()];
|
|
||||||
buf.get(cleartext);
|
|
||||||
KeychainEntry entry = new KeychainEntry();
|
|
||||||
entry.salt = generateSalt();
|
|
||||||
entry.ciphertext = dataProtection().protect(cleartext, entry.salt);
|
|
||||||
Arrays.fill(buf.array(), (byte) 0x00);
|
|
||||||
Arrays.fill(cleartext, (byte) 0x00);
|
|
||||||
keychainEntries.put(key, entry);
|
|
||||||
saveKeychainEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char[] loadPassphrase(String key) {
|
|
||||||
loadKeychainEntriesIfNeeded();
|
|
||||||
KeychainEntry entry = keychainEntries.get(key);
|
|
||||||
if (entry == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
byte[] cleartext = dataProtection().unprotect(entry.ciphertext, entry.salt);
|
|
||||||
if (cleartext == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
CharBuffer buf = UTF_8.decode(ByteBuffer.wrap(cleartext));
|
|
||||||
char[] passphrase = new char[buf.remaining()];
|
|
||||||
buf.get(passphrase);
|
|
||||||
Arrays.fill(cleartext, (byte) 0x00);
|
|
||||||
Arrays.fill(buf.array(), (char) 0x00);
|
|
||||||
return passphrase;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deletePassphrase(String key) {
|
|
||||||
loadKeychainEntriesIfNeeded();
|
|
||||||
keychainEntries.remove(key);
|
|
||||||
saveKeychainEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void changePassphrase(String key, CharSequence passphrase) {
|
|
||||||
loadKeychainEntriesIfNeeded();
|
|
||||||
if (keychainEntries.remove(key) != null) {
|
|
||||||
storePassphrase(key, passphrase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSupported() {
|
|
||||||
return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && !keychainPaths.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] generateSalt() {
|
|
||||||
byte[] result = new byte[2 * Long.BYTES];
|
|
||||||
UUID uuid = UUID.randomUUID();
|
|
||||||
ByteBuffer buf = ByteBuffer.wrap(result);
|
|
||||||
buf.putLong(uuid.getMostSignificantBits());
|
|
||||||
buf.putLong(uuid.getLeastSignificantBits());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadKeychainEntriesIfNeeded() {
|
|
||||||
if (keychainEntries == null) {
|
|
||||||
for (Path keychainPath : keychainPaths) {
|
|
||||||
Optional<Map<String, KeychainEntry>> keychain = loadKeychainEntries(keychainPath);
|
|
||||||
if (keychain.isPresent()) {
|
|
||||||
keychainEntries = keychain.get();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (keychainEntries == null) {
|
|
||||||
LOG.info("Unable to load existing keychain file, creating new keychain.");
|
|
||||||
keychainEntries = new HashMap<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<Map<String, KeychainEntry>> loadKeychainEntries(Path keychainPath) {
|
|
||||||
LOG.debug("Attempting to load keychain from {}", keychainPath);
|
|
||||||
Type type = new TypeToken<Map<String, KeychainEntry>>() {
|
|
||||||
}.getType();
|
|
||||||
try (InputStream in = Files.newInputStream(keychainPath, StandardOpenOption.READ); //
|
|
||||||
Reader reader = new InputStreamReader(in, UTF_8)) {
|
|
||||||
return Optional.of(GSON.fromJson(reader, type));
|
|
||||||
} catch (NoSuchFileException | JsonParseException e) {
|
|
||||||
return Optional.empty();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveKeychainEntries() {
|
|
||||||
if (keychainPaths.isEmpty()) {
|
|
||||||
throw new IllegalStateException("Can't save keychain if no keychain path is specified.");
|
|
||||||
}
|
|
||||||
saveKeychainEntries(keychainPaths.get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveKeychainEntries(Path keychainPath) {
|
|
||||||
try (OutputStream out = Files.newOutputStream(keychainPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
|
|
||||||
Writer writer = new OutputStreamWriter(out, UTF_8)) {
|
|
||||||
GSON.toJson(keychainEntries, writer);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class KeychainEntry {
|
|
||||||
|
|
||||||
@SerializedName("ciphertext")
|
|
||||||
byte[] ciphertext;
|
|
||||||
@SerializedName("salt")
|
|
||||||
byte[] salt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ByteArrayJsonAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
|
|
||||||
|
|
||||||
private static final BaseEncoding BASE64 = BaseEncoding.base64();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
|
||||||
return BASE64.decode(json.getAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
|
|
||||||
return new JsonPrimitive(BASE64.encode(src));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-56
@@ -1,56 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.keychain;
|
|
||||||
|
|
||||||
import org.cryptomator.common.Environment;
|
|
||||||
import org.cryptomator.jni.WinDataProtection;
|
|
||||||
import org.cryptomator.jni.WinFunctions;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
import org.mockito.Mockito;
|
|
||||||
import org.mockito.stubbing.Answer;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public class WindowsProtectedKeychainAccessTest {
|
|
||||||
|
|
||||||
private WindowsProtectedKeychainAccess keychain;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setup(@TempDir Path tempDir) {
|
|
||||||
Path keychainPath = tempDir.resolve("keychainfile.tmp");
|
|
||||||
Environment env = Mockito.mock(Environment.class);
|
|
||||||
Mockito.when(env.getKeychainPath()).thenReturn(Stream.of(keychainPath));
|
|
||||||
WinFunctions winFunctions = Mockito.mock(WinFunctions.class);
|
|
||||||
WinDataProtection winDataProtection = Mockito.mock(WinDataProtection.class);
|
|
||||||
Mockito.when(winFunctions.dataProtection()).thenReturn(winDataProtection);
|
|
||||||
Answer<byte[]> answerReturningFirstArg = invocation -> ((byte[]) invocation.getArgument(0)).clone();
|
|
||||||
Mockito.when(winDataProtection.protect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
|
|
||||||
Mockito.when(winDataProtection.unprotect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
|
|
||||||
keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions), env);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStoreAndLoad() {
|
|
||||||
String storedPw1 = "topSecret";
|
|
||||||
String storedPw2 = "bottomSecret";
|
|
||||||
keychain.storePassphrase("myPassword", storedPw1);
|
|
||||||
keychain.storePassphrase("myOtherPassword", storedPw2);
|
|
||||||
String loadedPw1 = new String(keychain.loadPassphrase("myPassword"));
|
|
||||||
String loadedPw2 = new String(keychain.loadPassphrase("myOtherPassword"));
|
|
||||||
Assertions.assertEquals(storedPw1, loadedPw1);
|
|
||||||
Assertions.assertEquals(storedPw2, loadedPw2);
|
|
||||||
keychain.deletePassphrase("myPassword");
|
|
||||||
Assertions.assertNull(keychain.loadPassphrase("myPassword"));
|
|
||||||
Assertions.assertNotNull(keychain.loadPassphrase("myOtherPassword"));
|
|
||||||
Assertions.assertNull(keychain.loadPassphrase("nonExistingPassword"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!--
|
|
||||||
Copyright (c) 2014 Markus Kreusch
|
|
||||||
This file is licensed under the terms of the MIT license.
|
|
||||||
See the LICENSE.txt file for more info.
|
|
||||||
|
|
||||||
Contributors:
|
|
||||||
Sebastian Stenzel - log4j config for WebDAV unit tests
|
|
||||||
-->
|
|
||||||
<Configuration status="WARN">
|
|
||||||
|
|
||||||
<Appenders>
|
|
||||||
<Console name="Console" target="SYSTEM_OUT">
|
|
||||||
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
|
|
||||||
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
|
|
||||||
</Console>
|
|
||||||
<Console name="StdErr" target="SYSTEM_ERR">
|
|
||||||
<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
|
|
||||||
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
|
|
||||||
</Console>
|
|
||||||
</Appenders>
|
|
||||||
|
|
||||||
<Loggers>
|
|
||||||
<!-- show our own debug messages: -->
|
|
||||||
<Logger name="org.cryptomator" level="DEBUG" />
|
|
||||||
<!-- mute dependencies: -->
|
|
||||||
<Root level="INFO">
|
|
||||||
<AppenderRef ref="Console" />
|
|
||||||
<AppenderRef ref="StdErr" />
|
|
||||||
</Root>
|
|
||||||
</Loggers>
|
|
||||||
|
|
||||||
</Configuration>
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.6</version>
|
<version>1.6.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>launcher</artifactId>
|
<artifactId>launcher</artifactId>
|
||||||
<name>Cryptomator Launcher</name>
|
<name>Cryptomator Launcher</name>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.concurrent.CountDownLatch;
|
|||||||
|
|
||||||
@Module
|
@Module
|
||||||
class CryptomatorModule {
|
class CryptomatorModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@Named("shutdownLatch")
|
@Named("shutdownLatch")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This class is licensed under the LGPL 3.0 (https://www.gnu.org/licenses/lgpl-3.0.de.html).
|
* This class is licensed under the LGPL 3.0 (https://www.gnu.org/licenses/lgpl-3.0.de.html).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.launcher;
|
package org.cryptomator.launcher;
|
||||||
@@ -25,7 +25,6 @@ import java.util.Collection;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class FileOpenRequestHandler {
|
class FileOpenRequestHandler {
|
||||||
@@ -42,7 +41,7 @@ class FileOpenRequestHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void openFiles(OpenFilesEvent evt) {
|
private void openFiles(OpenFilesEvent evt) {
|
||||||
Collection<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath).collect(Collectors.toList());
|
Collection<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath).toList();
|
||||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||||
tryToEnqueueFileOpenRequest(launchEvent);
|
tryToEnqueueFileOpenRequest(launchEvent);
|
||||||
}
|
}
|
||||||
@@ -60,7 +59,7 @@ class FileOpenRequestHandler {
|
|||||||
LOG.trace("Argument not a valid path: {}", str);
|
LOG.trace("Argument not a valid path: {}", str);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
}).filter(Objects::nonNull).toList();
|
||||||
if (!pathsToOpen.isEmpty()) {
|
if (!pathsToOpen.isEmpty()) {
|
||||||
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
AppLaunchEvent launchEvent = new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, pathsToOpen);
|
||||||
tryToEnqueueFileOpenRequest(launchEvent);
|
tryToEnqueueFileOpenRequest(launchEvent);
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import javax.inject.Singleton;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class IpcProtocolImpl implements IpcProtocol {
|
class IpcProtocolImpl implements IpcProtocol {
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ package org.cryptomator.logging;
|
|||||||
import ch.qos.logback.classic.Level;
|
import ch.qos.logback.classic.Level;
|
||||||
import ch.qos.logback.classic.Logger;
|
import ch.qos.logback.classic.Logger;
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
import ch.qos.logback.classic.LoggerContext;
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|||||||
+2
-2
@@ -5,11 +5,11 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.logging;
|
package org.cryptomator.logging;
|
||||||
|
|
||||||
|
import ch.qos.logback.core.rolling.TriggeringPolicyBase;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import ch.qos.logback.core.rolling.TriggeringPolicyBase;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers a roll-over on the first log event, so each launched application instance will rotate the log.
|
* Triggers a roll-over on the first log event, so each launched application instance will rotate the log.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class LoggerConfiguration {
|
public class LoggerConfiguration {
|
||||||
|
|
||||||
private final LoggerContext context;
|
private final LoggerContext context;
|
||||||
private final Environment environment;
|
private final Environment environment;
|
||||||
private final Appender<ILoggingEvent> stdout;
|
private final Appender<ILoggingEvent> stdout;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user