mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-14 16:51:28 +00:00
Compare commits
584 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
565ab4a09b | ||
|
|
f797491a17 | ||
|
|
cff83b6ac6 | ||
|
|
85c2901484 | ||
|
|
434030b139 | ||
|
|
e43bb37758 | ||
|
|
2eb9f0fca8 | ||
|
|
9503feb9c4 | ||
|
|
08e9f130e4 | ||
|
|
4e39eaa1f1 | ||
|
|
eaca95e8dc | ||
|
|
cf32e794c8 | ||
|
|
b5b221a5c7 | ||
|
|
d7cb99a12d | ||
|
|
b1893c63d4 | ||
|
|
b50cc703b6 | ||
|
|
9dc4a1dc5a | ||
|
|
f1a63a1679 | ||
|
|
d53d4e3452 | ||
|
|
b7fa1ae6d8 | ||
|
|
3f192a587c | ||
|
|
3a1b0b17d6 | ||
|
|
5d2a243c55 | ||
|
|
008a2a3301 | ||
|
|
0680437004 | ||
|
|
db6cf5459e | ||
|
|
1d8466a9e3 | ||
|
|
dcbcd692e7 | ||
|
|
87fd6c712a | ||
|
|
958505559f | ||
|
|
4736e5e7e7 | ||
|
|
55d217887a | ||
|
|
34ccd3ff73 | ||
|
|
989c349331 | ||
|
|
b9df56a6de | ||
|
|
38dbefff0b | ||
|
|
4cbe45919b | ||
|
|
b7ccf7986d | ||
|
|
40d632a489 | ||
|
|
800aca5207 | ||
|
|
6a26d95c15 | ||
|
|
439d3d7529 | ||
|
|
7476e192a3 | ||
|
|
c6193bc259 | ||
|
|
7446c69cd8 | ||
|
|
43c352e0d2 | ||
|
|
61ec3bc465 | ||
|
|
36dd98127d | ||
|
|
30862146b0 | ||
|
|
c19734da6f | ||
|
|
ba8fb273c7 | ||
|
|
5204a8e356 | ||
|
|
43b341860b | ||
|
|
ad02e43daf | ||
|
|
4942bcb52b | ||
|
|
6b2e718e04 | ||
|
|
a1323b5e81 | ||
|
|
ab4f487de1 | ||
|
|
d506e7472d | ||
|
|
2d33d02b46 | ||
|
|
06ddec4b70 | ||
|
|
24e31553fd | ||
|
|
eaa6b31de8 | ||
|
|
060ae588a2 | ||
|
|
262f84e8db | ||
|
|
6d55331ef2 | ||
|
|
42269a6057 | ||
|
|
181e45a596 | ||
|
|
3c8e506805 | ||
|
|
401b0afe9f | ||
|
|
42ec41b991 | ||
|
|
7ec3c5d04f | ||
|
|
f0a7379575 | ||
|
|
0d12b11c48 | ||
|
|
14a025f769 | ||
|
|
598fc9bbc8 | ||
|
|
fbab2df00f | ||
|
|
fb56b61a75 | ||
|
|
c5c3d55658 | ||
|
|
48c2d49c86 | ||
|
|
00d68393f6 | ||
|
|
7a50ba0b43 | ||
|
|
b2bee99286 | ||
|
|
88d384afdf | ||
|
|
7af82b831f | ||
|
|
a86f42fa44 | ||
|
|
70b0413777 | ||
|
|
577a77af38 | ||
|
|
3ba7e9ba00 | ||
|
|
b2250e8ce0 | ||
|
|
f2a480c7a0 | ||
|
|
bda38096e1 | ||
|
|
7e66a61294 | ||
|
|
3aa627a467 | ||
|
|
ae1b5fc925 | ||
|
|
b1076f9c86 | ||
|
|
ede1c72595 | ||
|
|
d2fcd5b64f | ||
|
|
6d9704ffa2 | ||
|
|
40df3d015a | ||
|
|
4e22706d92 | ||
|
|
341710f724 | ||
|
|
8386183f7a | ||
|
|
b76a7d6895 | ||
|
|
4a397a8151 | ||
|
|
2239a3e6a3 | ||
|
|
5318368879 | ||
|
|
41a279da9d | ||
|
|
478c69f82c | ||
|
|
eb0e630a44 | ||
|
|
0bcbf9a13a | ||
|
|
e3073a3613 | ||
|
|
3333dc7f22 | ||
|
|
12ec6fbec8 | ||
|
|
43fc976ad7 | ||
|
|
034278c0a0 | ||
|
|
92b3a9e1bd | ||
|
|
fc7169f2a0 | ||
|
|
c39710ede0 | ||
|
|
cc0b6aed15 | ||
|
|
f8a83c9cc8 | ||
|
|
43ce64f01e | ||
|
|
d585a03f76 | ||
|
|
f953be6237 | ||
|
|
e81bbe197b | ||
|
|
738fa4da12 | ||
|
|
9a08dcc46d | ||
|
|
74ef8d915d | ||
|
|
025a7a5582 | ||
|
|
3b2ddcf98d | ||
|
|
c10fc0ee5b | ||
|
|
aaa37e2c7a | ||
|
|
7af7c920e3 | ||
|
|
a4f79fb98a | ||
|
|
f429f2d3e7 | ||
|
|
4f6e091c13 | ||
|
|
2df779f7ab | ||
|
|
5b9d9150c5 | ||
|
|
f0aaec2058 | ||
|
|
4adc4a9175 | ||
|
|
5a97060fac | ||
|
|
db96074119 | ||
|
|
7ecc10bc79 | ||
|
|
44fe4a6f8a | ||
|
|
76a4ef50cb | ||
|
|
ca2f80024a | ||
|
|
dcd7077b08 | ||
|
|
31482d7d18 | ||
|
|
5ef666154e | ||
|
|
a810eff797 | ||
|
|
664158eb84 | ||
|
|
6b43881909 | ||
|
|
126004b1f8 | ||
|
|
ba34cfa9d5 | ||
|
|
03b4ad85ef | ||
|
|
1abfcd495f | ||
|
|
69964a80f1 | ||
|
|
aa8306ea4a | ||
|
|
3fecde37f4 | ||
|
|
2fc5fd99fb | ||
|
|
7edacfea70 | ||
|
|
7020fa49d9 | ||
|
|
7cea0bb33c | ||
|
|
3d622b18dc | ||
|
|
62cd506588 | ||
|
|
cd830f27a0 | ||
|
|
3f75018369 | ||
|
|
270a4c594d | ||
|
|
a5fdc5755b | ||
|
|
0a5a554714 | ||
|
|
976f22c27a | ||
|
|
8267574697 | ||
|
|
9077c964b3 | ||
|
|
f80467a3a4 | ||
|
|
8c462c4cb7 | ||
|
|
b25b5bd5a3 | ||
|
|
6365c22297 | ||
|
|
d7dd24b94e | ||
|
|
aee8844558 | ||
|
|
c536149c62 | ||
|
|
ca1ae8181b | ||
|
|
2b19c9757b | ||
|
|
d6871e3d82 | ||
|
|
0432a64537 | ||
|
|
06988b06c7 | ||
|
|
9b72eb8219 | ||
|
|
ea188f6176 | ||
|
|
cf67d022c8 | ||
|
|
7ba6e12799 | ||
|
|
b512518ccc | ||
|
|
93dfd35627 | ||
|
|
50b92f9510 | ||
|
|
7cca8922d8 | ||
|
|
c00889b1c4 | ||
|
|
b6224f355d | ||
|
|
9a8f96d432 | ||
|
|
3cc199227b | ||
|
|
3d0647bce3 | ||
|
|
192f35a9dd | ||
|
|
f2b4c9a35b | ||
|
|
3b8fec4c5a | ||
|
|
60b74a018d | ||
|
|
be69e04f51 | ||
|
|
194f6009df | ||
|
|
910a2eace8 | ||
|
|
3c95618eec | ||
|
|
fc709eb700 | ||
|
|
a6c60ac5d0 | ||
|
|
b88b9c8f92 | ||
|
|
2b8cd36b7b | ||
|
|
3d23deb744 | ||
|
|
de3b2715c7 | ||
|
|
f3d8bd359d | ||
|
|
d7d6e46b2a | ||
|
|
501f682d68 | ||
|
|
7cbf1e10e5 | ||
|
|
fb00972bc1 | ||
|
|
387fb28c65 | ||
|
|
40fbfd10d1 | ||
|
|
e5e4695cd6 | ||
|
|
4a0c8ae80e | ||
|
|
384ca8eb2a | ||
|
|
59f360bde7 | ||
|
|
8830e0f5fb | ||
|
|
7a391a2825 | ||
|
|
a1941911ef | ||
|
|
a0667ff361 | ||
|
|
cbc41d535d | ||
|
|
f51780dcb5 | ||
|
|
c055488df7 | ||
|
|
b15c376e59 | ||
|
|
fdf03d2fdc | ||
|
|
437350af9f | ||
|
|
5114e2ad22 | ||
|
|
b3b4f61c42 | ||
|
|
fa16556af1 | ||
|
|
de766c2653 | ||
|
|
dcc27efe5c | ||
|
|
78ceb604f9 | ||
|
|
7bdcdcba3d | ||
|
|
e929d41d67 | ||
|
|
600aca083c | ||
|
|
02c20c01ee | ||
|
|
50cfdbbc0b | ||
|
|
38c556af0b | ||
|
|
0b9d777109 | ||
|
|
eb3c1f3c7a | ||
|
|
311c490377 | ||
|
|
39ed00ff6f | ||
|
|
28338d2dcf | ||
|
|
bba5d11bdc | ||
|
|
36ef191874 | ||
|
|
3e4c6c1b61 | ||
|
|
c9d0224814 | ||
|
|
8aed6045a0 | ||
|
|
8440705436 | ||
|
|
af667b10cf | ||
|
|
1b522fe9a0 | ||
|
|
cb3c46744e | ||
|
|
c471289fb5 | ||
|
|
18ca563865 | ||
|
|
2430526ee7 | ||
|
|
c9bb0235e2 | ||
|
|
2f8236e9db | ||
|
|
e4e757606d | ||
|
|
6fcb796177 | ||
|
|
4d6b035c29 | ||
|
|
68c206a4fa | ||
|
|
d258171131 | ||
|
|
adaf9ef5ce | ||
|
|
22f299f67c | ||
|
|
0fb0bc8e1d | ||
|
|
da666410fc | ||
|
|
ea007f3cfe | ||
|
|
1c868a644f | ||
|
|
5ff6a8c258 | ||
|
|
62d7b7a0c0 | ||
|
|
7fcbb57ab1 | ||
|
|
8e25dcd396 | ||
|
|
a9f1e5b761 | ||
|
|
f642149723 | ||
|
|
4b359d98cb | ||
|
|
764fccf0b5 | ||
|
|
81d1059489 | ||
|
|
a2574a5883 | ||
|
|
118a1411d8 | ||
|
|
44310cbd0e | ||
|
|
2dce7b6f71 | ||
|
|
98db82d137 | ||
|
|
fd55656261 | ||
|
|
978dec64ee | ||
|
|
8d7bf3a370 | ||
|
|
1cea7bcffc | ||
|
|
16e77d4b9b | ||
|
|
f22111e36f | ||
|
|
c5d4c9a9c1 | ||
|
|
4bc9b1d60c | ||
|
|
7a40a3cb0c | ||
|
|
ad555ece8e | ||
|
|
2d96d2e5c6 | ||
|
|
0a968b60aa | ||
|
|
3b4f82092d | ||
|
|
de16647d00 | ||
|
|
03403e53bc | ||
|
|
2939702842 | ||
|
|
b3d09a4cae | ||
|
|
73c0af9749 | ||
|
|
53c7e031a3 | ||
|
|
1e280f2c97 | ||
|
|
ce82593fc6 | ||
|
|
c2c3d778d1 | ||
|
|
7771181e15 | ||
|
|
1a39333b4c | ||
|
|
85472db00a | ||
|
|
d6a020994e | ||
|
|
518f75be32 | ||
|
|
28c7dbad69 | ||
|
|
31e9f3a025 | ||
|
|
a03d5af144 | ||
|
|
50a654d5af | ||
|
|
1954f31910 | ||
|
|
68bf441fdd | ||
|
|
a606bcb81e | ||
|
|
fe93e51ffa | ||
|
|
f2949aae63 | ||
|
|
80e101456b | ||
|
|
2f311c1dfe | ||
|
|
c5cfe4d1b3 | ||
|
|
a09b55c81b | ||
|
|
32f266c721 | ||
|
|
3adffad087 | ||
|
|
84e3cf0762 | ||
|
|
1292936904 | ||
|
|
7a913c89c9 | ||
|
|
4c4816bdab | ||
|
|
f9bfd5d1b1 | ||
|
|
00e1e3654e | ||
|
|
8aaee3710a | ||
|
|
1946fc6c0e | ||
|
|
8fb95b67d9 | ||
|
|
e6890e8fa5 | ||
|
|
7cdb2025dc | ||
|
|
414d81dc06 | ||
|
|
b450ab028d | ||
|
|
5a51e53603 | ||
|
|
d65beb632d | ||
|
|
2b03477d31 | ||
|
|
f25e23da82 | ||
|
|
82368e2632 | ||
|
|
9057090b56 | ||
|
|
357937e0bf | ||
|
|
f84a2396eb | ||
|
|
193dcad6e8 | ||
|
|
257f4427b8 | ||
|
|
4778ba0770 | ||
|
|
77b908199e | ||
|
|
d448c42f87 | ||
|
|
b9403222ba | ||
|
|
93d0fd41a6 | ||
|
|
067bd4752c | ||
|
|
fb4f9fc68f | ||
|
|
a20667a156 | ||
|
|
4d47544244 | ||
|
|
d59f149f7c | ||
|
|
028f6e7a02 | ||
|
|
71e4f98665 | ||
|
|
d60ced52fe | ||
|
|
45c7d2b071 | ||
|
|
7a789d2eed | ||
|
|
cc1e542146 | ||
|
|
e4e84d46b6 | ||
|
|
61fc53bf9c | ||
|
|
f8386a5a99 | ||
|
|
da7e89fc72 | ||
|
|
6b57a0ad9c | ||
|
|
f1eb997804 | ||
|
|
2fe5180721 | ||
|
|
958c22bed8 | ||
|
|
6ff640648f | ||
|
|
7b132adfe2 | ||
|
|
2fa88490bd | ||
|
|
5f5b5a5426 | ||
|
|
cb054bb4f5 | ||
|
|
3497144034 | ||
|
|
b4f9bc62ca | ||
|
|
34bface2d7 | ||
|
|
70e52b1a31 | ||
|
|
59a2398c4c | ||
|
|
edbeea5502 | ||
|
|
a88e08147d | ||
|
|
8ff06a3efd | ||
|
|
902c66cf1e | ||
|
|
d58307d1d6 | ||
|
|
3e6204a657 | ||
|
|
e677a0beaa | ||
|
|
e5003c1783 | ||
|
|
d379ada100 | ||
|
|
cb7d0ade47 | ||
|
|
d6fd012f71 | ||
|
|
d9d8c84230 | ||
|
|
4d9dc4312d | ||
|
|
1ad2cc3ee5 | ||
|
|
2eac09f6be | ||
|
|
4114ea6163 | ||
|
|
82da9f7a88 | ||
|
|
aa08046c4d | ||
|
|
b6d7f63470 | ||
|
|
624d18238c | ||
|
|
3bfe2b7a9e | ||
|
|
ba79cd0f8c | ||
|
|
be63ea1104 | ||
|
|
a3ad2a5677 | ||
|
|
26e5ceea01 | ||
|
|
83f1c4ea41 | ||
|
|
8d78e1432a | ||
|
|
d2ec5a5646 | ||
|
|
6bb0328799 | ||
|
|
8a8617b480 | ||
|
|
92fad41b96 | ||
|
|
7484744038 | ||
|
|
0bbc4ecae5 | ||
|
|
9fcb166047 | ||
|
|
1ac017e3ab | ||
|
|
1ba401b8e6 | ||
|
|
07303b58f7 | ||
|
|
32a463768c | ||
|
|
2307950e3b | ||
|
|
8108128c22 | ||
|
|
58fe6da7a8 | ||
|
|
92bf73297a | ||
|
|
0bf8fb39dd | ||
|
|
0dc9a3a834 | ||
|
|
91d6264b9f | ||
|
|
d2599ea525 | ||
|
|
7180af9bc7 | ||
|
|
dc993dc57c | ||
|
|
464c74ab6c | ||
|
|
e038348dca | ||
|
|
8c7dd8c74f | ||
|
|
69bcf6fac6 | ||
|
|
7c243dd434 | ||
|
|
357a0b9c31 | ||
|
|
d69d11b82f | ||
|
|
863e9bbcb3 | ||
|
|
1c47012033 | ||
|
|
cd99eaa323 | ||
|
|
dda2afda92 | ||
|
|
5519eefcfa | ||
|
|
a81352800d | ||
|
|
6fec16e498 | ||
|
|
42f31204a3 | ||
|
|
7ab64cfe46 | ||
|
|
1581a10c04 | ||
|
|
249becc25d | ||
|
|
da8e84f39d | ||
|
|
9ad2d223c3 | ||
|
|
3bf1e659ef | ||
|
|
e79257e5ea | ||
|
|
dac517a6fb | ||
|
|
1ab808f1b0 | ||
|
|
08f81b7df4 | ||
|
|
b16f32ca83 | ||
|
|
d6ed88b544 | ||
|
|
ae697d7b73 | ||
|
|
ff3306cc17 | ||
|
|
9ae2f4e0f6 | ||
|
|
4230afcbac | ||
|
|
7fc6ab05a4 | ||
|
|
060f6bfc97 | ||
|
|
919dac6caa | ||
|
|
98bcf63b2c | ||
|
|
0585262952 | ||
|
|
a33dc3980e | ||
|
|
caa5e77386 | ||
|
|
c037920e79 | ||
|
|
3cdd352d39 | ||
|
|
8ae80e2932 | ||
|
|
2ffa1ee236 | ||
|
|
2bf5b57823 | ||
|
|
853ea69180 | ||
|
|
43d0dd99ec | ||
|
|
b40e6db701 | ||
|
|
387eb420eb | ||
|
|
7785bb8820 | ||
|
|
de4fa8c7b0 | ||
|
|
17f519e01c | ||
|
|
6c50023074 | ||
|
|
0009940e1e | ||
|
|
f2a50b59b5 | ||
|
|
ed03606981 | ||
|
|
4a1eaf25c7 | ||
|
|
f5e445a610 | ||
|
|
6a3a256c0b | ||
|
|
1ed77ebcc0 | ||
|
|
db224e9e5c | ||
|
|
c719982ef3 | ||
|
|
f783f5d5ec | ||
|
|
dd4f87b54c | ||
|
|
b3789700e1 | ||
|
|
b39834f4eb | ||
|
|
8064d75102 | ||
|
|
4064b61cd7 | ||
|
|
ef3affece3 | ||
|
|
414465371b | ||
|
|
feed72a729 | ||
|
|
bca20a7a66 | ||
|
|
a38377baaa | ||
|
|
61a45fc738 | ||
|
|
3a65b5551f | ||
|
|
94ed5c18b8 | ||
|
|
a559483d86 | ||
|
|
3acdef1dd0 | ||
|
|
def6f8ab95 | ||
|
|
11ba1f3ddc | ||
|
|
3228f2cf5f | ||
|
|
9c4f7ad79d | ||
|
|
3bd57d162b | ||
|
|
5ea73a5a8d | ||
|
|
38670838c7 | ||
|
|
3e0b84dbce | ||
|
|
2302db6206 | ||
|
|
a94bf99660 | ||
|
|
0c5ce353b1 | ||
|
|
b3ce777a42 | ||
|
|
3085df3397 | ||
|
|
fb740b605f | ||
|
|
6a74d9f3b2 | ||
|
|
1c7dffb63f | ||
|
|
a213f073b1 | ||
|
|
1ab73be1f4 | ||
|
|
8412871090 | ||
|
|
fa63f3ca67 | ||
|
|
dbc0f52481 | ||
|
|
b8cd1caeac | ||
|
|
43c25b6d97 | ||
|
|
e44bc09074 | ||
|
|
7b02f78ef5 | ||
|
|
f7e8a4d1e6 | ||
|
|
664375d692 | ||
|
|
2d968eac8c | ||
|
|
542d2fcfe1 | ||
|
|
144e929896 | ||
|
|
803748f78d | ||
|
|
3410e7243a | ||
|
|
84732337ca | ||
|
|
c527808710 | ||
|
|
99c89dbf39 | ||
|
|
829b64cd3d | ||
|
|
f4007267fb | ||
|
|
c82a0bfaf3 | ||
|
|
251ad65344 | ||
|
|
155ba4607b | ||
|
|
62f6865d3e | ||
|
|
ffb3290248 | ||
|
|
a013ae3d91 | ||
|
|
19a954e677 | ||
|
|
f262980acc | ||
|
|
3b3aa18c92 | ||
|
|
c7b8bc89c2 | ||
|
|
5fc981abd3 | ||
|
|
36ec1a5ebc | ||
|
|
be4aad4168 | ||
|
|
fcb940e29c | ||
|
|
4f3ca2a6c4 | ||
|
|
27cd34bee0 | ||
|
|
c07e51be51 | ||
|
|
0421879b39 | ||
|
|
2d627717a0 | ||
|
|
dc0e88a694 | ||
|
|
a5e3630375 | ||
|
|
17335e8f70 | ||
|
|
42dd2fba48 | ||
|
|
e1cca6427c | ||
|
|
42d3dbaa23 | ||
|
|
185d67c492 | ||
|
|
a8af3c8b40 | ||
|
|
038a7fac62 | ||
|
|
48408fa40d | ||
|
|
18a417667e | ||
|
|
aab07b13e3 | ||
|
|
869e40e351 | ||
|
|
bf91e3f15c | ||
|
|
d85c6c8f41 | ||
|
|
17057e8f8d |
6
.github/CONTRIBUTING.md
vendored
6
.github/CONTRIBUTING.md
vendored
@@ -3,7 +3,7 @@
|
||||
## Did you find a bug?
|
||||
|
||||
- Ensure you're running the latest version of Cryptomator.
|
||||
- Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS and Android app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/cryptomator-ios/issues) and [Cryptomator for Android issues list](https://github.com/cryptomator/cryptomator-android/issues) respectively.
|
||||
- Ensure the bug is related to the desktop version of Cryptomator. Bugs concerning the Cryptomator iOS and Android app can be reported on the [Cryptomator for iOS issues list](https://github.com/cryptomator/ios/issues) and [Cryptomator for Android issues list](https://github.com/cryptomator/android/issues) respectively.
|
||||
- Ensure the bug was not [already reported](https://github.com/cryptomator/cryptomator/issues). You can also check out our [FAQ](https://community.cryptomator.org/c/kb/faq).
|
||||
- If you're unable to find an open issue addressing the problem, [submit a new one](https://github.com/cryptomator/cryptomator/issues/new/choose).
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
- Suggest your change by [submitting a new issue](https://github.com/cryptomator/cryptomator/issues/new/choose) and start writing code.
|
||||
|
||||
## Do you intend to add a new translation or change an existing one?
|
||||
|
||||
Translations are not managed directly in this repository. Instead, we use [Crowdin](https://translate.cryptomator.org/), which automatically synchronizes translations with this repository. If you want to help us with translations, please visit our translation project on Crowdin.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md).
|
||||
|
||||
18
.github/ISSUE_TEMPLATE/bug.yml
vendored
18
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -1,7 +1,14 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve
|
||||
labels: ["type:bug"]
|
||||
type: "Bug"
|
||||
body:
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
placeholder: Please summarize your problem.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
@@ -11,13 +18,6 @@ body:
|
||||
required: true
|
||||
- label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
|
||||
required: true
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
placeholder: Please summarize your problem.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: software-versions
|
||||
attributes:
|
||||
@@ -97,4 +97,4 @@ body:
|
||||
id: further-info
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
|
||||
description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
|
||||
16
.github/ISSUE_TEMPLATE/feature.yml
vendored
16
.github/ISSUE_TEMPLATE/feature.yml
vendored
@@ -1,7 +1,14 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea for this project
|
||||
labels: ["type:feature-request"]
|
||||
type: "Feature"
|
||||
body:
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
placeholder: Please summarize your feature request.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
@@ -11,13 +18,6 @@ body:
|
||||
required: true
|
||||
- label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
|
||||
required: true
|
||||
- type: input
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
placeholder: Please summarize your feature request.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: motivation
|
||||
attributes:
|
||||
|
||||
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
@@ -7,6 +7,13 @@ updates:
|
||||
day: "monday"
|
||||
time: "06:00"
|
||||
timezone: "Etc/UTC"
|
||||
ignore:
|
||||
- dependency-name: "org.cryptomator:integrations-api"
|
||||
versions: ["2.0.0-alpha1"]
|
||||
- dependency-name: "jakarta.inject:jakarta.inject-api"
|
||||
versions: ["2.0.1.MR"]
|
||||
- dependency-name: "org.openjfx:*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
groups:
|
||||
java-test-dependencies:
|
||||
patterns:
|
||||
|
||||
41
.github/workflows/appimage.yml
vendored
41
.github/workflows/appimage.yml
vendored
@@ -10,8 +10,8 @@ on:
|
||||
required: false
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'zulu'
|
||||
JAVA_VERSION: '21.0.2+13'
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '23.0.2'
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
@@ -29,12 +29,12 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
appimage-suffix: x86_64
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-x64_bin-jmods.zip'
|
||||
openjfx-sha: '7baed11ca56d5fee85995fa6612d4299f1e8b7337287228f7f12fd50407c56f8'
|
||||
- os: [self-hosted, Linux, ARM64]
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_linux-x64_bin-jmods.zip'
|
||||
openjfx-sha: '063baebc6922e4a89c94b9dfb7a4f53e59e8d6fec400d4e670b31bc2ab324dec'
|
||||
- os: ubuntu-24.04-arm
|
||||
appimage-suffix: aarch64
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-aarch64_bin-jmods.zip'
|
||||
openjfx-sha: '871e7b9d7af16aef2e55c1b7830d0e0b2503b13dd8641374ba7e55ecb81d2ef9'
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_linux-aarch64_bin-jmods.zip'
|
||||
openjfx-sha: '9bbedaeae1590b69e2b22237bda310936df33e344dbc243bea2e86acaab3a0d8'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Java
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
- name: Set version
|
||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||
- name: Run maven
|
||||
run: mvn -B clean package -Plinux -DskipTests
|
||||
run: mvn -B clean package -Plinux -DskipTests -Djavafx.platform=linux
|
||||
- name: Patch target dir
|
||||
run: |
|
||||
cp LICENSE.txt target
|
||||
@@ -80,17 +80,12 @@ jobs:
|
||||
--verbose
|
||||
--output runtime
|
||||
--module-path "${JAVA_HOME}/jmods:openjfx-jmods"
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler
|
||||
--strip-native-commands
|
||||
--no-header-files
|
||||
--no-man-pages
|
||||
--strip-debug
|
||||
--compress zip-0
|
||||
- name: Prepare additional launcher
|
||||
run: envsubst '${SEMVER_STR} ${REVISION_NUM}' < dist/linux/launcher-gtk2.properties > launcher-gtk2.properties
|
||||
env:
|
||||
SEMVER_STR: ${{ needs.get-version.outputs.semVerStr }}
|
||||
REVISION_NUM: ${{ needs.get-version.outputs.revNum }}
|
||||
- name: Run jpackage
|
||||
run: >
|
||||
${JAVA_HOME}/bin/jpackage
|
||||
@@ -103,7 +98,7 @@ jobs:
|
||||
--dest appdir
|
||||
--name Cryptomator
|
||||
--vendor "Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2024 Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2025 Skymatic GmbH"
|
||||
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||
--java-options "--enable-preview"
|
||||
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator"
|
||||
@@ -121,7 +116,7 @@ jobs:
|
||||
--java-options "-Dcryptomator.showTrayIcon=true"
|
||||
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\""
|
||||
--java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.get-version.outputs.revNum }}\""
|
||||
--add-launcher Cryptomator-gtk2=launcher-gtk2.properties
|
||||
--java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\""
|
||||
--resource-dir dist/linux/resources
|
||||
- name: Patch Cryptomator.AppDir
|
||||
run: |
|
||||
@@ -138,13 +133,13 @@ jobs:
|
||||
cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop
|
||||
cp dist/linux/common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml
|
||||
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg
|
||||
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg
|
||||
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon
|
||||
ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop
|
||||
ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/org.cryptomator.Cryptomator.desktop
|
||||
ln -s org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml
|
||||
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
|
||||
- name: Download AppImageKit
|
||||
run: |
|
||||
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${{ matrix.appimage-suffix }}.AppImage -o appimagetool.AppImage
|
||||
curl -L https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${{ matrix.appimage-suffix }}.AppImage -o appimagetool.AppImage
|
||||
chmod +x appimagetool.AppImage
|
||||
./appimagetool.AppImage --appimage-extract
|
||||
- name: Prepare GPG-Agent for signing with key 615D449FE6E6A235
|
||||
@@ -158,7 +153,7 @@ jobs:
|
||||
run: >
|
||||
./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.appimage-suffix }}.AppImage
|
||||
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${{ matrix.appimage-suffix }}.AppImage.zsync'
|
||||
--sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback"
|
||||
--sign --sign-key=615D449FE6E6A235
|
||||
- name: Create detached GPG signatures
|
||||
run: |
|
||||
gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage
|
||||
@@ -173,12 +168,12 @@ jobs:
|
||||
cryptomator-*.asc
|
||||
if-no-files-found: error
|
||||
- name: Publish AppImage on GitHub Releases
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
files: |
|
||||
cryptomator-*.AppImage
|
||||
cryptomator-*.zsync
|
||||
cryptomator-*.asc
|
||||
cryptomator-*.asc
|
||||
|
||||
88
.github/workflows/av-whitelist.yml
vendored
Normal file
88
.github/workflows/av-whitelist.yml
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
name: AntiVirus Whitelisting
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
url:
|
||||
description: "Url to the file to upload"
|
||||
required: true
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
url:
|
||||
description: "Url to the file to upload"
|
||||
required: true
|
||||
type: string
|
||||
avast:
|
||||
description: "Upload to Avast"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
kaspersky:
|
||||
description: "Upload to Kaspersky"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
download-file:
|
||||
name: Downloads the file into the VM
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
fileName: ${{ steps.extractName.outputs.fileName}}
|
||||
steps:
|
||||
- name: Extract file name
|
||||
id: extractName
|
||||
run: |
|
||||
url="${{ inputs.url }}"
|
||||
echo "fileName=${url##*/}" >> $GITHUB_OUTPUT
|
||||
- name: Download file
|
||||
run: curl --remote-name ${{ inputs.url }} -L -o ${{steps.extractName.outputs.fileName}}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.extractName.outputs.fileName }}
|
||||
path: ${{ steps.extractName.outputs.fileName }}
|
||||
if-no-files-found: error
|
||||
allowlist-kaspersky:
|
||||
name: Anti Virus Allowlisting Kaspersky
|
||||
runs-on: ubuntu-latest
|
||||
needs: download-file
|
||||
if: github.event_name == 'workflow_call' || inputs.kaspersky
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ needs.download-file.outputs.fileName }}
|
||||
path: upload
|
||||
- name: Upload to Kaspersky
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
|
||||
with:
|
||||
protocol: ftps
|
||||
server: allowlist.kaspersky-labs.com
|
||||
port: 990
|
||||
username: ${{ secrets.ALLOWLIST_KASPERSKY_USERNAME }}
|
||||
password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }}
|
||||
local-dir: ./upload/
|
||||
allowlist-avast:
|
||||
name: Anti Virus Allowlisting Avast
|
||||
runs-on: ubuntu-latest
|
||||
needs: download-file
|
||||
if: github.event_name == 'workflow_call' || inputs.avast
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ needs.download-file.outputs.fileName }}
|
||||
path: upload
|
||||
- name: Upload to Avast
|
||||
uses: wlixcc/SFTP-Deploy-Action@v1.2.5
|
||||
with:
|
||||
server: whitelisting.avast.com
|
||||
port: 22
|
||||
username: ${{ secrets.ALLOWLIST_AVAST_USERNAME }}
|
||||
password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }}
|
||||
ssh_private_key: ''
|
||||
sftp_only: true
|
||||
local_path: './upload/*'
|
||||
remote_path: '/data'
|
||||
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
@@ -6,8 +6,8 @@ on:
|
||||
types: [labeled]
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'zulu'
|
||||
JAVA_VERSION: 21
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: 23
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
- name: Build and Test
|
||||
run: >
|
||||
xvfb-run
|
||||
mvn -B verify
|
||||
mvn -B verify -Djavafx.platform=linux
|
||||
jacoco:report
|
||||
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
|
||||
-Pcoverage
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
- name: Draft a release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
draft: true
|
||||
discussion_category_name: releases
|
||||
@@ -53,10 +53,25 @@ jobs:
|
||||
generate_release_notes: true
|
||||
body: |-
|
||||
:construction: Work in Progress
|
||||
### What's New 🎉
|
||||
|
||||
⏳ Please be patient, the builds are still [running](https://github.com/cryptomator/cryptomator/actions). New versions of Cryptomator can be found here in a few moments. ⏳
|
||||
### Bugfixes 🐛
|
||||
|
||||
As usual, the GPG signatures can be checked using [our public key `5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
|
||||
### Other Changes 📎
|
||||
|
||||
---
|
||||
<!-- Don't forget to include the 💾 SHA-256 checksums of release artifacts: -->
|
||||
|
||||
TODO FULL CHANGELOG
|
||||
|
||||
📜 List of closed issues is available [here](TODO)
|
||||
|
||||
---
|
||||
⏳ Please be patient, the builds are still [running](https://github.com/cryptomator/cryptomator/actions). New versions of Cryptomator can be found here in a few moments. ⏳
|
||||
|
||||
<!-- Don't forget to include the
|
||||
💾 SHA-256 checksums of release artifacts:
|
||||
```
|
||||
```
|
||||
-->
|
||||
|
||||
As usual, the GPG signatures can be checked using [our public key `5811 7AFA 1F85 B3EE C154 677D 615D 449F E6E6 A235`](https://gist.github.com/cryptobot/211111cf092037490275f39d408f461a).
|
||||
|
||||
99
.github/workflows/check-jdk-updates.yml
vendored
99
.github/workflows/check-jdk-updates.yml
vendored
@@ -1,56 +1,75 @@
|
||||
name: Checks JDK version for minor updates
|
||||
name: Check JDK for non-major updates
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 1 * *' # run once a month at the first day of month
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
JDK_VERSION: '21.0.1+12'
|
||||
JDK_VENDOR: zulu
|
||||
JDK_VERSION: '23.0.1+11'
|
||||
JDK_VENDOR: temurin
|
||||
RUNTIME_VERSION_HELPER: >
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(Runtime.version());
|
||||
}
|
||||
}
|
||||
|
||||
jobs:
|
||||
jdk-current:
|
||||
name: Check out current version
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
|
||||
steps:
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: ${{ env.JDK_VERSION }}
|
||||
distribution: ${{ env.JDK_VENDOR }}
|
||||
check-latest: false
|
||||
- name: Read JAVA_VERSION_DATE and store in env variable
|
||||
id: get-data
|
||||
run: |
|
||||
date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"')
|
||||
echo "jdk-date=${date}" >> "$GITHUB_OUTPUT"
|
||||
jdk-latest:
|
||||
check-version:
|
||||
name: Checkout latest jdk version
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
jdk-date: ${{ steps.get-data.outputs.jdk-date}}
|
||||
jdk-version: ${{ steps.get-data.outputs.jdk-version}}
|
||||
env:
|
||||
JDK_MAJOR_VERSION: 'toBeFilled'
|
||||
steps:
|
||||
- uses: actions/setup-java@v4
|
||||
- name: Determine current major version
|
||||
run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,20) >> "$env:GITHUB_ENV"
|
||||
shell: pwsh
|
||||
- name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }}
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 21
|
||||
java-version: ${{ env.JDK_MAJOR_VERSION}}
|
||||
distribution: ${{ env.JDK_VENDOR }}
|
||||
check-latest: true
|
||||
- name: Read JAVA_VERSION_DATE and store in env variable
|
||||
id: get-data
|
||||
- name: Determine if update is available
|
||||
id: determine
|
||||
shell: pwsh
|
||||
run: |
|
||||
date=$(cat ${JAVA_HOME}/release | grep "JAVA_VERSION_DATE=\"" | awk -F'=' '{print $2}' | tr -d '"')
|
||||
echo "jdk-date=${date}" >> "$GITHUB_OUTPUT"
|
||||
version=$(cat ${JAVA_HOME}/release | grep "JAVA_RUNTIME_VERSION=\"" | awk -F'=' '{print $2}' | tr -d '"')
|
||||
echo "jdk-version=${version}" >> "$GITHUB_OUTPUT"
|
||||
notify:
|
||||
name: Notifies for jdk update
|
||||
runs-on: ubuntu-latest
|
||||
needs: [jdk-current, jdk-latest]
|
||||
if: ${{ needs.jdk-latest.outputs.jdk-date }} > ${{ needs.jdk-current.outputs.jdk-date }}
|
||||
steps:
|
||||
- name: Slack Notification
|
||||
$latestVersion = 0,0,0,0 #INTERIM, UPDATE, PATCH and BUILD
|
||||
$currentVersion = 0,0,0,0
|
||||
|
||||
# Get the latest JDK runtime version
|
||||
"${env:RUNTIME_VERSION_HELPER}" | Set-Content -Path "GetRuntimeVersion.java"
|
||||
$latestVersionString = & java GetRuntimeVersion.java
|
||||
$runtimeVersionAndBuild = $latestVersionString.Split('+')
|
||||
if($runtimeVersionAndBuild.Length -eq 2) {
|
||||
$latestVersion[3]=$runtimeVersionAndBuild[1];
|
||||
}
|
||||
$tmp=$runtimeVersionAndBuild[0].Split('.')
|
||||
for($i=0;$i -lt $latestVersion.Length; $i++) {
|
||||
$latestVersion[$i]=$tmp[$i+1];
|
||||
}
|
||||
|
||||
# Get the current JDK version
|
||||
$runtimeVersionAndBuild = '${{ env.JDK_VERSION}}'.Split('+')
|
||||
if($runtimeVersionAndBuild.Length -eq 2) {
|
||||
$currentVersion[3]=$runtimeVersionAndBuild[1];
|
||||
}
|
||||
$tmp=$runtimeVersionAndBuild[0].Split('.')
|
||||
for($i=0;$i -lt $currentVersion.Length; $i++) {
|
||||
$currentVersion[$i]=$tmp[$i+1];
|
||||
}
|
||||
|
||||
# compare
|
||||
for($i=0; $i -lt $currentVersion.Length ; $i++) {
|
||||
if($latestVersion[$i] -gt $currentVersion[$i]){
|
||||
echo 'UPDATE_AVAILABLE=true' >> "$env:GITHUB_OUTPUT"
|
||||
echo "LATEST_JDK_VERSION='${latestVersionString}'" >> "$env:GITHUB_OUTPUT"
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
- name: Notify
|
||||
if: steps.determine.outputs.UPDATE_AVAILABLE == 'true'
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
env:
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
@@ -59,6 +78,6 @@ jobs:
|
||||
SLACK_ICON_EMOJI: ':bot:'
|
||||
SLACK_CHANNEL: 'cryptomator-desktop'
|
||||
SLACK_TITLE: "JDK update available"
|
||||
SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ needs.jdk-latest.outputs.jdk-version }}. See https://github.com/cryptomator/cryptomator/wiki/How-to-update-the-build-JDK for instructions."
|
||||
SLACK_MESSAGE: "Cryptomator-CI JDK can be upgraded to ${{ steps.determine.outputs.LATEST_JDK_VERSION }}. Check the Nextcloud collective for instructions."
|
||||
SLACK_FOOTER: false
|
||||
MSG_MINIMAL: true
|
||||
MSG_MINIMAL: true
|
||||
25
.github/workflows/debian.yml
vendored
25
.github/workflows/debian.yml
vendored
@@ -16,19 +16,19 @@ on:
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'zulu'
|
||||
JAVA_VERSION: '21.0.2+13'
|
||||
COFFEELIBS_JDK: 21
|
||||
COFFEELIBS_JDK_VERSION: '21.0.2+13-0ppa1'
|
||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-x64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AMD64_HASH: '7baed11ca56d5fee85995fa6612d4299f1e8b7337287228f7f12fd50407c56f8'
|
||||
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-aarch64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AARCH64_HASH: '871e7b9d7af16aef2e55c1b7830d0e0b2503b13dd8641374ba7e55ecb81d2ef9'
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '23.0.2+7'
|
||||
COFFEELIBS_JDK: 23
|
||||
COFFEELIBS_JDK_VERSION: '23.0.2+7-0ppa1'
|
||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_linux-x64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AMD64_HASH: '063baebc6922e4a89c94b9dfb7a4f53e59e8d6fec400d4e670b31bc2ab324dec'
|
||||
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_linux-aarch64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AARCH64_HASH: '9bbedaeae1590b69e2b22237bda310936df33e344dbc243bea2e86acaab3a0d8'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Debian Package
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: versions
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
run: |
|
||||
sudo add-apt-repository ppa:coffeelibs/openjdk
|
||||
sudo apt-get update
|
||||
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }} libgtk2.0-0
|
||||
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }}
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
check-latest: true
|
||||
cache: 'maven'
|
||||
- name: Run maven
|
||||
run: mvn -B clean package -Plinux -DskipTests
|
||||
run: mvn -B clean package -Plinux -Djavafx.platform=linux -DskipTests
|
||||
- name: Download OpenJFX jmods
|
||||
id: download-jmods
|
||||
run: |
|
||||
@@ -142,10 +142,9 @@ jobs:
|
||||
- name: Publish on PPA
|
||||
if: inputs.dput
|
||||
run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes
|
||||
|
||||
# If ref is a tag, also upload to GitHub Releases:
|
||||
- name: Publish Debian package on GitHub Releases
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
if: startsWith(github.ref, 'refs/tags/') && inputs.dput
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
run: |
|
||||
|
||||
5
.github/workflows/dependency-check.yml
vendored
5
.github/workflows/dependency-check.yml
vendored
@@ -7,11 +7,12 @@ on:
|
||||
|
||||
jobs:
|
||||
check-dependencies:
|
||||
uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@main
|
||||
uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@v1
|
||||
with:
|
||||
runner-os: 'ubuntu-latest'
|
||||
java-distribution: 'temurin'
|
||||
java-version: 21
|
||||
java-version: 23
|
||||
check-command: 'mvn -B validate -Pdependency-check -Djavafx.platform=linux'
|
||||
secrets:
|
||||
nvd-api-key: ${{ secrets.NVD_API_KEY }}
|
||||
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
88
.github/workflows/flathub.yml
vendored
Normal file
88
.github/workflows/flathub.yml
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
name: Create PR for flathub
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
uses: ./.github/workflows/get-version.yml
|
||||
with:
|
||||
version: ${{ inputs.tag }}
|
||||
tarball:
|
||||
name: Determines tarball url and compute checksum
|
||||
runs-on: ubuntu-latest
|
||||
needs: [get-version]
|
||||
if: github.event_name == 'workflow_dispatch' || needs.get-version.outputs.versionType == 'stable'
|
||||
outputs:
|
||||
url: ${{ steps.url.outputs.url}}
|
||||
sha512: ${{ steps.sha512.outputs.sha512}}
|
||||
steps:
|
||||
- name: Determine tarball url
|
||||
id: url
|
||||
run: |
|
||||
URL="";
|
||||
if [[ -n "${{ inputs.tag }}" ]]; then
|
||||
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ inputs.tag }}.tar.gz"
|
||||
else
|
||||
URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ github.event.release.tag_name }}.tar.gz"
|
||||
fi
|
||||
echo "url=${URL}" >> "$GITHUB_OUTPUT"
|
||||
- name: Download source tarball and compute checksum
|
||||
id: sha512
|
||||
run: |
|
||||
curl --silent --fail-with-body -L -H "Accept: application/vnd.github+json" ${{ steps.url.outputs.url }} --output cryptomator.tar.gz
|
||||
TARBALL_SHA512=$(sha512sum cryptomator.tar.gz | cut -d ' ' -f1)
|
||||
echo "sha512=${TARBALL_SHA512}" >> "$GITHUB_OUTPUT"
|
||||
flathub:
|
||||
name: Create PR for flathub
|
||||
runs-on: ubuntu-latest
|
||||
needs: [tarball, get-version]
|
||||
env:
|
||||
FLATHUB_PR_URL: tbd
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'flathub/org.cryptomator.Cryptomator'
|
||||
token: ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
||||
- name: Checkout release branch
|
||||
run: |
|
||||
git checkout -b release/${{ needs.get-version.outputs.semVerStr }}
|
||||
- name: Update build file
|
||||
run: |
|
||||
sed -i -e 's/VERSION: [0-9]\+\.[0-9]\+\.[0-9]\+.*/VERSION: ${{ needs.get-version.outputs.semVerStr }}/g' org.cryptomator.Cryptomator.yaml
|
||||
sed -i -e 's/sha512: [0-9A-Za-z_\+-]\{128\} #CRYPTOMATOR/sha512: ${{ needs.tarball.outputs.sha512 }} #CRYPTOMATOR/g' org.cryptomator.Cryptomator.yaml
|
||||
sed -i -e 's;url: https://github.com/cryptomator/cryptomator/archive/refs/tags/[^[:blank:]]\+;url: ${{ needs.tarball.outputs.url }};g' org.cryptomator.Cryptomator.yaml
|
||||
- name: Commit and push
|
||||
run: |
|
||||
git config user.name "${{ github.actor }}"
|
||||
git config user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com"
|
||||
git config push.autoSetupRemote true
|
||||
git stage .
|
||||
git commit -m "Prepare release ${{needs.get-version.outputs.semVerStr}}"
|
||||
git push
|
||||
- name: Create pull request
|
||||
run: |
|
||||
printf "> [!IMPORTANT]\n> Todos:\n> - [ ] Update maven dependencies\n> - [ ] Check for JDK update\n> - [ ] Check for JFX update" > pr_body.md
|
||||
PR_URL=$(gh pr create --title "Release ${{ needs.get-version.outputs.semVerStr }}" --body-file pr_body.md)
|
||||
echo "FLATHUB_PR_URL=$PR_URL" >> "$GITHUB_ENV"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
||||
- name: Slack Notification
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
if: github.event_name == 'release'
|
||||
env:
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
SLACK_USERNAME: 'Cryptobot'
|
||||
SLACK_ICON: false
|
||||
SLACK_ICON_EMOJI: ':bot:'
|
||||
SLACK_CHANNEL: 'cryptomator-desktop'
|
||||
SLACK_TITLE: "Flathub release PR created for ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} created."
|
||||
SLACK_MESSAGE: "See <${{ env.FLATHUB_PR_URL }}|PR> on how to proceed.>."
|
||||
SLACK_FOOTER: false
|
||||
MSG_MINIMAL: true
|
||||
10
.github/workflows/get-version.yml
vendored
10
.github/workflows/get-version.yml
vendored
@@ -22,8 +22,8 @@ on:
|
||||
value: ${{ jobs.determine-version.outputs.type }}
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'zulu'
|
||||
JAVA_VERSION: 21
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: 23
|
||||
|
||||
jobs:
|
||||
determine-version:
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR=${GITHUB_REF##*/}
|
||||
elif [[ "${{ inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then
|
||||
SEM_VER_STR="${{ github.event.inputs.version }}"
|
||||
SEM_VER_STR="${{ inputs.version }}"
|
||||
else
|
||||
SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`
|
||||
fi
|
||||
@@ -71,6 +71,6 @@ jobs:
|
||||
echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT
|
||||
echo "type=${TYPE}" >> $GITHUB_OUTPUT
|
||||
- name: Validate Version
|
||||
uses: skymatic/semver-validation-action@v2
|
||||
uses: skymatic/semver-validation-action@v3
|
||||
with:
|
||||
version: ${{ steps.versions.outputs.semVerStr }}
|
||||
version: ${{ steps.versions.outputs.semVerStr }}
|
||||
264
.github/workflows/mac-dmg-x64.yml
vendored
Normal file
264
.github/workflows/mac-dmg-x64.yml
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
name: Build macOS .dmg for x64
|
||||
|
||||
#######################################
|
||||
# STOP! DO NOT EDIT THIS FILE!
|
||||
#
|
||||
# It is a copy of mac-dmg.yml with tiny adjustements (mainly lines 42 to 47)
|
||||
# It was made necessary, since Github does not offer free macos intel runners for macos 15 and above.
|
||||
# This workflow can only be triggered by a release.
|
||||
#
|
||||
#######################################
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '23.0.2+7'
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
uses: ./.github/workflows/get-version.yml
|
||||
with:
|
||||
version: ${{ inputs.version }}
|
||||
|
||||
build-arm:
|
||||
name: Build Cryptomator.app for ${{ matrix.output-suffix }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [get-version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-15-large
|
||||
architecture: x64
|
||||
output-suffix: x64
|
||||
fuse-lib: macFUSE
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_osx-x64_bin-jmods.zip'
|
||||
openjfx-sha: '5e6c65c065eea22430c0eab36f37a5985eb8ad99e19e8772262021740d338f68'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: ${{ env.JAVA_DIST }}
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
architecture: ${{ matrix.architecture }}
|
||||
check-latest: true
|
||||
cache: 'maven'
|
||||
- name: Download OpenJFX jmods
|
||||
id: download-jmods
|
||||
run: |
|
||||
curl -L ${{ matrix.openjfx-url }} -o openjfx-jmods.zip
|
||||
echo "${{ matrix.openjfx-sha }} *openjfx-jmods.zip" | shasum -a256 --check
|
||||
mkdir -p openjfx-jmods/
|
||||
unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
|
||||
- name: Ensure major jfx version in pom and in jmods is the same
|
||||
run: |
|
||||
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
|
||||
JMOD_VERSION=${JMOD_VERSION#*@}
|
||||
JMOD_VERSION=${JMOD_VERSION%%.*}
|
||||
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
|
||||
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
|
||||
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
|
||||
|
||||
if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then
|
||||
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != jmod version (${JMOD_VERSION})"
|
||||
exit 1
|
||||
fi
|
||||
- name: Set version
|
||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||
- name: Run maven
|
||||
run: mvn -B -Djavafx.platform=mac clean package -Pmac -DskipTests
|
||||
- name: Patch target dir
|
||||
run: |
|
||||
cp LICENSE.txt target
|
||||
cp target/cryptomator-*.jar target/mods
|
||||
- name: Run jlink
|
||||
#Remark: no compression is applied for improved build compression later (here dmg)
|
||||
run: >
|
||||
${JAVA_HOME}/bin/jlink
|
||||
--verbose
|
||||
--output runtime
|
||||
--module-path "${JAVA_HOME}/jmods:openjfx-jmods"
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
|
||||
--strip-native-commands
|
||||
--no-header-files
|
||||
--no-man-pages
|
||||
--strip-debug
|
||||
--compress zip-0
|
||||
- name: Run jpackage
|
||||
run: >
|
||||
${JAVA_HOME}/bin/jpackage
|
||||
--verbose
|
||||
--type app-image
|
||||
--runtime-image runtime
|
||||
--input target/libs
|
||||
--module-path target/mods
|
||||
--module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
|
||||
--dest appdir
|
||||
--name Cryptomator
|
||||
--vendor "Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2025 Skymatic GmbH"
|
||||
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
||||
--java-options "--enable-preview"
|
||||
--java-options "--enable-native-access=org.cryptomator.jfuse.mac"
|
||||
--java-options "-Xss5m"
|
||||
--java-options "-Xmx256m"
|
||||
--java-options "-Dfile.encoding=\"utf-8\""
|
||||
--java-options "-Djava.net.useSystemProxies=true"
|
||||
--java-options "-Dapple.awt.enableTemplateImages=true"
|
||||
--java-options "-Dsun.java2d.metal=true"
|
||||
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||
--java-options "-Dcryptomator.logDir=\"@{userhome}/Library/Logs/Cryptomator\""
|
||||
--java-options "-Dcryptomator.pluginDir=\"@{userhome}/Library/Application Support/Cryptomator/Plugins\""
|
||||
--java-options "-Dcryptomator.settingsPath=\"@{userhome}/Library/Application Support/Cryptomator/settings.json\""
|
||||
--java-options "-Dcryptomator.p12Path=\"@{userhome}/Library/Application Support/Cryptomator/key.p12\""
|
||||
--java-options "-Dcryptomator.ipcSocketPath=\"@{userhome}/Library/Application Support/Cryptomator/ipc.socket\""
|
||||
--java-options "-Dcryptomator.integrationsMac.keychainServiceName=\"Cryptomator\""
|
||||
--java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Library/Application Support/Cryptomator/mnt\""
|
||||
--java-options "-Dcryptomator.showTrayIcon=true"
|
||||
--java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.get-version.outputs.revNum }}\""
|
||||
--mac-package-identifier org.cryptomator
|
||||
--resource-dir dist/mac/resources
|
||||
- name: Patch Cryptomator.app
|
||||
run: |
|
||||
mv appdir/Cryptomator.app Cryptomator.app
|
||||
mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/
|
||||
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
|
||||
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
|
||||
echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode --output Cryptomator.app/Contents/embedded.provisionprofile
|
||||
env:
|
||||
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
||||
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
|
||||
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
|
||||
- name: Generate license for dmg
|
||||
run: >
|
||||
mvn -B -Djavafx.platform=mac license:add-third-party
|
||||
-Dlicense.thirdPartyFilename=license.rtf
|
||||
-Dlicense.outputDirectory=dist/mac/dmg/resources
|
||||
-Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl
|
||||
-Dlicense.includedScopes=compile
|
||||
-Dlicense.excludedGroups=^org\.cryptomator
|
||||
-Dlicense.failOnMissing=true
|
||||
-Dlicense.licenseMergesUrl=file://${{ github.workspace }}/license/merges
|
||||
- name: Install codesign certificate
|
||||
run: |
|
||||
# create variables
|
||||
CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12
|
||||
KEYCHAIN_PATH=$RUNNER_TEMP/codesign.keychain-db
|
||||
|
||||
# import certificate and provisioning profile from secrets
|
||||
echo -n "$CODESIGN_P12_BASE64" | base64 --decode --output $CERTIFICATE_PATH
|
||||
|
||||
# create temporary keychain
|
||||
security create-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH
|
||||
security set-keychain-settings -lut 900 $KEYCHAIN_PATH
|
||||
security unlock-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH
|
||||
|
||||
# import certificate to keychain
|
||||
security import $CERTIFICATE_PATH -P "$CODESIGN_P12_PW" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
|
||||
security list-keychain -d user -s $KEYCHAIN_PATH
|
||||
env:
|
||||
CODESIGN_P12_BASE64: ${{ secrets.MACOS_CODESIGN_P12_BASE64 }}
|
||||
CODESIGN_P12_PW: ${{ secrets.MACOS_CODESIGN_P12_PW }}
|
||||
CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }}
|
||||
- name: Codesign
|
||||
run: |
|
||||
echo "Codesigning jdk files..."
|
||||
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ \( -name 'jspawnhelper' -o -name 'pauseengine' -o -name 'simengine' \) -exec codesign --force -o runtime -s ${CODESIGN_IDENTITY} {} \;
|
||||
echo "Codesigning jar contents..."
|
||||
find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||
for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do
|
||||
if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
|
||||
JAR_FILENAME=$(basename ${JAR_PATH})
|
||||
OUTPUT_PATH=${JAR_PATH%.*}
|
||||
echo "Codesigning libs in ${JAR_FILENAME}..."
|
||||
unzip -q ${JAR_PATH} -d ${OUTPUT_PATH}
|
||||
find ${OUTPUT_PATH} -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||
find ${OUTPUT_PATH} -name '*.jnilib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||
rm ${JAR_PATH}
|
||||
pushd ${OUTPUT_PATH} > /dev/null
|
||||
zip -qr ../${JAR_FILENAME} *
|
||||
popd > /dev/null
|
||||
rm -r ${OUTPUT_PATH}
|
||||
fi
|
||||
done
|
||||
echo "Codesigning Cryptomator.app..."
|
||||
sed -i '' "s|###APP_IDENTIFIER_PREFIX###|${TEAM_IDENTIFIER}.|g" dist/mac/Cryptomator.entitlements
|
||||
sed -i '' "s|###TEAM_IDENTIFIER###|${TEAM_IDENTIFIER}|g" dist/mac/Cryptomator.entitlements
|
||||
codesign --force --deep --entitlements dist/mac/Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app
|
||||
env:
|
||||
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
|
||||
TEAM_IDENTIFIER: ${{ secrets.MACOS_TEAM_IDENTIFIER }}
|
||||
- name: Prepare .dmg contents
|
||||
run: |
|
||||
mkdir dmg
|
||||
mv Cryptomator.app dmg
|
||||
cp dist/mac/dmg/resources/${{ matrix.fuse-lib }}.webloc dmg
|
||||
ls -l dmg
|
||||
- name: Install create-dmg
|
||||
run: |
|
||||
brew install create-dmg
|
||||
create-dmg --help
|
||||
- name: Create .dmg
|
||||
run: >
|
||||
create-dmg
|
||||
--volname Cryptomator
|
||||
--volicon "dist/mac/dmg/resources/Cryptomator-Volume.icns"
|
||||
--background "dist/mac/dmg/resources/Cryptomator-${{ matrix.fuse-lib }}-background.tiff"
|
||||
--window-pos 400 100
|
||||
--window-size 640 694
|
||||
--icon-size 128
|
||||
--icon "Cryptomator.app" 128 245
|
||||
--hide-extension "Cryptomator.app"
|
||||
--icon "${{ matrix.fuse-lib }}.webloc" 320 501
|
||||
--hide-extension "${{ matrix.fuse-lib }}.webloc"
|
||||
--app-drop-link 512 245
|
||||
--eula "dist/mac/dmg/resources/license.rtf"
|
||||
--icon ".background" 128 758
|
||||
--icon ".VolumeIcon.icns" 512 758
|
||||
Cryptomator-${VERSION_NO}-${{ matrix.output-suffix }}.dmg dmg
|
||||
env:
|
||||
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
||||
- name: Notarize .dmg
|
||||
if: startsWith(github.ref, 'refs/tags/') || inputs.notarize
|
||||
uses: cocoalibs/xcode-notarization-action@v1
|
||||
with:
|
||||
app-path: 'Cryptomator-*.dmg'
|
||||
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
|
||||
password: ${{ secrets.MACOS_NOTARIZATION_PW }}
|
||||
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
|
||||
xcode-path: '/Applications/Xcode_16.app'
|
||||
- name: Add possible alpha/beta tags to installer name
|
||||
run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
|
||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||
run: |
|
||||
echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import
|
||||
echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.dmg
|
||||
env:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||
- name: Clean up codesign certificate
|
||||
if: ${{ always() }}
|
||||
run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db
|
||||
continue-on-error: true
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dmg-${{ matrix.output-suffix }}
|
||||
path: |
|
||||
Cryptomator-*.dmg
|
||||
Cryptomator-*.asc
|
||||
if-no-files-found: error
|
||||
- name: Publish dmg on GitHub Releases
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
files: |
|
||||
Cryptomator-*.dmg
|
||||
Cryptomator-*.asc
|
||||
45
.github/workflows/mac-dmg.yml
vendored
45
.github/workflows/mac-dmg.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Build macOS .dmg
|
||||
name: Build macOS .dmg for arm64
|
||||
|
||||
on:
|
||||
release:
|
||||
@@ -15,8 +15,8 @@ on:
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'zulu'
|
||||
JAVA_VERSION: '21.0.2+13'
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: '23.0.2+7'
|
||||
|
||||
jobs:
|
||||
get-version:
|
||||
@@ -32,20 +32,12 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-11
|
||||
architecture: x64
|
||||
output-suffix: x64
|
||||
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||
fuse-lib: macFUSE
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_osx-x64_bin-jmods.zip'
|
||||
openjfx-sha: 'bd6abab20da73d5a968dcf2fd915d81b5fb919340e3bb84979ee9a888a829939'
|
||||
- os: [self-hosted, macOS, ARM64]
|
||||
- os: macos-15
|
||||
architecture: aarch64
|
||||
output-suffix: arm64
|
||||
xcode-path: '/Applications/Xcode_13.2.1.app'
|
||||
fuse-lib: FUSE-T
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_osx-aarch64_bin-jmods.zip'
|
||||
openjfx-sha: '7afaa1c57a6cc3c384d636e597b9a5364693e2db4aaec0a6e63d2fa964400b58'
|
||||
openjfx-url: 'https://download2.gluonhq.com/openjfx/23.0.2/openjfx-23.0.2_osx-aarch64_bin-jmods.zip'
|
||||
openjfx-sha: 'c690cc642a3924cf56622951f478ba57aec9ce09063761f800c3319331bed3fc'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Java
|
||||
@@ -79,7 +71,7 @@ jobs:
|
||||
- name: Set version
|
||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||
- name: Run maven
|
||||
run: mvn -B clean package -Pmac -DskipTests
|
||||
run: mvn -B -Djavafx.platform=mac clean package -Pmac -DskipTests
|
||||
- name: Patch target dir
|
||||
run: |
|
||||
cp LICENSE.txt target
|
||||
@@ -91,7 +83,7 @@ jobs:
|
||||
--verbose
|
||||
--output runtime
|
||||
--module-path "${JAVA_HOME}/jmods:openjfx-jmods"
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
|
||||
--strip-native-commands
|
||||
--no-header-files
|
||||
--no-man-pages
|
||||
@@ -109,7 +101,7 @@ jobs:
|
||||
--dest appdir
|
||||
--name Cryptomator
|
||||
--vendor "Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2024 Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2025 Skymatic GmbH"
|
||||
--app-version "${{ needs.get-version.outputs.semVerNum }}"
|
||||
--java-options "--enable-preview"
|
||||
--java-options "--enable-native-access=org.cryptomator.jfuse.mac"
|
||||
@@ -137,12 +129,14 @@ jobs:
|
||||
mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/
|
||||
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist
|
||||
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist
|
||||
echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode --output Cryptomator.app/Contents/embedded.provisionprofile
|
||||
env:
|
||||
VERSION_NO: ${{ needs.get-version.outputs.semVerNum }}
|
||||
REVISION_NO: ${{ needs.get-version.outputs.revNum }}
|
||||
PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }}
|
||||
- name: Generate license for dmg
|
||||
run: >
|
||||
mvn -B license:add-third-party
|
||||
mvn -B -Djavafx.platform=mac license:add-third-party
|
||||
-Dlicense.thirdPartyFilename=license.rtf
|
||||
-Dlicense.outputDirectory=dist/mac/dmg/resources
|
||||
-Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl
|
||||
@@ -175,7 +169,7 @@ jobs:
|
||||
run: |
|
||||
echo "Codesigning jdk files..."
|
||||
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ -name 'jspawnhelper' -exec codesign --force -o runtime -s ${CODESIGN_IDENTITY} {} \;
|
||||
find Cryptomator.app/Contents/runtime/Contents/Home/lib/ \( -name 'jspawnhelper' -o -name 'pauseengine' -o -name 'simengine' \) -exec codesign --force -o runtime -s ${CODESIGN_IDENTITY} {} \;
|
||||
echo "Codesigning jar contents..."
|
||||
find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||
for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do
|
||||
@@ -194,9 +188,12 @@ jobs:
|
||||
fi
|
||||
done
|
||||
echo "Codesigning Cryptomator.app..."
|
||||
sed -i '' "s|###APP_IDENTIFIER_PREFIX###|${TEAM_IDENTIFIER}.|g" dist/mac/Cryptomator.entitlements
|
||||
sed -i '' "s|###TEAM_IDENTIFIER###|${TEAM_IDENTIFIER}|g" dist/mac/Cryptomator.entitlements
|
||||
codesign --force --deep --entitlements dist/mac/Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app
|
||||
env:
|
||||
CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
|
||||
TEAM_IDENTIFIER: ${{ secrets.MACOS_TEAM_IDENTIFIER }}
|
||||
- name: Prepare .dmg contents
|
||||
run: |
|
||||
mkdir dmg
|
||||
@@ -235,7 +232,7 @@ jobs:
|
||||
apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }}
|
||||
password: ${{ secrets.MACOS_NOTARIZATION_PW }}
|
||||
team-id: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }}
|
||||
xcode-path: ${{ matrix.xcode-path }}
|
||||
xcode-path: '/Applications/Xcode_16.app'
|
||||
- name: Add possible alpha/beta tags to installer name
|
||||
run: mv Cryptomator-*.dmg Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.output-suffix }}.dmg
|
||||
- name: Create detached GPG signature with key 615D449FE6E6A235
|
||||
@@ -253,11 +250,13 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dmg-${{ matrix.output-suffix }}
|
||||
path: Cryptomator-*.dmg
|
||||
path: |
|
||||
Cryptomator-*.dmg
|
||||
Cryptomator-*.asc
|
||||
if-no-files-found: error
|
||||
- name: Publish dmg on GitHub Releases
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
|
||||
2
.github/workflows/post-publish.yml
vendored
2
.github/workflows/post-publish.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }}
|
||||
GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
|
||||
- name: Publish asc on GitHub Releases
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
|
||||
6
.github/workflows/pullrequest.yml
vendored
6
.github/workflows/pullrequest.yml
vendored
@@ -4,8 +4,8 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'zulu'
|
||||
JAVA_VERSION: 21
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: 23
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -24,4 +24,4 @@ jobs:
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
cache: 'maven'
|
||||
- name: Build and Test
|
||||
run: xvfb-run mvn -B clean install jacoco:report -Pcoverage
|
||||
run: xvfb-run mvn -B clean install jacoco:report -Pcoverage -Djavafx.platform=linux
|
||||
8
.github/workflows/release-check.yml
vendored
8
.github/workflows/release-check.yml
vendored
@@ -11,8 +11,8 @@ defaults:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'zulu'
|
||||
JAVA_VERSION: 21
|
||||
JAVA_DIST: 'temurin'
|
||||
JAVA_VERSION: 23
|
||||
|
||||
jobs:
|
||||
check-preconditions:
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
fi
|
||||
- name: Validate release in org.cryptomator.Cryptomator.metainfo.xml file
|
||||
run: |
|
||||
if ! grep -q "<release date=\".*\" version=\"${{ steps.validate-pom-version.outputs.semVerStr }}\"/>" dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml; then
|
||||
if ! grep -q "<release date=\".*\" version=\"${{ steps.validate-pom-version.outputs.semVerStr }}\">" dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml; then
|
||||
echo "Release not set in dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml"
|
||||
exit 1
|
||||
fi
|
||||
@@ -60,6 +60,6 @@ jobs:
|
||||
- name: Run org.owasp:dependency-check plugin
|
||||
id: dependency-check
|
||||
continue-on-error: true
|
||||
run: mvn -B verify -Pdependency-check -DskipTests
|
||||
run: mvn -B verify -Pdependency-check -DskipTests -Djavafx.platform=linux
|
||||
env:
|
||||
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
|
||||
135
.github/workflows/win-exe.yml
vendored
135
.github/workflows/win-exe.yml
vendored
@@ -11,15 +11,16 @@ on:
|
||||
isDebug:
|
||||
description: 'Build debug version with console output'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
|
||||
env:
|
||||
JAVA_DIST: 'zulu'
|
||||
JAVA_VERSION: '21.0.2+13'
|
||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_windows-x64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AMD64_HASH: 'daf8acae631c016c24cfe23f88469400274d3441dd890615a42dfb501f3eb94a'
|
||||
JAVA_VERSION: '23.0.2+7'
|
||||
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/23.0.1/openjfx-23.0.1_windows-x64_bin-jmods.zip'
|
||||
OPENJFX_JMODS_AMD64_HASH: 'ee176dcee3bd78bde7910735bd67f67c792882f5b89626796ae06f7a1c0119d3'
|
||||
WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi'
|
||||
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0/winfsp-uninstaller.exe'
|
||||
WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -73,7 +74,7 @@ jobs:
|
||||
- name: Set version
|
||||
run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }}
|
||||
- name: Run maven
|
||||
run: mvn -B clean package -Pwin -DskipTests
|
||||
run: mvn -B clean package -Pwin -DskipTests -Djavafx.platform=win
|
||||
- name: Patch target dir
|
||||
run: |
|
||||
cp LICENSE.txt target
|
||||
@@ -85,7 +86,7 @@ jobs:
|
||||
--verbose
|
||||
--output runtime
|
||||
--module-path "jfxjmods;${JAVA_HOME}/jmods"
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler
|
||||
--strip-native-commands
|
||||
--no-header-files
|
||||
--no-man-pages
|
||||
@@ -106,10 +107,10 @@ jobs:
|
||||
--dest appdir
|
||||
--name Cryptomator
|
||||
--vendor "Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2024 Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2025 Skymatic GmbH"
|
||||
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||
--java-options "--enable-preview"
|
||||
--java-options "--enable-native-access=org.cryptomator.jfuse.win"
|
||||
--java-options "--enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win"
|
||||
--java-options "-Xss5m"
|
||||
--java-options "-Xmx256m"
|
||||
--java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\""
|
||||
@@ -170,7 +171,7 @@ jobs:
|
||||
& $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod"
|
||||
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
|
||||
- name: Codesign
|
||||
uses: skymatic/code-sign-action@v2
|
||||
uses: skymatic/code-sign-action@v3
|
||||
with:
|
||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||
@@ -195,7 +196,7 @@ jobs:
|
||||
}
|
||||
- name: Generate license for MSI
|
||||
run: >
|
||||
mvn -B license:add-third-party
|
||||
mvn -B license:add-third-party "-Djavafx.platform=win"
|
||||
"-Dlicense.thirdPartyFilename=license.rtf"
|
||||
"-Dlicense.outputDirectory=dist/win/resources"
|
||||
"-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl"
|
||||
@@ -214,12 +215,12 @@ jobs:
|
||||
--dest installer
|
||||
--name Cryptomator
|
||||
--vendor "Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2024 Skymatic GmbH"
|
||||
--copyright "(C) 2016 - 2025 Skymatic GmbH"
|
||||
--app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum}}"
|
||||
--win-menu
|
||||
--win-dir-chooser
|
||||
--win-shortcut-prompt
|
||||
--win-update-url "https:\\cryptomator.org"
|
||||
--win-update-url "https:\\cryptomator.org\downloads"
|
||||
--win-menu-group Cryptomator
|
||||
--resource-dir dist/win/resources
|
||||
--license-file dist/win/resources/license.rtf
|
||||
@@ -228,7 +229,7 @@ jobs:
|
||||
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
|
||||
JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir
|
||||
- name: Codesign MSI
|
||||
uses: skymatic/code-sign-action@v2
|
||||
uses: skymatic/code-sign-action@v3
|
||||
with:
|
||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||
@@ -253,15 +254,6 @@ jobs:
|
||||
Cryptomator-*.msi
|
||||
Cryptomator-*.asc
|
||||
if-no-files-found: error
|
||||
- name: Publish .msi on GitHub Releases
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
files: |
|
||||
*.msi
|
||||
*.asc
|
||||
|
||||
build-exe:
|
||||
name: Build .exe installer
|
||||
@@ -284,7 +276,7 @@ jobs:
|
||||
cache: 'maven'
|
||||
- name: Generate license for exe
|
||||
run: >
|
||||
mvn -B license:add-third-party
|
||||
mvn -B license:add-third-party "-Djavafx.platform=win"
|
||||
"-Dlicense.thirdPartyFilename=license.rtf"
|
||||
"-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl"
|
||||
"-Dlicense.outputDirectory=dist/win/bundle/resources"
|
||||
@@ -309,7 +301,7 @@ jobs:
|
||||
-out dist/win/bundle/
|
||||
-dBundleVersion="${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}"
|
||||
-dBundleVendor="Skymatic GmbH"
|
||||
-dBundleCopyright="(C) 2016 - 2024 Skymatic GmbH"
|
||||
-dBundleCopyright="(C) 2016 - 2025 Skymatic GmbH"
|
||||
-dAboutUrl="https://cryptomator.org"
|
||||
-dHelpUrl="https://cryptomator.org/contact"
|
||||
-dUpdateUrl="https://cryptomator.org/downloads/"
|
||||
@@ -325,7 +317,7 @@ jobs:
|
||||
-ib installer/unsigned/Cryptomator-Installer.exe
|
||||
-o tmp/engine.exe
|
||||
- name: Codesign burn engine
|
||||
uses: skymatic/code-sign-action@v2
|
||||
uses: skymatic/code-sign-action@v3
|
||||
with:
|
||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||
@@ -339,7 +331,7 @@ jobs:
|
||||
-ab tmp/engine.exe installer/unsigned/Cryptomator-Installer.exe
|
||||
-o installer/Cryptomator-Installer.exe
|
||||
- name: Codesign EXE
|
||||
uses: skymatic/code-sign-action@v2
|
||||
uses: skymatic/code-sign-action@v3
|
||||
with:
|
||||
certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }}
|
||||
password: ${{ secrets.WIN_CODESIGN_P12_PW }}
|
||||
@@ -364,52 +356,61 @@ jobs:
|
||||
Cryptomator-*.exe
|
||||
Cryptomator-*.asc
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Publish installers to the github release
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-msi, build-exe]
|
||||
outputs:
|
||||
download-url-msi: ${{ fromJSON(steps.publish.outputs.assets)[0].browser_download_url }}
|
||||
download-url-exe: ${{ fromJSON(steps.publish.outputs.assets)[1].browser_download_url }}
|
||||
steps:
|
||||
- name: Download installers
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
merge-multiple: true
|
||||
- name: Publish .msi on GitHub Releases
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
id: publish
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }}
|
||||
# do not change ordering of filelist, required for correct job output
|
||||
files: |
|
||||
Cryptomator-*.exe
|
||||
Cryptomator-*.asc
|
||||
*.msi
|
||||
*.exe
|
||||
*.asc
|
||||
|
||||
allowlist:
|
||||
name: Anti Virus Allowlisting
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
allowlist-msi:
|
||||
uses: ./.github/workflows/av-whitelist.yml
|
||||
needs: [publish]
|
||||
with:
|
||||
url: ${{ needs.publish.outputs.download-url-msi }}
|
||||
secrets: inherit
|
||||
|
||||
allowlist-exe:
|
||||
uses: ./.github/workflows/av-whitelist.yml
|
||||
needs: [publish, allowlist-msi]
|
||||
with:
|
||||
url: ${{ needs.publish.outputs.download-url-exe }}
|
||||
secrets: inherit
|
||||
|
||||
notify-winget:
|
||||
name: Notify for winget-release
|
||||
if: needs.get-version.outputs.versionType == 'stable'
|
||||
needs: [publish, get-version]
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-msi, build-exe]
|
||||
steps:
|
||||
- name: Download .msi
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: msi
|
||||
path: msi
|
||||
- name: Download .exe
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: exe
|
||||
path: exe
|
||||
- name: Collect files
|
||||
run: |
|
||||
mkdir files
|
||||
cp msi/*.msi files
|
||||
cp exe/*.exe files
|
||||
- name: Upload to Kaspersky
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
|
||||
with:
|
||||
protocol: ftps
|
||||
server: allowlist.kaspersky-labs.com
|
||||
port: 990
|
||||
username: ${{ secrets.ALLOWLIST_KASPERSKY_USERNAME }}
|
||||
password: ${{ secrets.ALLOWLIST_KASPERSKY_PASSWORD }}
|
||||
local-dir: files/
|
||||
- name: Upload to Avast
|
||||
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
|
||||
with:
|
||||
protocol: ftp
|
||||
server: whitelisting.avast.com
|
||||
port: 21
|
||||
username: ${{ secrets.ALLOWLIST_AVAST_USERNAME }}
|
||||
password: ${{ secrets.ALLOWLIST_AVAST_PASSWORD }}
|
||||
local-dir: files/
|
||||
- name: Slack Notification
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
env:
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
SLACK_USERNAME: 'Cryptobot'
|
||||
SLACK_ICON: false
|
||||
SLACK_ICON_EMOJI: ':bot:'
|
||||
SLACK_CHANNEL: 'cryptomator-desktop'
|
||||
SLACK_TITLE: "MSI of ${{ github.event.repository.name }} ${{ github.event.release.tag_name }} published."
|
||||
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/winget.yml| release to winget>."
|
||||
SLACK_FOOTER: false
|
||||
MSG_MINIMAL: true
|
||||
27
.github/workflows/winget.yml
vendored
Normal file
27
.github/workflows/winget.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Publish MSI to winget-pkgs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release tag'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
winget:
|
||||
name: Publish winget package
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Sync winget-pkgs fork
|
||||
run: |
|
||||
gh repo sync cryptomator/winget-pkgs -b master --force
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
||||
- name: Submit package
|
||||
uses: vedantmgoyal2009/winget-releaser@main
|
||||
with:
|
||||
identifier: Cryptomator.Cryptomator
|
||||
version: ${{ inputs.tag }}
|
||||
release-tag: ${{ inputs.tag }}
|
||||
installers-regex: '\.msi$'
|
||||
token: ${{ secrets.CRYPTOBOT_WINGET_TOKEN }}
|
||||
30
.idea/compiler.xml
generated
30
.idea/compiler.xml
generated
@@ -14,23 +14,22 @@
|
||||
<option name="dagger.fastInit" value="enabled" />
|
||||
<option name="dagger.formatGeneratedSource" value="enabled" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.48.1/dagger-compiler-2.48.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.48.1/dagger-2.48.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.55/dagger-compiler-2.55.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.55/dagger-2.55.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-producers/2.48.1/dagger-producers-2.48.1.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/31.0.1-jre/guava-31.0.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$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.55/dagger-spi-2.55.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-qual/3.12.0/checker-qual-3.12.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.7.1/error_prone_annotations-2.7.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.48.1/dagger-spi-2.48.1.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/1.9.0-1.0.12/symbol-processing-api-1.9.0-1.0.12.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/2.0.21-1.0.28/symbol-processing-api-2.0.21-1.0.28.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.2/failureaccess-1.0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/33.0.0-jre/guava-33.0.0-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$/org/checkerframework/checker-qual/3.41.0/checker-qual-3.41.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.23.0/error_prone_annotations-2.23.0.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.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" />
|
||||
@@ -39,6 +38,7 @@
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
|
||||
</processorPath>
|
||||
<module name="cryptomator" />
|
||||
</profile>
|
||||
@@ -46,7 +46,7 @@
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="cryptomator" options="-Adagger.fastInit=enabled -Adagger.formatGeneratedSource=enabled --enable-preview" />
|
||||
<module name="cryptomator" options="-Adagger.fastInit=enabled -Adagger.formatGeneratedSource=enabled" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -8,7 +8,7 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21_PREVIEW" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" project-jdk-name="23" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/runConfigurations/Cryptomator_Windows.xml
generated
2
.idea/runConfigurations/Cryptomator_Windows.xml
generated
@@ -2,7 +2,7 @@
|
||||
<configuration default="false" name="Cryptomator Windows" type="Application" factoryName="Application">
|
||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||
<module name="cryptomator" />
|
||||
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcSocketPath="@{localappdata}/Cryptomator/ipc.socket" -Dcryptomator.logDir="@{localappdata}/Cryptomator" -Dcryptomator.pluginDir="@{appdata}/Cryptomator/Plugins" -Dcryptomator.integrationsWin.keychainPaths="@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json" -Dcryptomator.p12Path="@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12" -Dcryptomator.mountPointsDir="@{userhome}/Cryptomator" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" />
|
||||
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="@{appdata}/Cryptomator/settings.json;@{userhome}/AppData/Roaming/Cryptomator/settings.json" -Dcryptomator.ipcSocketPath="@{localappdata}/Cryptomator/ipc.socket" -Dcryptomator.logDir="@{localappdata}/Cryptomator" -Dcryptomator.pluginDir="@{appdata}/Cryptomator/Plugins" -Dcryptomator.integrationsWin.keychainPaths="@{appdata}/Cryptomator/keychain.json;@{userhome}/AppData/Roaming/Cryptomator/keychain.json" -Dcryptomator.p12Path="@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12" -Dcryptomator.mountPointsDir="@{userhome}/Cryptomator" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<configuration default="false" name="Cryptomator Windows Dev" type="Application" factoryName="Application">
|
||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||
<module name="cryptomator" />
|
||||
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="@{appdata}/Cryptomator-Dev/settings.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/settings.json" -Dcryptomator.ipcSocketPath="@{localappdata}/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="@{localappdata}/Cryptomator-Dev" -Dcryptomator.pluginDir="@{appdata}/Cryptomator-Dev/Plugins" -Dcryptomator.integrationsWin.keychainPaths="@{appdata}/Cryptomator-Dev/keychain.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/keychain.json" -Dcryptomator.p12Path="@{appdata}/Cryptomator-Dev/key.p12;@{userhome}/AppData/Roaming/Cryptomator-Dev/key.p12" -Dcryptomator.mountPointsDir="@{userhome}/Cryptomator-Dev" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win" />
|
||||
<option name="VM_PARAMETERS" value="-Dcryptomator.settingsPath="@{appdata}/Cryptomator-Dev/settings.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/settings.json" -Dcryptomator.ipcSocketPath="@{localappdata}/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="@{localappdata}/Cryptomator-Dev" -Dcryptomator.pluginDir="@{appdata}/Cryptomator-Dev/Plugins" -Dcryptomator.integrationsWin.keychainPaths="@{appdata}/Cryptomator-Dev/keychain.json;@{userhome}/AppData/Roaming/Cryptomator-Dev/keychain.json" -Dcryptomator.p12Path="@{appdata}/Cryptomator-Dev/key.p12;@{userhome}/AppData/Roaming/Cryptomator-Dev/key.p12" -Dcryptomator.mountPointsDir="@{userhome}/Cryptomator-Dev" -Dcryptomator.showTrayIcon=true -Xss2m -Xmx512m --enable-preview --enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</envs>
|
||||
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
|
||||
<module name="cryptomator" />
|
||||
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath="@{userhome}/Library/Application Support/Cryptomator-Dev/settings.json" -Dcryptomator.p12Path="@{userhome}/Library/Application Support/Cryptomator-Dev/key.p12" -Dcryptomator.ipcSocketPath="@{userhome}/Library/Application Support/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="@{userhome}/Library/Logs/Cryptomator-Dev" -Dcryptomator.pluginDir="@{userhome}/Library/Application Support/Cryptomator-Dev/Plugins" -Dcryptomator.mountPointsDir="@{userhome}/Cryptomator" -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" />
|
||||
<option name="VM_PARAMETERS" value="-Dapple.awt.enableTemplateImages=true -Dcryptomator.settingsPath="@{userhome}/Library/Application Support/Cryptomator-Dev/settings.json" -Dcryptomator.p12Path="@{userhome}/Library/Application Support/Cryptomator-Dev/key.p12" -Dcryptomator.ipcSocketPath="@{userhome}/Library/Application Support/Cryptomator-Dev/ipc.socket" -Dcryptomator.logDir="@{userhome}/Library/Logs/Cryptomator-Dev" -Dcryptomator.pluginDir="@{userhome}/Library/Application Support/Cryptomator-Dev/Plugins" -Dcryptomator.mountPointsDir="@{userhome}/Library/Application Support/Cryptomator-Dev/mnt" -Dcryptomator.showTrayIcon=true -Dcryptomator.integrationsMac.keychainServiceName=Cryptomator -Xss2m -Xmx512m -ea --enable-preview --enable-native-access=org.cryptomator.jfuse.mac" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
|
||||
28
README.md
28
README.md
@@ -1,9 +1,9 @@
|
||||
[](https://cryptomator.org/)
|
||||
|
||||
[](https://github.com/cryptomator/cryptomator/actions?query=workflow%3ABuild)
|
||||
[](https://github.com/cryptomator/cryptomator/actions/workflows/build.yml?query=branch%3Adevelop)
|
||||
[](https://snyk.io/test/github/cryptomator/cryptomator)
|
||||
[](https://sonarcloud.io/dashboard?id=cryptomator_cryptomator)
|
||||
[](http://twitter.com/Cryptomator)
|
||||
[](https://mastodon.online/@cryptomator)
|
||||
[](https://translate.cryptomator.org/)
|
||||
[](https://github.com/cryptomator/cryptomator/releases/latest)
|
||||
[](https://community.cryptomator.org)
|
||||
@@ -17,32 +17,24 @@ Cryptomator is provided free of charge as an open-source project despite the hig
|
||||
|
||||
### Gold Sponsors
|
||||
|
||||
<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>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
Become our Gold Sponsor and showcase your brand to a targeted audience! Please contact us if you are interested.
|
||||
|
||||
### Silver Sponsors
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><a href="https://mowcapital.com/"><img src="https://cryptomator.org/img/sponsors/mowcapital.svg" alt="Mow Capital" height="28"></a></td>
|
||||
<td><a href="https://www.easeus.com/"><img src="https://cryptomator.org/img/sponsors/easeus.png" alt="EaseUS" height="40"></a></td>
|
||||
<td><a href="https://www.hassmann-it-forensik.de/"><img src="https://cryptomator.org/img/sponsors/hassmannitforensik.png" alt="Hassmann IT-Forensik" height="40"></a></td>
|
||||
<td><a href="https://ente.io/"><img src="https://cryptomator.org/img/sponsors/ente.svg" alt="Ente" height="58"></a></td>
|
||||
<td><a href="https://www.gee-whiz.de/"><img src="https://cryptomator.org/img/sponsors/geewhiz.svg" alt="gee-whiz" height="56"></a></td>
|
||||
<td><a href="https://www.route4me.com/"><img src="https://cryptomator.org/img/sponsors/route4me.svg" alt="Route4Me" height="56"></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
### Special Shoutout
|
||||
|
||||
Continuous integration hosting for ARM64 builds is provided by [MacStadium](https://www.macstadium.com/opensource).
|
||||
Continuous integration hosting for ARM64 builds is provided by [MacStadium](https://www.macstadium.com/company/opensource).
|
||||
|
||||
<a href="https://www.macstadium.com/opensource"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="MacStadium" height="100"></a>
|
||||
<a href="https://www.macstadium.com/company/opensource"><img src="https://uploads-ssl.webflow.com/5ac3c046c82724970fc60918/5c019d917bba312af7553b49_MacStadium-developerlogo.png" alt="MacStadium" height="100"></a>
|
||||
|
||||
---
|
||||
|
||||
@@ -62,7 +54,7 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
|
||||
- File names get encrypted
|
||||
- Folder structure gets obfuscated
|
||||
- Use as many vaults in your Dropbox as you want, each having individual passwords
|
||||
- Four thousand commits for the security of your data!! :tada:
|
||||
- More than Five thousand commits for the security of your data!! :tada:
|
||||
|
||||
### Privacy
|
||||
|
||||
@@ -80,13 +72,13 @@ Download native binaries of Cryptomator on [cryptomator.org](https://cryptomator
|
||||
|
||||
### Security Architecture
|
||||
|
||||
For more information on the security details visit [cryptomator.org](https://docs.cryptomator.org/en/latest/security/architecture/).
|
||||
For more information on the security details visit [cryptomator.org](https://docs.cryptomator.org/security/architecture/).
|
||||
|
||||
## Building
|
||||
|
||||
### Dependencies
|
||||
|
||||
* JDK 21 (e.g. temurin, zulu)
|
||||
* JDK 23 (e.g. temurin, zulu)
|
||||
* Maven 3
|
||||
|
||||
### Run Maven
|
||||
|
||||
4
dist/linux/appimage/.gitignore
vendored
4
dist/linux/appimage/.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
# created during build
|
||||
# downloaded/created during build
|
||||
openjfx-jmods.zip
|
||||
*.jmod
|
||||
Cryptomator.AppDir
|
||||
*.AppImage
|
||||
*.AppImage.zsync
|
||||
59
dist/linux/appimage/build.sh
vendored
59
dist/linux/appimage/build.sh
vendored
@@ -12,40 +12,40 @@ command -v unzip >/dev/null 2>&1 || { echo >&2 "unzip not found."; exit 1; }
|
||||
|
||||
VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout)
|
||||
SEMVER_STR=${VERSION}
|
||||
MACHINE_TYPE=$(uname -m)
|
||||
CPU_ARCH=$(uname -p)
|
||||
|
||||
if [[ ! "${MACHINE_TYPE}" =~ x86_64|aarch64 ]]; then echo "Platform ${MACHINE_TYPE} not supported"; exit 1; fi
|
||||
if [[ ! "${CPU_ARCH}" =~ x86_64|aarch64 ]]; then echo "Platform ${CPU_ARCH} not supported"; exit 1; fi
|
||||
|
||||
mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR}
|
||||
|
||||
# compile
|
||||
mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests
|
||||
mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests -Djavafx.platform=linux
|
||||
cp ../../../LICENSE.txt ../../../target
|
||||
cp ../../../target/cryptomator-*.jar ../../../target/mods
|
||||
|
||||
|
||||
# download javaFX jmods
|
||||
OPENJFX_URL='https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-x64_bin-jmods.zip'
|
||||
OPENJFX_SHA='7baed11ca56d5fee85995fa6612d4299f1e8b7337287228f7f12fd50407c56f8'
|
||||
OPENJFX_URL_aarch64='https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_linux-aarch64_bin-jmods.zip'
|
||||
OPENJFX_SHA_aarch64='871e7b9d7af16aef2e55c1b7830d0e0b2503b13dd8641374ba7e55ecb81d2ef9'
|
||||
|
||||
if [[ "${MACHINE_TYPE}" = "aarch64" ]]; then
|
||||
OPENJFX_URL="${OPENJFX_URL_aarch64}";
|
||||
OPENJFX_SHA="${OPENJFX_SHA_aarch64}";
|
||||
JAVAFX_VERSION=23.0.2
|
||||
JAVAFX_ARCH="x64"
|
||||
JAVAFX_JMODS_SHA256='063baebc6922e4a89c94b9dfb7a4f53e59e8d6fec400d4e670b31bc2ab324dec'
|
||||
if [ "${CPU_ARCH}" = "aarch64" ]; then
|
||||
JAVAFX_ARCH="aarch64"
|
||||
JAVAFX_JMODS_SHA256='9bbedaeae1590b69e2b22237bda310936df33e344dbc243bea2e86acaab3a0d8'
|
||||
fi
|
||||
|
||||
curl -L ${OPENJFX_URL} -o openjfx-jmods.zip
|
||||
echo "${OPENJFX_SHA} openjfx-jmods.zip" | shasum -a256 --check
|
||||
# download javaFX jmods
|
||||
JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_linux-${JAVAFX_ARCH}_bin-jmods.zip"
|
||||
|
||||
|
||||
curl -L ${JAVAFX_JMODS_URL} -o openjfx-jmods.zip
|
||||
echo "${JAVAFX_JMODS_SHA256} openjfx-jmods.zip" | shasum -a256 --check
|
||||
mkdir -p openjfx-jmods
|
||||
unzip -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
|
||||
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
|
||||
unzip -o -j openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
|
||||
JMOD_VERSION=$(jmod describe ./openjfx-jmods/javafx.base.jmod | head -1)
|
||||
JMOD_VERSION=${JMOD_VERSION#*@}
|
||||
JMOD_VERSION=${JMOD_VERSION%%.*}
|
||||
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout)
|
||||
POM_JFX_VERSION=$(mvn help:evaluate "-Dexpression=javafx.version" -q -DforceStdout -B -f ../../../pom.xml)
|
||||
POM_JFX_VERSION=${POM_JFX_VERSION#*@}
|
||||
POM_JFX_VERSION=${POM_JFX_VERSION%%.*}
|
||||
if [ $POM_JFX_VERSION -ne $JMOD_VERSION_AMD64 ]; then
|
||||
if [ $POM_JFX_VERSION -ne $JMOD_VERSION ]; then
|
||||
>&2 echo "Major JavaFX version in pom.xml (${POM_JFX_VERSION}) != amd64 jmod version (${JMOD_VERSION})"
|
||||
exit 1
|
||||
fi
|
||||
@@ -56,7 +56,7 @@ ${JAVA_HOME}/bin/jlink \
|
||||
--verbose \
|
||||
--output runtime \
|
||||
--module-path "${JAVA_HOME}/jmods:openjfx-jmods" \
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net \
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \
|
||||
--strip-native-commands \
|
||||
--no-header-files \
|
||||
--no-man-pages \
|
||||
@@ -64,7 +64,6 @@ ${JAVA_HOME}/bin/jlink \
|
||||
--compress zip-0
|
||||
|
||||
# create app dir
|
||||
envsubst '${SEMVER_STR} ${REVISION_NUM}' < ../launcher-gtk2.properties > launcher-gtk2.properties
|
||||
${JAVA_HOME}/bin/jpackage \
|
||||
--verbose \
|
||||
--type app-image \
|
||||
@@ -77,7 +76,7 @@ ${JAVA_HOME}/bin/jpackage \
|
||||
--vendor "Skymatic GmbH" \
|
||||
--java-options "--enable-preview" \
|
||||
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
|
||||
--copyright "(C) 2016 - 2024 Skymatic GmbH" \
|
||||
--copyright "(C) 2016 - 2025 Skymatic GmbH" \
|
||||
--java-options "-Xss5m" \
|
||||
--java-options "-Xmx256m" \
|
||||
--app-version "${VERSION}.${REVISION_NO}" \
|
||||
@@ -92,7 +91,7 @@ ${JAVA_HOME}/bin/jpackage \
|
||||
--java-options "-Dcryptomator.showTrayIcon=true" \
|
||||
--java-options "-Dcryptomator.integrationsLinux.trayIconsDir=\"@{appdir}/usr/share/icons/hicolor/symbolic/apps\"" \
|
||||
--java-options "-Dcryptomator.buildNumber=\"appimage-${REVISION_NO}\"" \
|
||||
--add-launcher cryptomator-gtk2=launcher-gtk2.properties \
|
||||
--java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" \
|
||||
--resource-dir ../resources
|
||||
|
||||
# transform AppDir
|
||||
@@ -110,23 +109,23 @@ cp ../common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/ap
|
||||
cp ../common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml
|
||||
cp ../common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml
|
||||
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg
|
||||
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg
|
||||
ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon
|
||||
ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop
|
||||
ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/org.cryptomator.Cryptomator.desktop
|
||||
ln -s org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml
|
||||
ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun
|
||||
|
||||
# load AppImageTool
|
||||
curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-${MACHINE_TYPE}.AppImage -o /tmp/appimagetool.AppImage
|
||||
curl -L https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${CPU_ARCH}.AppImage -o /tmp/appimagetool.AppImage
|
||||
chmod +x /tmp/appimagetool.AppImage
|
||||
|
||||
# create AppImage
|
||||
/tmp/appimagetool.AppImage \
|
||||
Cryptomator.AppDir \
|
||||
cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage \
|
||||
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${MACHINE_TYPE}.AppImage.zsync'
|
||||
cryptomator-${SEMVER_STR}-${CPU_ARCH}.AppImage \
|
||||
-u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-${CPU_ARCH}.AppImage.zsync'
|
||||
|
||||
echo ""
|
||||
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-${MACHINE_TYPE}.AppImage"
|
||||
echo "Done. AppImage successfully created: cryptomator-${SEMVER_STR}-${CPU_ARCH}.AppImage"
|
||||
echo ""
|
||||
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir runtime squashfs-root openjfx-jmods; rm launcher-gtk2.properties /tmp/appimagetool.AppImage openjfx-jmods.zip"
|
||||
echo >&2 "To clean up, run: rm -rf Cryptomator.AppDir appdir runtime squashfs-root openjfx-jmods; rm /tmp/appimagetool.AppImage openjfx-jmods.zip"
|
||||
echo ""
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2018 Armin Schrenk <armin.schrenk@zoho.eu> -->
|
||||
<component type="desktop-application">
|
||||
<id>org.cryptomator.Cryptomator</id>
|
||||
<metadata_license>FSFAP</metadata_license>
|
||||
<project_license>GPL-3.0-or-later</project_license>
|
||||
<name>Cryptomator</name>
|
||||
<summary>Multi-platform client-side encryption tool optimized for cloud storages</summary>
|
||||
<summary>Encryption for your cloud made easy</summary>
|
||||
|
||||
<keywords>
|
||||
<keyword>encryption</keyword>
|
||||
<keyword>security</keyword>
|
||||
<keyword>privacy</keyword>
|
||||
</keywords>
|
||||
|
||||
<description>
|
||||
<p>
|
||||
Cryptomator provides transparent, client-side encryption for your cloud. Protect your documents from unauthorized
|
||||
access. Cryptomator is free and open source software, so you can rest assured there are no backdoors.
|
||||
Cryptomator provides easy-to-use, transparent, client-side encryption for your cloud.
|
||||
It protects your documents from unauthorized access and prying eyes, while you will still be able to view and edit your documents locally.
|
||||
By not requiring any registration or account and performing all encryption locally, it gives you back control over your data and ensures your privacy.
|
||||
Cryptomator is offered for all major platforms (including Android and iOS).
|
||||
</p>
|
||||
<p>
|
||||
Cryptomator encrypts file contents and names using AES. Your passphrase is protected against bruteforcing attempts
|
||||
using scrypt. Directory structures get obfuscated. The only thing which cannot be encrypted without breaking your
|
||||
cloud synchronization is the modification date of your files.
|
||||
Cryptomator encrypts file contents and names using the widespread industry standard AES.
|
||||
Your passphrase is protected against brute forcing attempts using scrypt.
|
||||
Additionally, directory structures get obfuscated.
|
||||
For more info about the Cryptomator encryption scheme, check out the online documentation.
|
||||
</p>
|
||||
<p>
|
||||
Cryptomator is a free and open source software licensed under the GPLv3. This allows anyone to check our code. It
|
||||
is impossible to introduce backdoors for third parties. Also we cannot hide vulnerabilities. And the best thing
|
||||
is: There is no need to trust us, as you can control us!
|
||||
</p>
|
||||
<p>
|
||||
Vendor lock-ins are impossible. Even if we decided to stop development: The source code is already cloned by
|
||||
hundreds of other developers. As you don't need an account, you will never stand in front of locked doors.
|
||||
Cryptomator is a free and open-source software licensed under the GPLv3.
|
||||
This allows anyone to check our code.
|
||||
Thus, it is impossible to introduce backdoors for third parties or to hide vulnerabilities, so you do not need to trust Cryptomator.
|
||||
Also, vendor lock-ins are impossible.
|
||||
Even if we decided to stop development: The source code is already cloned by hundreds of other developers and development can be picked up by others.
|
||||
</p>
|
||||
</description>
|
||||
|
||||
@@ -42,56 +48,163 @@
|
||||
</provides>
|
||||
|
||||
<screenshots>
|
||||
<screenshot>
|
||||
<caption>Light theme</caption>
|
||||
<image>https://user-images.githubusercontent.com/11858409/156986109-6e58f59c-8b8c-4501-b33b-bb1e33007cea.png</image>
|
||||
<screenshot type="default">
|
||||
<caption>Encrypts your data, protects your privacy</caption>
|
||||
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlockDialog_light.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Dark theme</caption>
|
||||
<image>https://user-images.githubusercontent.com/11858409/156986113-6c5d7801-86e0-4643-bc2f-aff9d95d3ce0.png</image>
|
||||
<caption>Dark theme available</caption>
|
||||
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_dark.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Easy to use - work on encrypted files as if they were not</caption>
|
||||
<image>https://static.cryptomator.org/desktop/flathubScreenshots/MainWindowUnlocked_light.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
<branding>
|
||||
<color type="primary" scheme_preference="light">#EBF5EB</color>
|
||||
<color type="primary" scheme_preference="dark">#2F4858</color>
|
||||
</branding>
|
||||
|
||||
<url type="homepage">https://cryptomator.org/</url>
|
||||
<url type="bugtracker">https://github.com/cryptomator/cryptomator/issues/</url>
|
||||
<url type="donation">https://cryptomator.org/donate</url>
|
||||
<url type="faq">https://community.cryptomator.org/c/kb/faq</url>
|
||||
<url type="help">https://community.cryptomator.org/</url>
|
||||
<url type="help">https://docs.cryptomator.org/</url>
|
||||
<url type="translate">https://translate.cryptomator.org</url>
|
||||
|
||||
<developer_name>Skymatic GmbH</developer_name>
|
||||
<developer id="de.skymatic">
|
||||
<name>Skymatic GmbH</name>
|
||||
</developer>
|
||||
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="social-info">mild</content_attribute> <!-- update checker connects to https://api.cryptomator.org/updates/latestVersion.json -->
|
||||
</content_rating>
|
||||
|
||||
<releases>
|
||||
<release date="2024-02-06" version="1.12.0"/>
|
||||
<release date="2023-12-05" version="1.11.1"/>
|
||||
<release date="2023-11-08" version="1.11.0"/>
|
||||
<release date="2023-09-20" version="1.10.1"/>
|
||||
<release date="2023-09-11" version="1.10.0"/>
|
||||
<release date="2023-08-11" version="1.9.4"/>
|
||||
<release date="2023-08-07" version="1.9.3"/>
|
||||
<release date="2023-07-24" version="1.9.2"/>
|
||||
<release date="2023-06-07" version="1.9.1"/>
|
||||
<release date="2023-05-30" version="1.9.0"/>
|
||||
<release date="2023-04-25" version="1.8.0"/>
|
||||
<release date="2023-04-07" version="1.7.5"/>
|
||||
<release date="2023-04-05" version="1.7.4"/>
|
||||
<release date="2023-03-15" version="1.7.3"/>
|
||||
<release date="2023-03-07" version="1.7.2"/>
|
||||
<release date="2023-03-03" version="1.7.1"/>
|
||||
<release date="2023-03-01" version="1.7.0"/>
|
||||
<release date="2022-12-14" version="1.6.17"/>
|
||||
<release date="2022-12-06" version="1.6.16"/>
|
||||
<release date="2022-10-06" version="1.6.15"/>
|
||||
<release date="2022-08-31" version="1.6.14"/>
|
||||
<release date="2022-07-27" version="1.6.12"/>
|
||||
<release date="2022-07-26" version="1.6.11"/>
|
||||
<release date="2022-05-03" version="1.6.10"/>
|
||||
<release date="2022-04-27" version="1.6.9"/>
|
||||
<release date="2022-03-30" version="1.6.8"/>
|
||||
<release date="2021-12-16" version="1.6.5"/>
|
||||
<release date="2025-05-15" version="1.16.2">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.16.2</url>
|
||||
</release>
|
||||
<release date="2025-04-30" version="1.16.1">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.16.1</url>
|
||||
</release>
|
||||
<release date="2025-04-29" version="1.16.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.16.0</url>
|
||||
</release>
|
||||
<release date="2025-04-09" version="1.15.3">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.3</url>
|
||||
</release>
|
||||
<release date="2025-04-04" version="1.15.2">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.2</url>
|
||||
</release>
|
||||
<release date="2025-02-05" version="1.15.1">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.1</url>
|
||||
</release>
|
||||
<release date="2025-02-03" version="1.15.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.15.0</url>
|
||||
</release>
|
||||
<release date="2024-11-19" version="1.14.2">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.14.2</url>
|
||||
</release>
|
||||
<release date="2024-09-17" version="1.14.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.14.0</url>
|
||||
</release>
|
||||
<release date="2024-06-26" version="1.13.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.13.0</url>
|
||||
</release>
|
||||
<release date="2024-03-27" version="1.12.4">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.4</url>
|
||||
</release>
|
||||
<release date="2024-02-27" version="1.12.3">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.3</url>
|
||||
</release>
|
||||
<release date="2024-02-09" version="1.12.2">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.2</url>
|
||||
</release>
|
||||
<release date="2024-02-07" version="1.12.1">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.1</url>
|
||||
</release>
|
||||
<release date="2024-02-06" version="1.12.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.12.0</url>
|
||||
</release>
|
||||
<release date="2023-12-05" version="1.11.1">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.11.1</url>
|
||||
</release>
|
||||
<release date="2023-11-08" version="1.11.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.11.0</url>
|
||||
</release>
|
||||
<release date="2023-09-20" version="1.10.1">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.10.1</url>
|
||||
</release>
|
||||
<release date="2023-09-11" version="1.10.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.10.0</url>
|
||||
</release>
|
||||
<release date="2023-08-11" version="1.9.4">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.9.4</url>
|
||||
</release>
|
||||
<release date="2023-08-07" version="1.9.3">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.9.3</url>
|
||||
</release>
|
||||
<release date="2023-07-24" version="1.9.2">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.9.2</url>
|
||||
</release>
|
||||
<release date="2023-06-07" version="1.9.1">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.9.1</url>
|
||||
</release>
|
||||
<release date="2023-05-30" version="1.9.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.9.0</url>
|
||||
</release>
|
||||
<release date="2023-04-25" version="1.8.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.8.0</url>
|
||||
</release>
|
||||
<release date="2023-04-07" version="1.7.5">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.7.5</url>
|
||||
</release>
|
||||
<release date="2023-04-05" version="1.7.4">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.7.4</url>
|
||||
</release>
|
||||
<release date="2023-03-15" version="1.7.3">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.7.3</url>
|
||||
</release>
|
||||
<release date="2023-03-07" version="1.7.2">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.7.2</url>
|
||||
</release>
|
||||
<release date="2023-03-03" version="1.7.1">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.7.1</url>
|
||||
</release>
|
||||
<release date="2023-03-01" version="1.7.0">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.7.0</url>
|
||||
</release>
|
||||
<release date="2022-12-14" version="1.6.17">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.17</url>
|
||||
</release>
|
||||
<release date="2022-12-06" version="1.6.16">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.16</url>
|
||||
</release>
|
||||
<release date="2022-10-06" version="1.6.15">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.15</url>
|
||||
</release>
|
||||
<release date="2022-08-31" version="1.6.14">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.14</url>
|
||||
</release>
|
||||
<release date="2022-07-27" version="1.6.12">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.12</url>
|
||||
</release>
|
||||
<release date="2022-07-26" version="1.6.11">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.11</url>
|
||||
</release>
|
||||
<release date="2022-05-03" version="1.6.10">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.10</url>
|
||||
</release>
|
||||
<release date="2022-04-27" version="1.6.9">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.9</url>
|
||||
</release>
|
||||
<release date="2022-03-30" version="1.6.8">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.8</url>
|
||||
</release>
|
||||
<release date="2021-12-16" version="1.6.5">
|
||||
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.6.5</url>
|
||||
</release>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
<svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<style
|
||||
id="current-color-scheme" type="text/css">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
<g fill-rule="evenodd" style="fill:#f2f2f2;fill-opacity:1" class="ColorScheme-Text" fill="currentColor">
|
||||
<path d="m15.591 35.824c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.043c.051 1.03-.161 2.644-.509 2.834zm6.409-6.824h-2l.5-5a2 2 0 1 1 1 0zm-14.544-3.241.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm13.544-10.759a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm14.473 6a8.067 8.067 0 0 0 -8.08 8v2.141a3.891 3.891 0 0 0 -2.893 3.734v5.125a23.166 23.166 0 0 1 -4.174-1.623 7.857 7.857 0 0 1 -.027.878 3.263 3.263 0 0 1 -.729 2.074l-1.794 1.483a.379.379 0 0 1 -.276.188h-4c-1.324 0-2.346-1.336-2.653-3.343a7.058 7.058 0 0 1 .234-3.18 3.477 3.477 0 0 1 1.636-2.157 1.868 1.868 0 0 1 .783-.32h1.5a8.035 8.035 0 0 1 -1.5-5 11.1 11.1 0 0 1 .5-3 2.519 2.519 0 0 0 0-1.5 13.272 13.272 0 0 1 -.5-3.5c6.687-1.936 11 0 11 0s4.319-1.955 11 0"/>
|
||||
<path d="m39 28h-10v-4a3.13 3.13 0 0 1 3-3 3.087 3.087 0 0 1 3 3v1a1.034 1.034 0 0 0 1 1h1a1.034 1.034 0 0 0 1-1v-1a6 6 0 0 0 -12 0v4h-1a2.073 2.073 0 0 0 -2 2v6a2.073 2.073 0 0 0 2 2h14a2.073 2.073 0 0 0 2-2v-6a2.073 2.073 0 0 0 -2-2zm-5.391 5.94a1.609 1.609 0 0 1 -3.217 0v-1.876a1.609 1.609 0 0 1 3.217 0z"/>
|
||||
<defs>
|
||||
<style id="current-color-scheme" type="text/css">
|
||||
.ColorScheme-Text {
|
||||
color:#222222;
|
||||
}
|
||||
.ColorScheme-Highlight {
|
||||
color:#49B04A;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g fill-rule="evenodd">
|
||||
<path d="m15.591 35.824c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.043c.051 1.03-.161 2.644-.509 2.834zm6.409-6.824h-2l.5-5a2 2 0 1 1 1 0zm-14.544-3.241.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm13.544-10.759a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm14.473 6a8.067 8.067 0 0 0 -8.08 8v2.141a3.891 3.891 0 0 0 -2.893 3.734v5.125a23.166 23.166 0 0 1 -4.174-1.623 7.857 7.857 0 0 1 -.027.878 3.263 3.263 0 0 1 -.729 2.074l-1.794 1.483a.379.379 0 0 1 -.276.188h-4c-1.324 0-2.346-1.336-2.653-3.343a7.058 7.058 0 0 1 .234-3.18 3.477 3.477 0 0 1 1.636-2.157 1.868 1.868 0 0 1 .783-.32h1.5a8.035 8.035 0 0 1 -1.5-5 11.1 11.1 0 0 1 .5-3 2.519 2.519 0 0 0 0-1.5 13.272 13.272 0 0 1 -.5-3.5c6.687-1.936 11 0 11 0s4.319-1.955 11 0" class="ColorScheme-Text" fill="currentColor"/>
|
||||
<path d="m39 28h-10v-4a3.13 3.13 0 0 1 3-3 3.087 3.087 0 0 1 3 3v1a1.034 1.034 0 0 0 1 1h1a1.034 1.034 0 0 0 1-1v-1a6 6 0 0 0 -12 0v4h-1a2.073 2.073 0 0 0 -2 2v6a2.073 2.073 0 0 0 2 2h14a2.073 2.073 0 0 0 2-2v-6a2.073 2.073 0 0 0 -2-2zm-5.391 5.94a1.609 1.609 0 0 1 -3.217 0v-1.876a1.609 1.609 0 0 1 3.217 0z" class="ColorScheme-Highlight" fill="currentColor"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -1,8 +1,10 @@
|
||||
<svg height="16" viewBox="0 0 42 42" width="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<style id="current-color-scheme" type="text/css">
|
||||
.ColorScheme-Text {
|
||||
color:#232629;
|
||||
}
|
||||
</style>
|
||||
<path d="m32.66 29.319a1.432 1.432 0 0 0 -.66-.319h-1.5a8.125 8.125 0 0 0 1.5-5 11.027 11.027 0 0 0 -.5-3 2.519 2.519 0 0 1 0-1.5 12.987 12.987 0 0 0 .5-3.5c-6.681-1.955-11 0-11 0s-4.313-1.936-11 0a13.272 13.272 0 0 0 .5 3.5 2.519 2.519 0 0 1 0 1.5 11.1 11.1 0 0 0 -.5 3 8.035 8.035 0 0 0 1.5 5h-1.5a1.868 1.868 0 0 0 -.783.319 3.477 3.477 0 0 0 -1.636 2.157 7.058 7.058 0 0 0 -.234 3.18c.307 2.008 1.329 3.344 2.653 3.344h4a.379.379 0 0 0 .277-.187l1.793-1.483a3.263 3.263 0 0 0 .729-2.074 7.857 7.857 0 0 0 .027-.878 23.166 23.166 0 0 0 4.174 1.622 24.4 24.4 0 0 0 4.051-1.614 7.848 7.848 0 0 0 .027.869 3.263 3.263 0 0 0 .729 2.074l1.793 1.484a.61.61 0 0 0 .4.187h4c1.324 0 2.223-1.336 2.529-3.343a7.057 7.057 0 0 0 -.234-3.18 3.477 3.477 0 0 0 -1.635-2.158zm-17.069 6.5c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.048c.051 1.03-.161 2.644-.509 2.834zm6.409-6.819h-2l.5-5a2 2 0 1 1 1 0zm6.38 7.921a.418.418 0 0 1 -.627.111c-.522-.433-1.439-1.2-1.458-1.208-.348-.189-.56-1.8-.505-2.828a15.84 15.84 0 0 0 2.9-2.037 9.322 9.322 0 0 1 -.31 5.962zm-20.924-11.162.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm33.217 1.2a3.021 3.021 0 0 0 -2.658-1.525 1.574 1.574 0 0 0 -.107-1.283l-.745-1.367a1.779 1.779 0 0 0 -1.317-.891l-.7-1.278a2.409 2.409 0 0 0 .229-2.661 2.283 2.283 0 0 0 -2.375-1.454 7.039 7.039 0 0 1 0 3 3.272 3.272 0 0 0 0 1.5c.047.152-.047.3 0 .5.227.04-.069.156.165.14l.653 1.2a1.579 1.579 0 0 0 -.019 1.557l.745 1.367a1.753 1.753 0 0 0 1.045.832 2.66 2.66 0 0 0 -.238 2.916 2.989 2.989 0 0 0 2.326 1.509.79.79 0 0 0 .082 0 .707.707 0 0 0 .717-.6.688.688 0 0 0 -.636-.749 1.546 1.546 0 0 1 -1.2-.78 1.408 1.408 0 0 1 .672-1.95 1.628 1.628 0 0 1 1.179-.089 1.512 1.512 0 0 1 .9.719 1.37 1.37 0 0 1 -.023 1.361.656.656 0 0 0 .284.92.748.748 0 0 0 .981-.266 2.648 2.648 0 0 0 .04-2.633zm-19.673-11.959a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2z" fill-rule="evenodd" style="fill:#f2f2f2;fill-opacity:1" class="ColorScheme-Text" fill="currentColor"/>
|
||||
<defs>
|
||||
<style id="current-color-scheme" type="text/css">
|
||||
.ColorScheme-Text {
|
||||
color:#222222;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path d="m32.66 29.319a1.432 1.432 0 0 0 -.66-.319h-1.5a8.125 8.125 0 0 0 1.5-5 11.027 11.027 0 0 0 -.5-3 2.519 2.519 0 0 1 0-1.5 12.987 12.987 0 0 0 .5-3.5c-6.681-1.955-11 0-11 0s-4.313-1.936-11 0a13.272 13.272 0 0 0 .5 3.5 2.519 2.519 0 0 1 0 1.5 11.1 11.1 0 0 0 -.5 3 8.035 8.035 0 0 0 1.5 5h-1.5a1.868 1.868 0 0 0 -.783.319 3.477 3.477 0 0 0 -1.636 2.157 7.058 7.058 0 0 0 -.234 3.18c.307 2.008 1.329 3.344 2.653 3.344h4a.379.379 0 0 0 .277-.187l1.793-1.483a3.263 3.263 0 0 0 .729-2.074 7.857 7.857 0 0 0 .027-.878 23.166 23.166 0 0 0 4.174 1.622 24.4 24.4 0 0 0 4.051-1.614 7.848 7.848 0 0 0 .027.869 3.263 3.263 0 0 0 .729 2.074l1.793 1.484a.61.61 0 0 0 .4.187h4c1.324 0 2.223-1.336 2.529-3.343a7.057 7.057 0 0 0 -.234-3.18 3.477 3.477 0 0 0 -1.635-2.158zm-17.069 6.5c-.019.009-.936.775-1.458 1.208a.418.418 0 0 1 -.627-.111 9.322 9.322 0 0 1 -.3-5.974 15.843 15.843 0 0 0 2.894 2.048c.051 1.03-.161 2.644-.509 2.834zm6.409-6.819h-2l.5-5a2 2 0 1 1 1 0zm6.38 7.921a.418.418 0 0 1 -.627.111c-.522-.433-1.439-1.2-1.458-1.208-.348-.189-.56-1.8-.505-2.828a15.84 15.84 0 0 0 2.9-2.037 9.322 9.322 0 0 1 -.31 5.962zm-20.924-11.162.744-1.366a1.579 1.579 0 0 0 -.019-1.557l.653-1.2c.2.014-.03-.113.165-.14.051-.217-.051-.336 0-.5a3.269 3.269 0 0 0 0-1.5 7.151 7.151 0 0 1 0-3 2.366 2.366 0 0 0 -2.378 1.448 2.409 2.409 0 0 0 .229 2.661l-.7 1.278a1.779 1.779 0 0 0 -1.317.891l-.741 1.372a1.577 1.577 0 0 0 -.019 1.487 3.028 3.028 0 0 0 -2.746 1.525 2.648 2.648 0 0 0 .044 2.631.748.748 0 0 0 .981.266.656.656 0 0 0 .284-.92 1.37 1.37 0 0 1 -.023-1.361 1.6 1.6 0 0 1 2.079-.63 1.408 1.408 0 0 1 .672 1.95 1.546 1.546 0 0 1 -1.2.78.688.688 0 0 0 -.636.749.707.707 0 0 0 .717.6.789.789 0 0 0 .082 0 2.989 2.989 0 0 0 2.322-1.513 2.669 2.669 0 0 0 -.377-3.084 1.767 1.767 0 0 0 1.184-.867zm33.217 1.2a3.021 3.021 0 0 0 -2.658-1.525 1.574 1.574 0 0 0 -.107-1.283l-.745-1.367a1.779 1.779 0 0 0 -1.317-.891l-.7-1.278a2.409 2.409 0 0 0 .229-2.661 2.283 2.283 0 0 0 -2.375-1.454 7.039 7.039 0 0 1 0 3 3.272 3.272 0 0 0 0 1.5c.047.152-.047.3 0 .5.227.04-.069.156.165.14l.653 1.2a1.579 1.579 0 0 0 -.019 1.557l.745 1.367a1.753 1.753 0 0 0 1.045.832 2.66 2.66 0 0 0 -.238 2.916 2.989 2.989 0 0 0 2.326 1.509.79.79 0 0 0 .082 0 .707.707 0 0 0 .717-.6.688.688 0 0 0 -.636-.749 1.546 1.546 0 0 1 -1.2-.78 1.408 1.408 0 0 1 .672-1.95 1.628 1.628 0 0 1 1.179-.089 1.512 1.512 0 0 1 .9.719 1.37 1.37 0 0 1 -.023 1.361.656.656 0 0 0 .284.92.748.748 0 0 0 .981-.266 2.648 2.648 0 0 0 .04-2.633zm-19.673-11.959a13.013 13.013 0 0 1 5-1 21.6 21.6 0 0 1 4.5.5 9.312 9.312 0 0 0 -9.5-8.5c-5.794 0-9.176 4-9.5 8.5a21.858 21.858 0 0 1 4.5-.5 12.819 12.819 0 0 1 5 1zm3.5-5c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2zm-7 0c1.209 0 2.5.866 2.5 2h-5c0-1.134 1.291-2 2.5-2z" fill-rule="evenodd" class="ColorScheme-Text" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
2
dist/linux/debian/control
vendored
2
dist/linux/debian/control
vendored
@@ -2,7 +2,7 @@ Source: cryptomator
|
||||
Maintainer: Cryptobot <releases@cryptomator.org>
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>=10), coffeelibs-jdk-21 (= 21.0.1+12-0ppa1), libgtk2.0-0, libgtk-3-0, libxxf86vm1, libgl1
|
||||
Build-Depends: debhelper (>=10), coffeelibs-jdk-23 (>= 23.0.2+7-0ppa1), libgtk-3-0, libxxf86vm1, libgl1
|
||||
Standards-Version: 4.5.0
|
||||
Homepage: https://cryptomator.org
|
||||
Vcs-Git: https://github.com/cryptomator/cryptomator.git
|
||||
|
||||
4
dist/linux/debian/copyright
vendored
4
dist/linux/debian/copyright
vendored
@@ -4,11 +4,11 @@ Upstream-Contact: Cryptomator <info@cryptomator.org>
|
||||
Source: https://cryptomator.org
|
||||
|
||||
Files: *
|
||||
Copyright: 2016-2024 Skymatic GmbH
|
||||
Copyright: 2016-2025 Skymatic GmbH
|
||||
License: GPL-3+
|
||||
|
||||
Files: debian/org.cryptomator.Cryptomator.appdata.xml
|
||||
Copyright: 2016-2024 Skymatic GmbH
|
||||
Copyright: 2016-2025 Skymatic GmbH
|
||||
License: FSFAP
|
||||
|
||||
License: GPL-3+
|
||||
|
||||
8
dist/linux/debian/rules
vendored
8
dist/linux/debian/rules
vendored
@@ -4,7 +4,7 @@
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
JAVA_HOME = /usr/lib/jvm/java-21-coffeelibs
|
||||
JAVA_HOME = /usr/lib/jvm/java-23-coffeelibs
|
||||
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
|
||||
ifeq ($(DEB_BUILD_ARCH),amd64)
|
||||
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
|
||||
@@ -28,7 +28,7 @@ override_dh_auto_build:
|
||||
$(JAVA_HOME)/bin/jlink \
|
||||
--output runtime \
|
||||
--module-path "${JMODS_PATH}" \
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net \
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \
|
||||
--strip-native-commands \
|
||||
--no-header-files \
|
||||
--no-man-pages \
|
||||
@@ -45,7 +45,7 @@ override_dh_auto_build:
|
||||
--vendor "Skymatic GmbH" \
|
||||
--java-options "--enable-preview" \
|
||||
--java-options "--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \
|
||||
--copyright "(C) 2016 - 2024 Skymatic GmbH" \
|
||||
--copyright "(C) 2016 - 2025 Skymatic GmbH" \
|
||||
--java-options "-Xss5m" \
|
||||
--java-options "-Xmx256m" \
|
||||
--java-options "-Dfile.encoding=\"utf-8\"" \
|
||||
@@ -61,6 +61,8 @@ override_dh_auto_build:
|
||||
--java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \
|
||||
--java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \
|
||||
--java-options "-Dcryptomator.disableUpdateCheck=\"${DISABLE_UPDATE_CHECK}\"" \
|
||||
--java-options "-Dcryptomator.integrationsLinux.autoStartCmd=\"cryptomator\"" \
|
||||
--java-options "-Dcryptomator.networking.truststore.p12Path=\"/etc/cryptomator/certs.p12\"" \
|
||||
--app-version "${VERSION_NUM}.${REVISION_NUM}" \
|
||||
--resource-dir resources \
|
||||
--verbose
|
||||
|
||||
14
dist/linux/launcher-gtk2.properties
vendored
14
dist/linux/launcher-gtk2.properties
vendored
@@ -1,14 +0,0 @@
|
||||
java-options=-Xss5m \
|
||||
-Xmx256m \
|
||||
--enable-preview \
|
||||
--enable-native-access=org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64 \
|
||||
-Dfile.encoding=\"utf-8\" \
|
||||
-Dcryptomator.appVersion=\"${SEMVER_STR}\" \
|
||||
-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\" \
|
||||
-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\" \
|
||||
-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\" \
|
||||
-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\" \
|
||||
-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\" \
|
||||
-Dcryptomator.showTrayIcon=false \
|
||||
-Dcryptomator.buildNumber=\"appimage-${REVISION_NUM}\" \
|
||||
-Djdk.gtk.version=2
|
||||
1
dist/mac/.gitignore
vendored
Normal file
1
dist/mac/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
embedded.provisionprofile
|
||||
8
dist/mac/Cryptomator.entitlements
vendored
8
dist/mac/Cryptomator.entitlements
vendored
@@ -2,6 +2,10 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.application-identifier</key>
|
||||
<string>###APP_IDENTIFIER_PREFIX###org.cryptomator</string>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>###TEAM_IDENTIFIER###</string>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
@@ -10,5 +14,9 @@
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>###APP_IDENTIFIER_PREFIX###org.cryptomator</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
7
dist/mac/dmg/.gitignore
vendored
7
dist/mac/dmg/.gitignore
vendored
@@ -1,6 +1,9 @@
|
||||
# created during build
|
||||
# downloaded/created during build
|
||||
Cryptomator.app/
|
||||
runtime/
|
||||
dmg/
|
||||
*.dmg
|
||||
license.rtf
|
||||
license.rtf
|
||||
openjfx-jmods.zip
|
||||
*.jmod
|
||||
Cryptomator.entitlements
|
||||
|
||||
42
dist/mac/dmg/build.sh
vendored
42
dist/mac/dmg/build.sh
vendored
@@ -1,12 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
# parse options
|
||||
usage() { echo "Usage: $0 [-s <codesign-identity>]" 1>&2; exit 1; }
|
||||
while getopts ":s:" o; do
|
||||
usage() { echo "Usage: $0 [-s <codesign-identity>] [-t <team-identifier>]" 1>&2; exit 1; }
|
||||
while getopts ":s:t:" o; do
|
||||
case "${o}" in
|
||||
s)
|
||||
CODESIGN_IDENTITY=${OPTARG}
|
||||
;;
|
||||
t)
|
||||
TEAM_IDENTIFIER=${OPTARG}
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
@@ -21,7 +24,7 @@ rm -rf runtime dmg *.app *.dmg
|
||||
# set variables
|
||||
APP_NAME="Cryptomator"
|
||||
VENDOR="Skymatic GmbH"
|
||||
COPYRIGHT_YEARS="2016 - 2024"
|
||||
COPYRIGHT_YEARS="2016 - 2025"
|
||||
PACKAGE_IDENTIFIER="org.cryptomator"
|
||||
MAIN_JAR_GLOB="cryptomator-*.jar"
|
||||
MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
|
||||
@@ -29,13 +32,17 @@ REVISION_NO=`git rev-list --count HEAD`
|
||||
VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'`
|
||||
FUSE_LIB="FUSE-T"
|
||||
|
||||
ARCH="undefined"
|
||||
JAVAFX_VERSION=23.0.2
|
||||
JAVAFX_ARCH="undefined"
|
||||
JAVAFX_JMODS_SHA256="undefined"
|
||||
if [ "$(machine)" = "arm64e" ]; then
|
||||
ARCH="aarch64"
|
||||
JAVAFX_ARCH="aarch64"
|
||||
JAVAFX_JMODS_SHA256="c690cc642a3924cf56622951f478ba57aec9ce09063761f800c3319331bed3fc"
|
||||
else
|
||||
ARCH="x64"
|
||||
JAVAFX_ARCH="x64"
|
||||
JAVAFX_JMODS_SHA256="5e6c65c065eea22430c0eab36f37a5985eb8ad99e19e8772262021740d338f68"
|
||||
fi
|
||||
OPENJFX_JMODS="https://download2.gluonhq.com/openjfx/21.0.1/openjfx-21.0.1_osx-${ARCH}_bin-jmods.zip"
|
||||
JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${JAVAFX_ARCH}_bin-jmods.zip"
|
||||
|
||||
# check preconditions
|
||||
if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi
|
||||
@@ -47,7 +54,8 @@ if [ -n "${CODESIGN_IDENTITY}" ]; then
|
||||
fi
|
||||
|
||||
# download and check jmods
|
||||
curl -L ${OPENJFX_JMODS} -o openjfx-jmods.zip
|
||||
curl -L ${JAVAFX_JMODS_URL} -o openjfx-jmods.zip
|
||||
echo "${JAVAFX_JMODS_SHA256} openjfx-jmods.zip" | shasum -a256 --check
|
||||
mkdir -p openjfx-jmods/
|
||||
unzip -jo openjfx-jmods.zip \*/javafx.base.jmod \*/javafx.controls.jmod \*/javafx.fxml.jmod \*/javafx.graphics.jmod -d openjfx-jmods
|
||||
JMOD_VERSION=$(jmod describe openjfx-jmods/javafx.base.jmod | head -1)
|
||||
@@ -63,7 +71,7 @@ if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then
|
||||
fi
|
||||
|
||||
# compile
|
||||
mvn -B -f../../../pom.xml clean package -DskipTests -Pmac
|
||||
mvn -B -Djavafx.platform=mac -f../../../pom.xml clean package -DskipTests -Pmac
|
||||
cp ../../../LICENSE.txt ../../../target
|
||||
cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
|
||||
|
||||
@@ -71,7 +79,7 @@ cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods
|
||||
${JAVA_HOME}/bin/jlink \
|
||||
--output runtime \
|
||||
--module-path "${JAVA_HOME}/jmods:openjfx-jmods" \
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.crypto.ec,jdk.security.auth,jdk.accessibility,jdk.management.jfr \
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,java.compiler \
|
||||
--strip-native-commands \
|
||||
--no-header-files \
|
||||
--no-man-pages \
|
||||
@@ -116,9 +124,10 @@ ${JAVA_HOME}/bin/jpackage \
|
||||
cp ../resources/${APP_NAME}-Vault.icns ${APP_NAME}.app/Contents/Resources/
|
||||
sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" ${APP_NAME}.app/Contents/Info.plist
|
||||
sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" ${APP_NAME}.app/Contents/Info.plist
|
||||
cp ../embedded.provisionprofile ${APP_NAME}.app/Contents/
|
||||
|
||||
# generate license
|
||||
mvn -B -f../../../pom.xml license:add-third-party \
|
||||
mvn -B -Djavafx.platform=mac -f../../../pom.xml license:add-third-party \
|
||||
-Dlicense.thirdPartyFilename=license.rtf \
|
||||
-Dlicense.outputDirectory=dist/mac/dmg/resources \
|
||||
-Dlicense.fileTemplate=resources/licenseTemplate.ftl \
|
||||
@@ -128,7 +137,11 @@ mvn -B -f../../../pom.xml license:add-third-party \
|
||||
-Dlicense.licenseMergesUrl=file://$(pwd)/../../../license/merges
|
||||
|
||||
# codesign
|
||||
if [ -n "${CODESIGN_IDENTITY}" ]; then
|
||||
if [ -n "${CODESIGN_IDENTITY}" ] && [ -n "${TEAM_IDENTIFIER}" ]; then
|
||||
echo "Codesigning jdk files..."
|
||||
find ${APP_NAME}.app/Contents/runtime/Contents/Home/lib/ -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||
find ${APP_NAME}.app/Contents/runtime/Contents/Home/lib/ -name 'jspawnhelper' -exec codesign --force -o runtime -s ${CODESIGN_IDENTITY} {} \;
|
||||
echo "Codesigning jar contents..."
|
||||
find ${APP_NAME}.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \;
|
||||
for JAR_PATH in `find ${APP_NAME}.app -name "*.jar"`; do
|
||||
if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then
|
||||
@@ -146,7 +159,10 @@ if [ -n "${CODESIGN_IDENTITY}" ]; then
|
||||
fi
|
||||
done
|
||||
echo "Codesigning ${APP_NAME}.app..."
|
||||
codesign --force --deep --entitlements ../${APP_NAME}.entitlements -o runtime -s ${CODESIGN_IDENTITY} ${APP_NAME}.app
|
||||
cp ../${APP_NAME}.entitlements .
|
||||
sed -i '' "s|###APP_IDENTIFIER_PREFIX###|${TEAM_IDENTIFIER}.|g" ${APP_NAME}.entitlements
|
||||
sed -i '' "s|###TEAM_IDENTIFIER###|${TEAM_IDENTIFIER}|g" ${APP_NAME}.entitlements
|
||||
codesign --force --deep --entitlements ${APP_NAME}.entitlements -o runtime -s ${CODESIGN_IDENTITY} ${APP_NAME}.app
|
||||
fi
|
||||
|
||||
# prepare dmg contents
|
||||
|
||||
2
dist/mac/dmg/resources/licenseTemplate.ftl
vendored
2
dist/mac/dmg/resources/licenseTemplate.ftl
vendored
@@ -17,7 +17,7 @@
|
||||
\f1\b0 \
|
||||
\
|
||||
|
||||
\f0\b \'a9 2016 \'96 2024 Skymatic GmbH
|
||||
\f0\b \'a9 2016 \'96 2025 Skymatic GmbH
|
||||
\f1\b0 \
|
||||
\
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\
|
||||
|
||||
2
dist/mac/resources/Info.plist
vendored
2
dist/mac/resources/Info.plist
vendored
@@ -3,7 +3,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.13.0</string>
|
||||
<string>11</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
|
||||
1
dist/win/.gitignore
vendored
1
dist/win/.gitignore
vendored
@@ -6,4 +6,5 @@ installer
|
||||
*.msi
|
||||
*.exe
|
||||
*.jmod
|
||||
resources/jfxJmods.zip
|
||||
license.rtf
|
||||
2
dist/win/build.bat
vendored
2
dist/win/build.bat
vendored
@@ -11,7 +11,7 @@ SET HELP_URL="https://cryptomator.org/contact/"
|
||||
SET MODULE_AND_MAIN_CLASS="org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator"
|
||||
SET LOOPBACK_ALIAS="cryptomator-vault"
|
||||
|
||||
powershell -NoLogo -NoProfile -ExecutionPolicy Unrestricted -Command .\build.ps1^
|
||||
pwsh -NoLogo -NoProfile -ExecutionPolicy Unrestricted -Command .\build.ps1^
|
||||
-AppName %APPNAME%^
|
||||
-MainJarGlob "%MAIN_JAR_GLOB%"^
|
||||
-ModuleAndMainClass "%MODULE_AND_MAIN_CLASS%"^
|
||||
|
||||
37
dist/win/build.ps1
vendored
37
dist/win/build.ps1
vendored
@@ -41,7 +41,7 @@ Write-Output "`$Env:JAVA_HOME=$Env:JAVA_HOME"
|
||||
$copyright = "(C) $CopyrightStartYear - $((Get-Date).Year) $Vendor"
|
||||
|
||||
# compile
|
||||
&mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin
|
||||
&mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin "-Djavafx.platform=win"
|
||||
Copy-Item "$buildDir\..\..\target\$MainJarGlob.jar" -Destination "$buildDir\..\..\target\mods"
|
||||
|
||||
# add runtime
|
||||
@@ -51,22 +51,23 @@ if ($clean -and (Test-Path -Path $runtimeImagePath)) {
|
||||
}
|
||||
|
||||
## download jfx jmods
|
||||
$jmodsVersion='21.0.1'
|
||||
$jmodsUrl = "https://download2.gluonhq.com/openjfx/${jmodsVersion}/openjfx-${jmodsVersion}_windows-x64_bin-jmods.zip"
|
||||
$jfxJmodsChecksum = 'daf8acae631c016c24cfe23f88469400274d3441dd890615a42dfb501f3eb94a'
|
||||
$jfxJmodsZip = '.\resources\jfxJmods.zip'
|
||||
if( !(Test-Path -Path $jfxJmodsZip) ) {
|
||||
Write-Output "Downloading ${jmodsUrl}..."
|
||||
Invoke-WebRequest $jmodsUrl -OutFile $jfxJmodsZip # redirects are followed by default
|
||||
$javaFxVersion='23.0.2'
|
||||
$javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip"
|
||||
$javaFxJmodsSHA256 = 'ee176dcee3bd78bde7910735bd67f67c792882f5b89626796ae06f7a1c0119d3'
|
||||
$javaFxJmods = '.\resources\jfxJmods.zip'
|
||||
if( !(Test-Path -Path $javaFxJmods) ) {
|
||||
Write-Output "Downloading ${javaFxJmodsUrl}..."
|
||||
Invoke-WebRequest $javaFxJmodsUrl -OutFile $javaFxJmods # redirects are followed by default
|
||||
}
|
||||
|
||||
$jmodsChecksumActual = $(Get-FileHash -Path $jfxJmodsZip -Algorithm SHA256).Hash
|
||||
if( $jmodsChecksumActual -ne $jfxJmodsChecksum ) {
|
||||
Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $jfxJmodsChecksum, actual: $jmodsChecksumActual"
|
||||
$jmodsChecksumActual = $(Get-FileHash -Path $javaFxJmods -Algorithm SHA256).Hash.ToLower()
|
||||
if( $jmodsChecksumActual -ne $javaFxJmodsSHA256 ) {
|
||||
Write-Error "Checksum mismatch for jfxJmods.zip. Expected: $javaFxJmodsSHA256
|
||||
, actual: $jmodsChecksumActual"
|
||||
exit 1;
|
||||
}
|
||||
Expand-Archive -Path $jfxJmodsZip -Force -DestinationPath ".\resources\"
|
||||
Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods"
|
||||
Expand-Archive -Path $javaFxJmods -Force -DestinationPath ".\resources\"
|
||||
Remove-Item -Recurse -Force -Path ".\resources\javafx-jmods" -ErrorAction Ignore
|
||||
Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\javafx-jmods" -ErrorAction Stop
|
||||
|
||||
## create custom runtime
|
||||
@@ -74,7 +75,7 @@ Move-Item -Force -Path ".\resources\javafx-jmods-*" -Destination ".\resources\ja
|
||||
--verbose `
|
||||
--output runtime `
|
||||
--module-path "$Env:JAVA_HOME/jmods;$buildDir/resources/javafx-jmods" `
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr,javafx.base,javafx.graphics,javafx.controls,javafx.fxml `
|
||||
--add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml `
|
||||
--strip-native-commands `
|
||||
--no-header-files `
|
||||
--no-man-pages `
|
||||
@@ -99,7 +100,7 @@ if ($clean -and (Test-Path -Path $appPath)) {
|
||||
--vendor $Vendor `
|
||||
--copyright $copyright `
|
||||
--java-options "--enable-preview" `
|
||||
--java-options "--enable-native-access=org.cryptomator.jfuse.win" `
|
||||
--java-options "--enable-native-access=org.cryptomator.jfuse.win,org.cryptomator.integrations.win" `
|
||||
--java-options "-Xss5m" `
|
||||
--java-options "-Xmx256m" `
|
||||
--java-options "-Dcryptomator.appVersion=`"$semVerNo`"" `
|
||||
@@ -121,7 +122,7 @@ if ($clean -and (Test-Path -Path $appPath)) {
|
||||
--icon resources/$AppName.ico
|
||||
|
||||
#Create RTF license for msi
|
||||
&mvn -B -f $buildDir/../../pom.xml license:add-third-party `
|
||||
&mvn -B -f $buildDir/../../pom.xml license:add-third-party "-Djavafx.platform=win" `
|
||||
"-Dlicense.thirdPartyFilename=license.rtf" `
|
||||
"-Dlicense.fileTemplate=$buildDir\resources\licenseTemplate.ftl" `
|
||||
"-Dlicense.outputDirectory=$buildDir\resources\" `
|
||||
@@ -166,7 +167,7 @@ $Env:JP_WIXHELPER_DIR = "."
|
||||
--file-associations resources/FAvaultFile.properties
|
||||
|
||||
#Create RTF license for bundle
|
||||
&mvn -B -f $buildDir/../../pom.xml license:add-third-party `
|
||||
&mvn -B -f $buildDir/../../pom.xml license:add-third-party "-Djavafx.platform=win" `
|
||||
"-Dlicense.thirdPartyFilename=license.rtf" `
|
||||
"-Dlicense.fileTemplate=$buildDir\bundle\resources\licenseTemplate.ftl" `
|
||||
"-Dlicense.outputDirectory=$buildDir\bundle\resources\" `
|
||||
@@ -181,7 +182,7 @@ Write-Output "Downloading ${winfspMsiUrl}..."
|
||||
Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default
|
||||
|
||||
# download legacy-winfsp uninstaller
|
||||
$winfspUninstaller= 'https://github.com/cryptomator/winfsp-uninstaller/releases/download/1.0.0/winfsp-uninstaller.exe'
|
||||
$winfspUninstaller= 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe'
|
||||
Write-Output "Downloading ${winfspUninstaller}..."
|
||||
Invoke-WebRequest $winfspUninstaller -OutFile ".\bundle\resources\winfsp-uninstaller.exe" # redirects are followed by default
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
\vieww12000\viewh15840\viewkind0
|
||||
\pard\tx283\tx567\tx850\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par
|
||||
\par
|
||||
\b\'a9 2016 \'96 2024 Skymatic GmbH \b0\par
|
||||
\b\'a9 2016 \'96 2025 Skymatic GmbH \b0\par
|
||||
\par
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par
|
||||
\par
|
||||
|
||||
BIN
dist/win/contrib/dokan1.dll
vendored
BIN
dist/win/contrib/dokan1.dll
vendored
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
@echo off
|
||||
:: see comments in file ./version170-migrate-settings.ps1
|
||||
|
||||
cd %~dp0
|
||||
powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -Command .\version170-migrate-settings.ps1
|
||||
35
dist/win/contrib/version170-migrate-settings.ps1
vendored
35
dist/win/contrib/version170-migrate-settings.ps1
vendored
@@ -1,35 +0,0 @@
|
||||
# This script migrates Cryptomator settings for all local users on Windows in case the users uses custom directories as mountpoint
|
||||
# See also https://github.com/cryptomator/cryptomator/pull/2654.
|
||||
#
|
||||
# TODO: This script should be evaluated in a yearly interval if it is still needed and if not, should be removed
|
||||
#
|
||||
#Requires -RunAsAdministrator
|
||||
|
||||
#Get all active, local user profiles
|
||||
$profileList = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'
|
||||
Get-ChildItem $profileList | ForEach-Object {
|
||||
$profilePath = $_.GetValue("ProfileImagePath")
|
||||
$settingsPath = "$profilePath\AppData\Roaming\Cryptomator\settings.json"
|
||||
if(!(Test-Path -Path $settingsPath -PathType Leaf)) {
|
||||
#No settings file, nothing to do.
|
||||
return;
|
||||
}
|
||||
$settings = Get-Content -Path $settingsPath | ConvertFrom-Json
|
||||
if($settings.preferredVolumeImpl -ne "FUSE") {
|
||||
#Fuse not used, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
#check if customMountPoints are used
|
||||
$atLeastOneCustomPath = $false;
|
||||
foreach ($vault in $settings.directories){
|
||||
$atLeastOneCustomPath = $atLeastOneCustomPath -or ($vault.useCustomMountPath -eq "True")
|
||||
}
|
||||
|
||||
#if so, use WinFsp Local Drive
|
||||
if( $atLeastOneCustomPath ) {
|
||||
Add-Member -Force -InputObject $settings -Name "mountService" -Value "org.cryptomator.frontend.fuse.mount.WinFspMountProvider" -MemberType NoteProperty
|
||||
$newSettings = $settings | Select-Object * -ExcludeProperty "preferredVolumeImpl"
|
||||
ConvertTo-Json $newSettings | Set-Content -Path $settingsPath
|
||||
}
|
||||
}
|
||||
2
dist/win/resources/licenseTemplate.ftl
vendored
2
dist/win/resources/licenseTemplate.ftl
vendored
@@ -10,7 +10,7 @@
|
||||
\vieww12000\viewh15840\viewkind0
|
||||
\pard\tx283\tx567\tx850\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par
|
||||
\par
|
||||
\b\'a9 2016 \'96 2024 Skymatic GmbH \b0\par
|
||||
\b\'a9 2016 \'96 2025 Skymatic GmbH \b0\par
|
||||
\par
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par
|
||||
\par
|
||||
|
||||
6
dist/win/resources/main.wxs
vendored
6
dist/win/resources/main.wxs
vendored
@@ -139,11 +139,6 @@
|
||||
Sequence="execute" Before="PatchWebDAV" />
|
||||
<CustomAction Id="PatchWebDAV" BinaryKey="WixCA" DllEntry="WixQuietExec64" Execute="deferred" Return="ignore" Impersonate="no"/>
|
||||
|
||||
<!-- Special Settings migration for 1.7.0,. Should be removed eventually, for more info, see ../contrib/version170-migrate-settings.ps1-->
|
||||
<SetProperty Id="V170MigrateSettings" Value=""[INSTALLDIR]version170-migrate-settings.bat""
|
||||
Sequence="execute" Before="V170MigrateSettings" />
|
||||
<CustomAction Id="V170MigrateSettings" BinaryKey="WixCA" DllEntry="WixQuietExec64" Execute="deferred" Return="ignore" Impersonate="no"/>
|
||||
|
||||
<!-- Running App detection and exit -->
|
||||
<Property Id="FOUNDRUNNINGAPP" Admin="yes"/>
|
||||
<util:CloseApplication
|
||||
@@ -195,7 +190,6 @@
|
||||
<RemoveExistingProducts After="InstallValidate"/> <!-- Moved from CostInitialize, due to WixCloseApplications -->
|
||||
|
||||
<Custom Action="PatchWebDAV" After="InstallFiles">NOT Installed OR REINSTALL</Custom>
|
||||
<Custom Action="V170MigrateSettings" After="InstallFiles">NOT Installed OR REINSTALL</Custom>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<InstallUISequence>
|
||||
|
||||
153
pom.xml
153
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptomator</artifactId>
|
||||
<version>1.12.0</version>
|
||||
<version>1.16.2</version>
|
||||
<name>Cryptomator Desktop App</name>
|
||||
|
||||
<organization>
|
||||
@@ -26,56 +26,73 @@
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.jdk.version>21</project.jdk.version>
|
||||
<project.jdk.version>23</project.jdk.version>
|
||||
|
||||
<!-- Group IDs of jars that need to stay on the class path for now -->
|
||||
<!-- remove them, as soon they got modularized or support is dropped (i.e., WebDAV) -->
|
||||
<nonModularGroupIds>org.ow2.asm,org.apache.jackrabbit,org.apache.httpcomponents</nonModularGroupIds>
|
||||
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptofs.version>2.6.8</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>1.3.0</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>1.2.5</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>1.2.3</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>1.4.2</cryptomator.integrations.linux.version>
|
||||
<cryptomator.fuse.version>4.0.0</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>2.0.0</cryptomator.dokany.version>
|
||||
<cryptomator.webdav.version>2.0.6</cryptomator.webdav.version>
|
||||
<cryptomator.cryptofs.version>2.9.0</cryptomator.cryptofs.version>
|
||||
<cryptomator.integrations.version>1.5.1</cryptomator.integrations.version>
|
||||
<cryptomator.integrations.win.version>1.3.0</cryptomator.integrations.win.version>
|
||||
<cryptomator.integrations.mac.version>1.3.2</cryptomator.integrations.mac.version>
|
||||
<cryptomator.integrations.linux.version>1.5.3</cryptomator.integrations.linux.version>
|
||||
<cryptomator.fuse.version>5.0.5</cryptomator.fuse.version>
|
||||
<cryptomator.webdav.version>2.0.10</cryptomator.webdav.version>
|
||||
|
||||
<!-- 3rd party dependencies -->
|
||||
<commons-lang3.version>3.14.0</commons-lang3.version>
|
||||
<dagger.version>2.50</dagger.version>
|
||||
<commons-lang3.version>3.17.0</commons-lang3.version>
|
||||
<dagger.version>2.56.1</dagger.version>
|
||||
<easybind.version>2.2</easybind.version>
|
||||
<guava.version>33.0.0-jre</guava.version>
|
||||
<jackson.version>2.16.1</jackson.version>
|
||||
<javafx.version>21.0.1</javafx.version>
|
||||
<jwt.version>4.4.0</jwt.version>
|
||||
<jackson.version>2.18.3</jackson.version>
|
||||
<javafx.version>23.0.2</javafx.version>
|
||||
<jwt.version>4.5.0</jwt.version>
|
||||
<nimbus-jose.version>9.37.3</nimbus-jose.version>
|
||||
<logback.version>1.4.14</logback.version>
|
||||
<slf4j.version>2.0.11</slf4j.version>
|
||||
<tinyoauth2.version>0.8.0</tinyoauth2.version>
|
||||
<zxcvbn.version>1.8.2</zxcvbn.version>
|
||||
<logback.version>1.5.18</logback.version>
|
||||
<slf4j.version>2.0.17</slf4j.version>
|
||||
<tinyoauth2.version>0.8.1</tinyoauth2.version>
|
||||
<zxcvbn.version>1.9.0</zxcvbn.version>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<junit.jupiter.version>5.10.2</junit.jupiter.version>
|
||||
<mockito.version>5.10.0</mockito.version>
|
||||
<hamcrest.version>2.2</hamcrest.version>
|
||||
<junit.jupiter.version>5.12.2</junit.jupiter.version>
|
||||
<mockito.version>5.17.0</mockito.version>
|
||||
<hamcrest.version>3.0</hamcrest.version>
|
||||
|
||||
<!-- build-time dependencies -->
|
||||
<jetbrains.annotations.version>24.1.0</jetbrains.annotations.version>
|
||||
<dependency-check.version>9.0.9</dependency-check.version>
|
||||
<jacoco.version>0.8.11</jacoco.version>
|
||||
<license-generator.version>2.4.0</license-generator.version>
|
||||
<junit-tree-reporter.version>1.2.1</junit-tree-reporter.version>
|
||||
<mvn-compiler.version>3.12.1</mvn-compiler.version>
|
||||
<jetbrains.annotations.version>26.0.2</jetbrains.annotations.version>
|
||||
<dependency-check.version>12.1.1</dependency-check.version>
|
||||
<jacoco.version>0.8.13</jacoco.version>
|
||||
<license-generator.version>2.5.0</license-generator.version>
|
||||
<junit-tree-reporter.version>1.4.0</junit-tree-reporter.version>
|
||||
<mvn-compiler.version>3.14.0</mvn-compiler.version>
|
||||
<mvn-resources.version>3.3.1</mvn-resources.version>
|
||||
<mvn-dependency.version>3.6.1</mvn-dependency.version>
|
||||
<mvn-surefire.version>3.2.5</mvn-surefire.version>
|
||||
<mvn-jar.version>3.3.0</mvn-jar.version>
|
||||
<mvn-dependency.version>3.8.1</mvn-dependency.version>
|
||||
<mvn-surefire.version>3.5.3</mvn-surefire.version>
|
||||
<mvn-jar.version>3.4.2</mvn-jar.version>
|
||||
|
||||
<!-- Property used by surefire to determine jacoco engine -->
|
||||
<surefire.jacoco.args></surefire.jacoco.args>
|
||||
</properties>
|
||||
|
||||
<!-- TODO: Remove once webdav version 2.0.11 is released -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>webdav-nio-adapter-servlet</artifactId>
|
||||
<version>1.2.9</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- Cryptomator Libs -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptolib</artifactId>
|
||||
<version>2.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptofs</artifactId>
|
||||
@@ -86,11 +103,6 @@
|
||||
<artifactId>fuse-nio-adapter</artifactId>
|
||||
<version>${cryptomator.fuse.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>dokany-nio-adapter</artifactId>
|
||||
<version>${cryptomator.dokany.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>webdav-nio-adapter</artifactId>
|
||||
@@ -158,17 +170,30 @@
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>${jwt.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
<artifactId>nimbus-jose-jwt</artifactId>
|
||||
<version>${nimbus-jose.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
@@ -185,40 +210,23 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Google -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
<exclusions>
|
||||
<!-- see https://github.com/google/guava/wiki/UseGuavaInYourBuild#what-about-guavas-own-dependencies -->
|
||||
<exclusion>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>listenablefuture</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.checkerframework</groupId>
|
||||
<artifactId>checker-qual</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.google.errorprone</groupId>
|
||||
<artifactId>error_prone_annotations</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.google.j2objc</groupId>
|
||||
<artifactId>j2objc-annotations</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.dagger</groupId>
|
||||
<artifactId>dagger</artifactId>
|
||||
<version>${dagger.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.inject</groupId>
|
||||
<artifactId>jakarta.inject-api</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Caffeine -->
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>3.2.0</version>
|
||||
</dependency>
|
||||
<!-- JUnit / Mockito / Hamcrest -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
@@ -320,7 +328,6 @@
|
||||
<compilerArgs>
|
||||
<arg>-Adagger.fastInit=enabled</arg>
|
||||
<arg>-Adagger.formatGeneratedSource=enabled</arg>
|
||||
<arg>--enable-preview</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
@@ -347,11 +354,11 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<configuration>
|
||||
<argLine>--enable-preview</argLine>
|
||||
<reportFormat>plain</reportFormat>
|
||||
<consoleOutputReporter>
|
||||
<disable>true</disable>
|
||||
</consoleOutputReporter>
|
||||
<argLine>@{surefire.jacoco.args} -javaagent:${org.mockito:mockito-core:jar}</argLine>
|
||||
<statelessTestsetInfoReporter
|
||||
implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter">
|
||||
</statelessTestsetInfoReporter>
|
||||
@@ -361,6 +368,13 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>jar-paths-to-properties</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>properties</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<!-- sort jars into two buckets (classpath and modulepath). exclude openjfx, which gets jlinked separately -->
|
||||
<execution>
|
||||
<id>copy-mods</id>
|
||||
@@ -433,6 +447,9 @@
|
||||
<goals>
|
||||
<goal>prepare-agent</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<propertyName>surefire.jacoco.args</propertyName>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>report</id>
|
||||
@@ -465,7 +482,7 @@
|
||||
<skipTestScope>true</skipTestScope>
|
||||
<detail>true</detail>
|
||||
<suppressionFile>suppression.xml</suppressionFile>
|
||||
<nvdApiKey>${env.NVD_API_KEY}</nvdApiKey>
|
||||
<nvdApiKeyEnvironmentVariable>NVD_API_KEY</nvdApiKeyEnvironmentVariable>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import ch.qos.logback.classic.spi.Configurator;
|
||||
import org.cryptomator.networking.SSLContextWithPKCS12TrustStore;
|
||||
import org.cryptomator.common.locationpresets.DropboxLinuxLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.DropboxMacLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.DropboxWindowsLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.GoogleDriveLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.GoogleDriveMacLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.GoogleDriveWindowsLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.ICloudMacLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.ICloudWindowsLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.LeitzcloudLocationPresetsProvider;
|
||||
@@ -12,6 +14,9 @@ import org.cryptomator.common.locationpresets.OneDriveLinuxLocationPresetsProvid
|
||||
import org.cryptomator.common.locationpresets.OneDriveMacLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.OneDriveWindowsLocationPresetsProvider;
|
||||
import org.cryptomator.common.locationpresets.PCloudLocationPresetsProvider;
|
||||
import org.cryptomator.networking.SSLContextWithMacKeychain;
|
||||
import org.cryptomator.networking.SSLContextProvider;
|
||||
import org.cryptomator.networking.SSLContextWithWindowsCertStore;
|
||||
import org.cryptomator.integrations.tray.TrayMenuController;
|
||||
import org.cryptomator.logging.LogbackConfiguratorFactory;
|
||||
import org.cryptomator.ui.traymenu.AwtTrayMenuController;
|
||||
@@ -21,7 +26,6 @@ open module org.cryptomator.desktop {
|
||||
|
||||
requires org.cryptomator.cryptolib;
|
||||
requires org.cryptomator.cryptofs;
|
||||
requires org.cryptomator.frontend.dokany;
|
||||
requires org.cryptomator.frontend.fuse;
|
||||
requires org.cryptomator.frontend.webdav;
|
||||
requires org.cryptomator.integrations.api;
|
||||
@@ -32,13 +36,13 @@ open module org.cryptomator.desktop {
|
||||
requires javafx.graphics;
|
||||
requires javafx.controls;
|
||||
requires javafx.fxml;
|
||||
requires jdk.crypto.ec;
|
||||
// 3rd party:
|
||||
requires ch.qos.logback.classic;
|
||||
requires ch.qos.logback.core;
|
||||
requires com.auth0.jwt;
|
||||
requires com.google.common;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires com.fasterxml.jackson.datatype.jsr310;
|
||||
requires com.nimbusds.jose.jwt;
|
||||
requires com.nulabinc.zxcvbn;
|
||||
requires com.tobiasdiez.easybind;
|
||||
@@ -47,16 +51,22 @@ open module org.cryptomator.desktop {
|
||||
requires org.slf4j;
|
||||
requires org.apache.commons.lang3;
|
||||
|
||||
/* TODO: filename-based modules: */
|
||||
requires static javax.inject; /* ugly dagger/guava crap */
|
||||
/* dagger bs */
|
||||
requires jakarta.inject;
|
||||
requires static javax.inject;
|
||||
requires java.compiler;
|
||||
requires com.github.benmanes.caffeine;
|
||||
|
||||
uses org.cryptomator.common.locationpresets.LocationPresetsProvider;
|
||||
uses SSLContextProvider;
|
||||
uses org.cryptomator.event.NotificationHandler;
|
||||
|
||||
provides TrayMenuController with AwtTrayMenuController;
|
||||
provides Configurator with LogbackConfiguratorFactory;
|
||||
provides SSLContextProvider with SSLContextWithWindowsCertStore, SSLContextWithMacKeychain, SSLContextWithPKCS12TrustStore;
|
||||
provides LocationPresetsProvider with //
|
||||
DropboxWindowsLocationPresetsProvider, DropboxMacLocationPresetsProvider, DropboxLinuxLocationPresetsProvider, //
|
||||
GoogleDriveLocationPresetsProvider, //
|
||||
GoogleDriveMacLocationPresetsProvider, GoogleDriveWindowsLocationPresetsProvider, //
|
||||
ICloudWindowsLocationPresetsProvider, ICloudMacLocationPresetsProvider, //
|
||||
LeitzcloudLocationPresetsProvider, //
|
||||
MegaLocationPresetsProvider, //
|
||||
|
||||
22
src/main/java/org/cryptomator/JavaFXUtil.java
Normal file
22
src/main/java/org/cryptomator/JavaFXUtil.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package org.cryptomator;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class JavaFXUtil {
|
||||
|
||||
private JavaFXUtil() {}
|
||||
|
||||
public static boolean startPlatform() throws InterruptedException {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
try {
|
||||
Platform.startup(latch::countDown);
|
||||
} catch (IllegalStateException e) {
|
||||
//already initialized
|
||||
latch.countDown();
|
||||
}
|
||||
return latch.await(5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
160
src/main/java/org/cryptomator/common/EventMap.java
Normal file
160
src/main/java/org/cryptomator/common/EventMap.java
Normal file
@@ -0,0 +1,160 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
|
||||
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
|
||||
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
import org.cryptomator.event.VaultEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import javafx.collections.ObservableMap;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Map containing {@link VaultEvent}s.
|
||||
* The map is keyed by the ciphertext path of the affected resource _and_ the {@link FilesystemEvent}s class in order to group same events
|
||||
* <p>
|
||||
* Use {@link EventMap#put(VaultEvent)} to add an element and {@link EventMap#remove(VaultEvent)} to remove it.
|
||||
* <p>
|
||||
* The map is size restricted to {@value MAX_SIZE} elements. If a _new_ element (i.e. not already present) is added, the least recently added is removed.
|
||||
*/
|
||||
@Singleton
|
||||
public class EventMap implements ObservableMap<EventMap.EventKey, VaultEvent> {
|
||||
|
||||
private static final int MAX_SIZE = 300;
|
||||
|
||||
public record EventKey(Path ciphertextPath, Class<? extends FilesystemEvent> c) {}
|
||||
|
||||
private final ObservableMap<EventMap.EventKey, VaultEvent> delegate;
|
||||
|
||||
@Inject
|
||||
public EventMap() {
|
||||
delegate = FXCollections.observableHashMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(MapChangeListener<? super EventKey, ? super VaultEvent> mapChangeListener) {
|
||||
delegate.addListener(mapChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(MapChangeListener<? super EventKey, ? super VaultEvent> mapChangeListener) {
|
||||
delegate.removeListener(mapChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return delegate.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return delegate.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return delegate.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultEvent get(Object key) {
|
||||
return delegate.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable VaultEvent put(EventKey key, VaultEvent value) {
|
||||
return delegate.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VaultEvent remove(Object key) {
|
||||
return delegate.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(@NotNull Map<? extends EventKey, ? extends VaultEvent> m) {
|
||||
delegate.putAll(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<EventKey> keySet() {
|
||||
return delegate.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Collection<VaultEvent> values() {
|
||||
return delegate.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<Entry<EventKey, VaultEvent>> entrySet() {
|
||||
return delegate.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(InvalidationListener invalidationListener) {
|
||||
delegate.addListener(invalidationListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvalidationListener invalidationListener) {
|
||||
delegate.removeListener(invalidationListener);
|
||||
}
|
||||
|
||||
public synchronized void put(VaultEvent e) {
|
||||
//compute key
|
||||
var key = computeKey(e.actualEvent());
|
||||
//if-else
|
||||
var nullOrEntry = delegate.get(key);
|
||||
if (nullOrEntry == null) {
|
||||
if (size() == MAX_SIZE) {
|
||||
delegate.entrySet().stream() //
|
||||
.min(Comparator.comparing(entry -> entry.getValue().actualEvent().getTimestamp())) //
|
||||
.ifPresent(oldestEntry -> delegate.remove(oldestEntry.getKey()));
|
||||
}
|
||||
delegate.put(key, e);
|
||||
} else {
|
||||
delegate.put(key, nullOrEntry.incrementCount(e.actualEvent()));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized VaultEvent remove(VaultEvent similar) {
|
||||
//compute key
|
||||
var key = computeKey(similar.actualEvent());
|
||||
return this.remove(key);
|
||||
}
|
||||
|
||||
private EventKey computeKey(FilesystemEvent e) {
|
||||
var p = switch (e) {
|
||||
case DecryptionFailedEvent(_, Path ciphertextPath, _) -> ciphertextPath;
|
||||
case ConflictResolvedEvent(_, _, _, _, Path resolvedCiphertext) -> resolvedCiphertext;
|
||||
case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
|
||||
case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
|
||||
case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
|
||||
};
|
||||
return new EventKey(p, e.getClass());
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SubstitutingProperties extends PropertiesDecorator {
|
||||
@@ -58,7 +59,7 @@ public class SubstitutingProperties extends PropertiesDecorator {
|
||||
LoggerFactory.getLogger(SubstitutingProperties.class).warn("Variable {} used for substitution not found in {}. Replaced with empty string.", key, src);
|
||||
return "";
|
||||
} else {
|
||||
return val.replace("\\", "\\\\");
|
||||
return Matcher.quoteReplacement(val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.cryptomator.common.keychain;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import org.cryptomator.common.Passphrase;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessException;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||
|
||||
@@ -14,20 +14,24 @@ import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
@Singleton
|
||||
public class KeychainManager implements KeychainAccessProvider {
|
||||
|
||||
private final ObjectExpression<KeychainAccessProvider> keychain;
|
||||
private final LoadingCache<String, BooleanProperty> passphraseStoredProperties;
|
||||
private final ReentrantReadWriteLock lock;
|
||||
|
||||
@Inject
|
||||
KeychainManager(ObjectExpression<KeychainAccessProvider> selectedKeychain) {
|
||||
this.keychain = selectedKeychain;
|
||||
this.passphraseStoredProperties = CacheBuilder.newBuilder() //
|
||||
.weakValues() //
|
||||
.build(CacheLoader.from(this::createStoredPassphraseProperty));
|
||||
this.passphraseStoredProperties = Caffeine.newBuilder() //
|
||||
.softValues() //
|
||||
.build(this::createStoredPassphraseProperty);
|
||||
keychain.addListener(ignored -> passphraseStoredProperties.invalidateAll());
|
||||
this.lock = new ReentrantReadWriteLock(false);
|
||||
}
|
||||
|
||||
private KeychainAccessProvider getKeychainOrFail() throws KeychainAccessException {
|
||||
@@ -45,27 +49,57 @@ public class KeychainManager implements KeychainAccessProvider {
|
||||
|
||||
@Override
|
||||
public void storePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
|
||||
getKeychainOrFail().storePassphrase(key, displayName, passphrase);
|
||||
storePassphrase(key, displayName, passphrase, true);
|
||||
}
|
||||
|
||||
//TODO: remove ignored parameter once the API is fixed
|
||||
@Override
|
||||
public void storePassphrase(String key, String displayName, CharSequence passphrase, boolean ignored) throws KeychainAccessException {
|
||||
try {
|
||||
lock.writeLock().lock();
|
||||
var kc = getKeychainOrFail();
|
||||
//this is the only keychain actually using the parameter
|
||||
var usesOSAuth = (kc.getClass().getName().equals("org.cryptomator.macos.keychain.TouchIdKeychainAccess"));
|
||||
kc.storePassphrase(key, displayName, passphrase, usesOSAuth);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
setPassphraseStored(key, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] loadPassphrase(String key) throws KeychainAccessException {
|
||||
char[] passphrase = getKeychainOrFail().loadPassphrase(key);
|
||||
char[] passphrase = null;
|
||||
try {
|
||||
lock.readLock().lock();
|
||||
passphrase = getKeychainOrFail().loadPassphrase(key);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
setPassphraseStored(key, passphrase != null);
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePassphrase(String key) throws KeychainAccessException {
|
||||
getKeychainOrFail().deletePassphrase(key);
|
||||
try {
|
||||
lock.writeLock().lock();
|
||||
getKeychainOrFail().deletePassphrase(key);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
setPassphraseStored(key, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changePassphrase(String key, String displayName, CharSequence passphrase) throws KeychainAccessException {
|
||||
if (isPassphraseStored(key)) {
|
||||
getKeychainOrFail().changePassphrase(key, displayName, passphrase);
|
||||
try {
|
||||
lock.writeLock().lock();
|
||||
getKeychainOrFail().changePassphrase(key, displayName, passphrase);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
setPassphraseStored(key, true);
|
||||
}
|
||||
}
|
||||
@@ -102,13 +136,11 @@ public class KeychainManager implements KeychainAccessProvider {
|
||||
}
|
||||
|
||||
private void setPassphraseStored(String key, boolean value) {
|
||||
BooleanProperty property = passphraseStoredProperties.getIfPresent(key);
|
||||
if (property != null) {
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
property.set(value);
|
||||
} else {
|
||||
Platform.runLater(() -> property.set(value));
|
||||
}
|
||||
BooleanProperty property = passphraseStoredProperties.get(key, _ -> new SimpleBooleanProperty(value));
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
property.set(value);
|
||||
} else {
|
||||
Platform.runLater(() -> property.set(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +156,7 @@ public class KeychainManager implements KeychainAccessProvider {
|
||||
* @see #isPassphraseStored(String)
|
||||
*/
|
||||
public ReadOnlyBooleanProperty getPassphraseStoredProperty(String key) {
|
||||
return passphraseStoredProperties.getUnchecked(key);
|
||||
return passphraseStoredProperties.get(key);
|
||||
}
|
||||
|
||||
private BooleanProperty createStoredPassphraseProperty(String key) {
|
||||
@@ -135,4 +167,22 @@ public class KeychainManager implements KeychainAccessProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectExpression<KeychainAccessProvider> getKeychainImplementation() {
|
||||
return this.keychain;
|
||||
}
|
||||
|
||||
public static void migrate(KeychainAccessProvider oldProvider, KeychainAccessProvider newProvider, Map<String, String> idsAndNames) throws KeychainAccessException {
|
||||
if (oldProvider instanceof KeychainManager || newProvider instanceof KeychainManager) {
|
||||
throw new IllegalArgumentException("KeychainManger must not be the source or target of migration");
|
||||
}
|
||||
for (var entry : idsAndNames.entrySet()) {
|
||||
var passphrase = oldProvider.loadPassphrase(entry.getKey());
|
||||
if (passphrase != null) {
|
||||
var wrapper = new Passphrase(passphrase);
|
||||
oldProvider.deletePassphrase(entry.getKey()); //we cannot apply "first-write-then-delete" pattern here, since we can potentially write to the same passphrase store (e.g., touchID and regular keychain)
|
||||
newProvider.storePassphrase(entry.getKey(), entry.getValue(), wrapper);
|
||||
wrapper.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
package org.cryptomator.common.locationpresets;
|
||||
|
||||
import org.cryptomator.integrations.common.CheckAvailability;
|
||||
import org.cryptomator.integrations.common.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
|
||||
|
||||
@OperatingSystem(MAC)
|
||||
@CheckAvailability
|
||||
public final class GoogleDriveMacLocationPresetsProvider implements LocationPresetsProvider {
|
||||
|
||||
private static final Path ROOT_LOCATION = LocationPresetsProvider.resolveLocation("~/Library/CloudStorage/").toAbsolutePath();
|
||||
private static final Predicate<String> PATTERN = Pattern.compile("^GoogleDrive-[^/]+$").asMatchPredicate();
|
||||
|
||||
private static final List<Path> FALLBACK_LOCATIONS = List.of( //
|
||||
LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive"), //
|
||||
LocationPresetsProvider.resolveLocation("~/Google Drive/My Drive"), //
|
||||
LocationPresetsProvider.resolveLocation("~/GoogleDrive"), //
|
||||
LocationPresetsProvider.resolveLocation("~/Google Drive") //
|
||||
);
|
||||
|
||||
@Override
|
||||
public Stream<LocationPreset> getLocations() {
|
||||
List<LocationPreset> cloudStorageDirLocations = getCloudStorageDirLocations();
|
||||
return cloudStorageDirLocations.isEmpty() ? getFallbackLocation().stream() : cloudStorageDirLocations.stream();
|
||||
|
||||
}
|
||||
|
||||
@CheckAvailability
|
||||
public static boolean isPresent() {
|
||||
return isRootLocationPresent() || FALLBACK_LOCATIONS.stream().anyMatch(Files::isDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a root location directory is present that matches the specified pattern.
|
||||
* <p>
|
||||
* This method scans the {@code ROOT_LOCATION} directory for subdirectories and tests each one against a pre-defined pattern ({@code PATTERN}).
|
||||
*
|
||||
* @return {@code true} if a matching root location is present, otherwise {@code false}.
|
||||
*/
|
||||
public static boolean isRootLocationPresent() {
|
||||
try (var dirStream = Files.list(ROOT_LOCATION)) {
|
||||
return dirStream.anyMatch(path -> Files.isDirectory(path) && PATTERN.test(path.getFileName().toString()));
|
||||
} catch (IOException | UncheckedIOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Google Drive preset String.
|
||||
*
|
||||
* @param accountPath The path to the Google Drive account directory (e.g. {@code ~/Library/CloudStorage/GoogleDrive-username})
|
||||
* @return {@code String}. For example: "Google Drive - username"
|
||||
*/
|
||||
private String getDriveLocationString(Path accountPath) {
|
||||
String accountName = accountPath.getFileName().toString().replace("GoogleDrive-", "");
|
||||
return "Google Drive - " + accountName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of cloud storage directory locations based on the {@code ROOT_LOCATION}.
|
||||
* <p>
|
||||
* This method lists all directories in the {@code ROOT_LOCATION}, filters them based on whether their names match
|
||||
* a predefined pattern ({@code PATTERN}), and then extracts presets using {@code getPresetsFromAccountPath(Path)}.
|
||||
* <p>
|
||||
*
|
||||
* @return a list of {@code LocationPreset} objects representing valid cloud storage directory locations.
|
||||
*/
|
||||
private List<LocationPreset> getCloudStorageDirLocations() {
|
||||
try (var dirStream = Files.list(ROOT_LOCATION)) {
|
||||
return dirStream.filter(path -> Files.isDirectory(path) && PATTERN.test(path.getFileName().toString()))
|
||||
.flatMap(this::getPresetsFromAccountPath)
|
||||
.toList();
|
||||
} catch (IOException | UncheckedIOException e) {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a stream of {@code LocationPreset} objects from a given Google Drive account path.
|
||||
* <p>
|
||||
* This method lists all directories within the provided {@code accountPath} and filters them
|
||||
* to identify folders whose names match any of the translations defined in {@code MY_DRIVE_TRANSLATIONS}.
|
||||
*
|
||||
* @param accountPath the root path of the Google Drive account to scan.
|
||||
* @return a stream of {@code LocationPreset} objects representing matching directories.
|
||||
*/
|
||||
private Stream<LocationPreset> getPresetsFromAccountPath(Path accountPath) {
|
||||
try (var driveStream = Files.list(accountPath)) {
|
||||
return driveStream
|
||||
.filter(preset -> MY_DRIVE_TRANSLATIONS
|
||||
.contains(preset.getFileName().toString()))
|
||||
.map(drivePath -> new LocationPreset(
|
||||
getDriveLocationString(accountPath),
|
||||
drivePath
|
||||
)).toList().stream();
|
||||
} catch (IOException e) {
|
||||
return Stream.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list containing a fallback location preset for Google Drive.
|
||||
* <p>
|
||||
* This method iterates through the predefined fallback locations, checks if any of them is a directory,
|
||||
* and creates a {@code LocationPreset} object for the first matching directory found.
|
||||
*
|
||||
* @return a list containing a single fallback location preset if a valid directory is found, otherwise an empty list.
|
||||
* @deprecated This method is intended for legacy support and may be removed in future releases.
|
||||
*/
|
||||
@Deprecated
|
||||
private List<LocationPreset> getFallbackLocation() {
|
||||
return FALLBACK_LOCATIONS.stream()
|
||||
.filter(Files::isDirectory)
|
||||
.map(location -> new LocationPreset("Google Drive", location))
|
||||
.findFirst()
|
||||
.stream()
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of translations for "My Drive" in various languages.
|
||||
* <p>
|
||||
* This constant is used to identify different language-specific labels for "My Drive" in Google Drive.
|
||||
* <p>
|
||||
* The translations were originally extracted from the Chromium project’s Chrome OS translation files.
|
||||
* <p>
|
||||
* Source: `ui/chromeos/translations` directory in the Chromium repository.
|
||||
*/
|
||||
private static final Set<String> MY_DRIVE_TRANSLATIONS = Set.of("My Drive", "የእኔ Drive", "ملفاتي", "মোৰ ড্ৰাইভ", "Diskim", "Мой Дыск", "Моят диск", "আমার ড্রাইভ", "Moj disk", "La meva unitat", "Můj disk", "Mit drev", "Meine Ablage", "Το Drive μου", "Mi unidad", "Minu ketas", "Nire unitatea", "Aking Drive", "Oma Drive", "Mon disque", "Mon Drive", "A miña unidade", "મારી ડ્રાઇવ", "मेरी ड्राइव", "Saját meghajtó", "Իմ դրայվը", "Drive Saya", "Drifið mitt", "I miei file", "האחסון שלי", "マイドライブ", "ჩემი Drive", "Менің Drive дискім", "ដ្រាយរបស់ខ្ញុំ", "ನನ್ನ ಡ್ರೈವ್", "내 드라이브", "Менин Drive'ым", "Mano Diskas", "Mans disks", "Мојот Drive", "എന്റെ ഡ്രൈവ്", "Миний Драйв", "माझा ड्राइव्ह", "मेरो ड्राइभ", "Mijn Drive", "Min disk", "ମୋ ଡ୍ରାଇଭ୍", "Mój dysk", "Meu Drive", "O meu disco", "Contul meu Drive", "Мой диск", "මගේ Drive", "Môj disk", "Disku im", "Мој диск", "Min enhet", "Hifadhi Yangu", "எனது இயக்ககம்", "నా డ్రైవ్", "ไดรฟ์ของฉัน", "Drive'ım", "Мій диск", "میری ڈرائیو", "Drive của tôi", "我的云端硬盘", "我的雲端硬碟", "IDrayivu yami");
|
||||
}
|
||||
@@ -9,13 +9,11 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.cryptomator.integrations.common.OperatingSystem.Value.MAC;
|
||||
import static org.cryptomator.integrations.common.OperatingSystem.Value.WINDOWS;
|
||||
|
||||
@OperatingSystem(WINDOWS)
|
||||
@OperatingSystem(MAC)
|
||||
@CheckAvailability
|
||||
public final class GoogleDriveLocationPresetsProvider implements LocationPresetsProvider {
|
||||
public final class GoogleDriveWindowsLocationPresetsProvider implements LocationPresetsProvider {
|
||||
|
||||
private static final List<Path> LOCATIONS = Arrays.asList( //
|
||||
LocationPresetsProvider.resolveLocation("~/GoogleDrive/My Drive"), //
|
||||
@@ -37,5 +35,4 @@ public final class GoogleDriveLocationPresetsProvider implements LocationPresets
|
||||
.findFirst() //
|
||||
.stream();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import org.cryptomator.integrations.mount.Mount;
|
||||
import org.cryptomator.integrations.mount.MountBuilder;
|
||||
import org.cryptomator.integrations.mount.MountFailedException;
|
||||
import org.cryptomator.integrations.mount.MountService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
@@ -28,6 +30,8 @@ import static org.cryptomator.integrations.mount.MountCapability.UNMOUNT_FORCED;
|
||||
@Singleton
|
||||
public class Mounter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Mounter.class);
|
||||
|
||||
// mount providers (key) can not be used if any of the conflicting mount providers (values) are already in use
|
||||
private static final Map<String, Set<String>> CONFLICTING_MOUNT_SERVICES = Map.of(
|
||||
"org.cryptomator.frontend.fuse.mount.MacFuseMountProvider", Set.of("org.cryptomator.frontend.fuse.mount.FuseTMountProvider"),
|
||||
@@ -127,9 +131,7 @@ public class Mounter {
|
||||
}
|
||||
} else if (canMountToParent && !canMountToDir) {
|
||||
MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
|
||||
cleanup = () -> {
|
||||
MountWithinParentUtil.cleanup(userChosenMountPoint);
|
||||
};
|
||||
cleanup = () -> MountWithinParentUtil.cleanup(userChosenMountPoint);
|
||||
}
|
||||
try {
|
||||
builder.setMountpoint(userChosenMountPoint);
|
||||
@@ -158,7 +160,7 @@ public class Mounter {
|
||||
var mountService = mountProviders.stream().filter(s -> s.getClass().getName().equals(vaultSettings.mountService.getValue())).findFirst().orElse(defaultMountService.getValue());
|
||||
|
||||
if (isConflictingMountService(mountService)) {
|
||||
var msg = STR."\{mountService.getClass()} unavailable due to conflict with either of \{CONFLICTING_MOUNT_SERVICES.get(mountService.getClass().getName())}";
|
||||
var msg = mountService.getClass() + " unavailable due to conflict with either of " + CONFLICTING_MOUNT_SERVICES.get(mountService.getClass().getName());
|
||||
throw new ConflictingMountServiceException(msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,30 +25,33 @@ import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
import java.time.Instant;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Settings {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Settings.class);
|
||||
|
||||
static final boolean DEFAULT_ASKED_FOR_UPDATE_CHECK = false;
|
||||
static final boolean DEFAULT_CHECK_FOR_UPDATES = false;
|
||||
static final boolean DEFAULT_START_HIDDEN = false;
|
||||
static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false;
|
||||
static final boolean DEFAULT_USE_KEYCHAIN = true;
|
||||
static final boolean DEFAULT_USE_QUICKACCESS = true;
|
||||
static final int DEFAULT_PORT = 42427;
|
||||
static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||
static final boolean DEFAULT_DEBUG_MODE = false;
|
||||
static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
||||
@Deprecated // to be changed to "whatever is available" eventually
|
||||
static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
|
||||
static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : //
|
||||
SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : //
|
||||
"org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
|
||||
static final String DEFAULT_QUICKACCESS_SERVICE = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.quickaccess.ExplorerQuickAccessService" : //
|
||||
SystemUtils.IS_OS_LINUX ? "org.cryptomator.linux.quickaccess.NautilusBookmarks" : null;
|
||||
|
||||
static final String DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT.name();
|
||||
static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
|
||||
static final String DEFAULT_LAST_UPDATE_CHECK = "2000-01-01";
|
||||
public static final Instant DEFAULT_TIMESTAMP = Instant.parse("2000-01-01T00:00:00Z");
|
||||
|
||||
public final ObservableList<VaultSettings> directories;
|
||||
public final BooleanProperty askedForUpdateCheck;
|
||||
public final BooleanProperty checkForUpdates;
|
||||
public final BooleanProperty startHidden;
|
||||
public final BooleanProperty autoCloseVaults;
|
||||
public final BooleanProperty useKeychain;
|
||||
@@ -57,17 +60,21 @@ public class Settings {
|
||||
public final BooleanProperty debugMode;
|
||||
public final ObjectProperty<UiTheme> theme;
|
||||
public final StringProperty keychainProvider;
|
||||
public final BooleanProperty useQuickAccess;
|
||||
public final StringProperty quickAccessService;
|
||||
public final ObjectProperty<NodeOrientation> userInterfaceOrientation;
|
||||
public final StringProperty licenseKey;
|
||||
public final BooleanProperty showMinimizeButton;
|
||||
public final BooleanProperty showTrayIcon;
|
||||
public final BooleanProperty compactMode;
|
||||
public final IntegerProperty windowXPosition;
|
||||
public final IntegerProperty windowYPosition;
|
||||
public final IntegerProperty windowWidth;
|
||||
public final IntegerProperty windowHeight;
|
||||
public final StringProperty language;
|
||||
public final StringProperty mountService;
|
||||
public final StringProperty lastUpdateCheck;
|
||||
public final BooleanProperty checkForUpdates;
|
||||
public final ObjectProperty<Instant> lastUpdateCheckReminder;
|
||||
public final ObjectProperty<Instant> lastSuccessfulUpdateCheck;
|
||||
|
||||
private Consumer<Settings> saveCmd;
|
||||
|
||||
@@ -84,11 +91,10 @@ public class Settings {
|
||||
*/
|
||||
Settings(SettingsJson json) {
|
||||
this.directories = FXCollections.observableArrayList(VaultSettings::observables);
|
||||
this.askedForUpdateCheck = new SimpleBooleanProperty(this, "askedForUpdateCheck", json.askedForUpdateCheck);
|
||||
this.checkForUpdates = new SimpleBooleanProperty(this, "checkForUpdates", json.checkForUpdatesEnabled);
|
||||
this.startHidden = new SimpleBooleanProperty(this, "startHidden", json.startHidden);
|
||||
this.autoCloseVaults = new SimpleBooleanProperty(this, "autoCloseVaults", json.autoCloseVaults);
|
||||
this.useKeychain = new SimpleBooleanProperty(this, "useKeychain", json.useKeychain);
|
||||
this.useQuickAccess = new SimpleBooleanProperty(this, "addToQuickAccess", json.useQuickAccess);
|
||||
this.port = new SimpleIntegerProperty(this, "webDavPort", json.port);
|
||||
this.numTrayNotifications = new SimpleIntegerProperty(this, "numTrayNotifications", json.numTrayNotifications);
|
||||
this.debugMode = new SimpleBooleanProperty(this, "debugMode", json.debugMode);
|
||||
@@ -96,26 +102,28 @@ public class Settings {
|
||||
this.keychainProvider = new SimpleStringProperty(this, "keychainProvider", json.keychainProvider);
|
||||
this.userInterfaceOrientation = new SimpleObjectProperty<>(this, "userInterfaceOrientation", parseEnum(json.uiOrientation, NodeOrientation.class, NodeOrientation.LEFT_TO_RIGHT));
|
||||
this.licenseKey = new SimpleStringProperty(this, "licenseKey", json.licenseKey);
|
||||
this.showMinimizeButton = new SimpleBooleanProperty(this, "showMinimizeButton", json.showMinimizeButton);
|
||||
this.showTrayIcon = new SimpleBooleanProperty(this, "showTrayIcon", json.showTrayIcon);
|
||||
this.compactMode = new SimpleBooleanProperty(this, "compactMode", json.compactMode);
|
||||
this.windowXPosition = new SimpleIntegerProperty(this, "windowXPosition", json.windowXPosition);
|
||||
this.windowYPosition = new SimpleIntegerProperty(this, "windowYPosition", json.windowYPosition);
|
||||
this.windowWidth = new SimpleIntegerProperty(this, "windowWidth", json.windowWidth);
|
||||
this.windowHeight = new SimpleIntegerProperty(this, "windowHeight", json.windowHeight);
|
||||
this.language = new SimpleStringProperty(this, "language", json.language);
|
||||
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
|
||||
this.lastUpdateCheck = new SimpleStringProperty(this, "lastUpdateCheck", json.lastUpdateCheck);
|
||||
this.quickAccessService = new SimpleStringProperty(this, "quickAccessService", json.quickAccessService);
|
||||
this.checkForUpdates = new SimpleBooleanProperty(this, "checkForUpdates", json.checkForUpdatesEnabled);
|
||||
this.lastUpdateCheckReminder = new SimpleObjectProperty<>(this, "lastUpdateCheckReminder", json.lastReminderForUpdateCheck);
|
||||
this.lastSuccessfulUpdateCheck = new SimpleObjectProperty<>(this, "lastSuccessfulUpdateCheck", json.lastSuccessfulUpdateCheck);
|
||||
|
||||
this.directories.addAll(json.directories.stream().map(VaultSettings::new).toList());
|
||||
|
||||
migrateLegacySettings(json);
|
||||
|
||||
directories.addListener(this::somethingChanged);
|
||||
askedForUpdateCheck.addListener(this::somethingChanged);
|
||||
checkForUpdates.addListener(this::somethingChanged);
|
||||
startHidden.addListener(this::somethingChanged);
|
||||
autoCloseVaults.addListener(this::somethingChanged);
|
||||
useKeychain.addListener(this::somethingChanged);
|
||||
useQuickAccess.addListener(this::somethingChanged);
|
||||
port.addListener(this::somethingChanged);
|
||||
numTrayNotifications.addListener(this::somethingChanged);
|
||||
debugMode.addListener(this::somethingChanged);
|
||||
@@ -123,15 +131,18 @@ public class Settings {
|
||||
keychainProvider.addListener(this::somethingChanged);
|
||||
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||
licenseKey.addListener(this::somethingChanged);
|
||||
showMinimizeButton.addListener(this::somethingChanged);
|
||||
showTrayIcon.addListener(this::somethingChanged);
|
||||
compactMode.addListener(this::somethingChanged);
|
||||
windowXPosition.addListener(this::somethingChanged);
|
||||
windowYPosition.addListener(this::somethingChanged);
|
||||
windowWidth.addListener(this::somethingChanged);
|
||||
windowHeight.addListener(this::somethingChanged);
|
||||
language.addListener(this::somethingChanged);
|
||||
mountService.addListener(this::somethingChanged);
|
||||
lastUpdateCheck.addListener(this::somethingChanged);
|
||||
quickAccessService.addListener(this::somethingChanged);
|
||||
checkForUpdates.addListener(this::somethingChanged);
|
||||
lastUpdateCheckReminder.addListener(this::somethingChanged);
|
||||
lastSuccessfulUpdateCheck.addListener(this::somethingChanged);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@@ -165,11 +176,10 @@ public class Settings {
|
||||
SettingsJson serialized() {
|
||||
var json = new SettingsJson();
|
||||
json.directories = directories.stream().map(VaultSettings::serialized).toList();
|
||||
json.askedForUpdateCheck = askedForUpdateCheck.get();
|
||||
json.checkForUpdatesEnabled = checkForUpdates.get();
|
||||
json.startHidden = startHidden.get();
|
||||
json.autoCloseVaults = autoCloseVaults.get();
|
||||
json.useKeychain = useKeychain.get();
|
||||
json.useQuickAccess = useQuickAccess.get();
|
||||
json.port = port.get();
|
||||
json.numTrayNotifications = numTrayNotifications.get();
|
||||
json.debugMode = debugMode.get();
|
||||
@@ -177,15 +187,18 @@ public class Settings {
|
||||
json.keychainProvider = keychainProvider.get();
|
||||
json.uiOrientation = userInterfaceOrientation.get().name();
|
||||
json.licenseKey = licenseKey.get();
|
||||
json.showMinimizeButton = showMinimizeButton.get();
|
||||
json.showTrayIcon = showTrayIcon.get();
|
||||
json.compactMode = compactMode.get();
|
||||
json.windowXPosition = windowXPosition.get();
|
||||
json.windowYPosition = windowYPosition.get();
|
||||
json.windowWidth = windowWidth.get();
|
||||
json.windowHeight = windowHeight.get();
|
||||
json.language = language.get();
|
||||
json.mountService = mountService.get();
|
||||
json.lastUpdateCheck = lastUpdateCheck.get();
|
||||
json.quickAccessService = quickAccessService.get();
|
||||
json.checkForUpdatesEnabled = checkForUpdates.get();
|
||||
json.lastReminderForUpdateCheck = lastUpdateCheckReminder.get();
|
||||
json.lastSuccessfulUpdateCheck = lastSuccessfulUpdateCheck.get();
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@@ -16,15 +18,9 @@ class SettingsJson {
|
||||
@JsonProperty("writtenByVersion")
|
||||
String writtenByVersion;
|
||||
|
||||
@JsonProperty("askedForUpdateCheck")
|
||||
boolean askedForUpdateCheck = Settings.DEFAULT_ASKED_FOR_UPDATE_CHECK;
|
||||
|
||||
@JsonProperty("autoCloseVaults")
|
||||
boolean autoCloseVaults = Settings.DEFAULT_AUTO_CLOSE_VAULTS;
|
||||
|
||||
@JsonProperty("checkForUpdatesEnabled")
|
||||
boolean checkForUpdatesEnabled = Settings.DEFAULT_CHECK_FOR_UPDATES;
|
||||
|
||||
@JsonProperty("debugMode")
|
||||
boolean debugMode = Settings.DEFAULT_DEBUG_MODE;
|
||||
|
||||
@@ -49,12 +45,12 @@ class SettingsJson {
|
||||
@JsonProperty("port")
|
||||
int port = Settings.DEFAULT_PORT;
|
||||
|
||||
@JsonProperty("showMinimizeButton")
|
||||
boolean showMinimizeButton = Settings.DEFAULT_SHOW_MINIMIZE_BUTTON;
|
||||
|
||||
@JsonProperty("showTrayIcon")
|
||||
boolean showTrayIcon;
|
||||
|
||||
@JsonProperty("compactMode")
|
||||
boolean compactMode;
|
||||
|
||||
@JsonProperty("startHidden")
|
||||
boolean startHidden = Settings.DEFAULT_START_HIDDEN;
|
||||
|
||||
@@ -80,7 +76,20 @@ class SettingsJson {
|
||||
@JsonProperty(value = "preferredVolumeImpl", access = JsonProperty.Access.WRITE_ONLY) // WRITE_ONLY means value is "written" into the java object during deserialization. Upvote this: https://github.com/FasterXML/jackson-annotations/issues/233
|
||||
String preferredVolumeImpl;
|
||||
|
||||
@JsonProperty("lastUpdateCheck")
|
||||
String lastUpdateCheck = Settings.DEFAULT_LAST_UPDATE_CHECK;
|
||||
@JsonProperty("checkForUpdatesEnabled")
|
||||
boolean checkForUpdatesEnabled = Settings.DEFAULT_CHECK_FOR_UPDATES;
|
||||
|
||||
@JsonProperty("lastReminderForUpdateCheck")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC")
|
||||
Instant lastReminderForUpdateCheck = Settings.DEFAULT_TIMESTAMP;
|
||||
|
||||
@JsonProperty("lastSuccessfulUpdateCheck")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC")
|
||||
Instant lastSuccessfulUpdateCheck = Settings.DEFAULT_TIMESTAMP;
|
||||
|
||||
@JsonProperty("useQuickAccess")
|
||||
boolean useQuickAccess = Settings.DEFAULT_USE_QUICKACCESS;
|
||||
|
||||
@JsonProperty("quickAccessService")
|
||||
String quickAccessService = Settings.DEFAULT_QUICKACCESS_SERVICE;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ package org.cryptomator.common.settings;
|
||||
|
||||
import com.fasterxml.jackson.core.JacksonException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.google.common.base.Suppliers;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.slf4j.Logger;
|
||||
@@ -36,7 +37,7 @@ import java.util.stream.Stream;
|
||||
@Singleton
|
||||
public class SettingsProvider implements Supplier<Settings> {
|
||||
|
||||
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true);
|
||||
private static final ObjectMapper JSON = new ObjectMapper().setDefaultLeniency(true).registerModule(new JavaTimeModule());
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
|
||||
private static final long SAVE_DELAY_MS = 1000;
|
||||
|
||||
@@ -57,7 +58,10 @@ public class SettingsProvider implements Supplier<Settings> {
|
||||
}
|
||||
|
||||
private Settings load() {
|
||||
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElseGet(() -> Settings.create(env));
|
||||
Settings settings = env.getSettingsPath() //
|
||||
.flatMap(this::tryLoad) //
|
||||
.findFirst() //
|
||||
.orElseGet(() -> Settings.create(env));
|
||||
settings.setSaveCmd(this::scheduleSave);
|
||||
return settings;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ public class VaultSettings {
|
||||
public final StringExpression mountName;
|
||||
public final StringProperty mountService;
|
||||
public final IntegerProperty port;
|
||||
public final StringProperty lastKnownKeyLoader;
|
||||
|
||||
VaultSettings(VaultSettingsJson json) {
|
||||
this.id = json.id;
|
||||
@@ -74,6 +75,7 @@ public class VaultSettings {
|
||||
this.mountPoint = new SimpleObjectProperty<>(this, "mountPoint", json.mountPoint == null ? null : Path.of(json.mountPoint));
|
||||
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
|
||||
this.port = new SimpleIntegerProperty(this, "port", json.port);
|
||||
this.lastKnownKeyLoader = new SimpleStringProperty(this, "lastKnownKeyLoader", json.lastKnownKeyLoader);
|
||||
// mount name is no longer an explicit setting, see https://github.com/cryptomator/cryptomator/pull/1318
|
||||
this.mountName = StringExpression.stringExpression(Bindings.createStringBinding(() -> {
|
||||
final String name;
|
||||
@@ -99,7 +101,7 @@ public class VaultSettings {
|
||||
}
|
||||
|
||||
Observable[] observables() {
|
||||
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService};
|
||||
return new Observable[]{actionAfterUnlock, autoLockIdleSeconds, autoLockWhenIdle, displayName, maxCleartextFilenameLength, mountFlags, mountPoint, path, revealAfterMount, unlockAfterStartup, usesReadOnlyMode, port, mountService, lastKnownKeyLoader};
|
||||
}
|
||||
|
||||
public static VaultSettings withRandomId() {
|
||||
@@ -130,6 +132,7 @@ public class VaultSettings {
|
||||
json.mountPoint = mountPoint.map(Path::toString).getValue();
|
||||
json.mountService = mountService.get();
|
||||
json.port = port.get();
|
||||
json.lastKnownKeyLoader = lastKnownKeyLoader.get();
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ class VaultSettingsJson {
|
||||
@JsonProperty("mountService")
|
||||
String mountService;
|
||||
|
||||
@JsonProperty("lastKnownKeyLoader")
|
||||
String lastKnownKeyLoader;
|
||||
|
||||
@JsonProperty("port")
|
||||
int port = VaultSettings.DEFAULT_PORT;
|
||||
|
||||
|
||||
@@ -10,24 +10,32 @@ package org.cryptomator.common.vaults;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
import org.cryptomator.common.mount.Mounter;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoader;
|
||||
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
import org.cryptomator.event.VaultEvent;
|
||||
import org.cryptomator.integrations.mount.MountFailedException;
|
||||
import org.cryptomator.integrations.mount.Mountpoint;
|
||||
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||
import org.cryptomator.integrations.quickaccess.QuickAccessService;
|
||||
import org.cryptomator.integrations.quickaccess.QuickAccessServiceException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
@@ -40,6 +48,7 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.ReadOnlyFileSystemException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@@ -54,6 +63,7 @@ public class Vault {
|
||||
|
||||
private final VaultSettings vaultSettings;
|
||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
||||
private final AtomicReference<QuickAccessService.QuickAccessEntry> quickAccessEntry;
|
||||
private final VaultState state;
|
||||
private final ObjectProperty<Exception> lastKnownException;
|
||||
private final VaultConfigCache configCache;
|
||||
@@ -67,6 +77,8 @@ public class Vault {
|
||||
private final BooleanBinding unknownError;
|
||||
private final ObjectBinding<Mountpoint> mountPoint;
|
||||
private final Mounter mounter;
|
||||
private final Settings settings;
|
||||
private final FileSystemEventAggregator fileSystemEventAggregator;
|
||||
private final BooleanProperty showingStats;
|
||||
|
||||
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
|
||||
@@ -78,7 +90,8 @@ public class Vault {
|
||||
VaultState state, //
|
||||
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
|
||||
VaultStats stats, //
|
||||
Mounter mounter) {
|
||||
Mounter mounter, Settings settings, //
|
||||
FileSystemEventAggregator fileSystemEventAggregator) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.configCache = configCache;
|
||||
this.cryptoFileSystem = cryptoFileSystem;
|
||||
@@ -94,7 +107,10 @@ public class Vault {
|
||||
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
||||
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
||||
this.mounter = mounter;
|
||||
this.settings = settings;
|
||||
this.fileSystemEventAggregator = fileSystemEventAggregator;
|
||||
this.showingStats = new SimpleBooleanProperty(false);
|
||||
this.quickAccessEntry = new AtomicReference<>(null);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
@@ -103,15 +119,22 @@ public class Vault {
|
||||
|
||||
private CryptoFileSystem createCryptoFileSystem(MasterkeyLoader keyLoader) throws IOException, MasterkeyLoadingFailedException {
|
||||
Set<FileSystemFlags> flags = EnumSet.noneOf(FileSystemFlags.class);
|
||||
if (vaultSettings.usesReadOnlyMode.get()) {
|
||||
var createReadOnly = vaultSettings.usesReadOnlyMode.get();
|
||||
try {
|
||||
FileSystemCapabilityChecker.assertWriteAccess(getPath());
|
||||
} catch (FileSystemCapabilityChecker.MissingCapabilityException e) {
|
||||
if (!createReadOnly) {
|
||||
throw new ReadOnlyFileSystemException();
|
||||
}
|
||||
}
|
||||
if (createReadOnly) {
|
||||
flags.add(FileSystemFlags.READONLY);
|
||||
} else if (vaultSettings.maxCleartextFilenameLength.get() == -1) {
|
||||
LOG.debug("Determining cleartext filename length limitations...");
|
||||
var checker = new FileSystemCapabilityChecker();
|
||||
int shorteningThreshold = configCache.get().allegedShorteningThreshold();
|
||||
int ciphertextLimit = checker.determineSupportedCiphertextFileNameLength(getPath());
|
||||
int ciphertextLimit = FileSystemCapabilityChecker.determineSupportedCiphertextFileNameLength(getPath());
|
||||
if (ciphertextLimit < shorteningThreshold) {
|
||||
int cleartextLimit = checker.determineSupportedCleartextFileNameLength(getPath());
|
||||
int cleartextLimit = FileSystemCapabilityChecker.determineSupportedCleartextFileNameLength(getPath());
|
||||
vaultSettings.maxCleartextFilenameLength.set(cleartextLimit);
|
||||
} else {
|
||||
vaultSettings.maxCleartextFilenameLength.setValue(UNLIMITED_FILENAME_LENGTH);
|
||||
@@ -127,6 +150,7 @@ public class Vault {
|
||||
.withFlags(flags) //
|
||||
.withMaxCleartextNameLength(vaultSettings.maxCleartextFilenameLength.get()) //
|
||||
.withVaultConfigFilename(Constants.VAULTCONFIG_FILENAME) //
|
||||
.withFilesystemEventConsumer(this::consumeVaultEvent) //
|
||||
.build();
|
||||
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
|
||||
}
|
||||
@@ -154,6 +178,9 @@ public class Vault {
|
||||
var rootPath = fs.getRootDirectories().iterator().next();
|
||||
var mountHandle = mounter.mount(vaultSettings, rootPath);
|
||||
success = this.mountHandle.compareAndSet(null, mountHandle);
|
||||
if (settings.useQuickAccess.getValue()) {
|
||||
addToQuickAccess();
|
||||
}
|
||||
} finally {
|
||||
if (!success) {
|
||||
destroyCryptoFileSystem();
|
||||
@@ -178,6 +205,7 @@ public class Vault {
|
||||
mountHandle.mountObj().close();
|
||||
mountHandle.specialCleanup().run();
|
||||
} finally {
|
||||
removeFromQuickAccess();
|
||||
destroyCryptoFileSystem();
|
||||
}
|
||||
|
||||
@@ -185,6 +213,57 @@ public class Vault {
|
||||
LOG.info("Locked vault '{}'", getDisplayName());
|
||||
}
|
||||
|
||||
private synchronized void addToQuickAccess() {
|
||||
if (quickAccessEntry.get() != null) {
|
||||
//we don't throw an exception since we don't wanna block unlocking
|
||||
LOG.warn("Vault already added to quick access area. Will be removed on next lock operation.");
|
||||
return;
|
||||
}
|
||||
|
||||
QuickAccessService.get() //
|
||||
.filter(s -> s.getClass().getName().equals(settings.quickAccessService.getValue())) //
|
||||
.findFirst() //
|
||||
.ifPresentOrElse( //
|
||||
this::addToQuickAccessInternal, //
|
||||
() -> LOG.warn("Unable to add Vault to quick access area: Desired implementation not available.") //
|
||||
);
|
||||
}
|
||||
|
||||
private void addToQuickAccessInternal(@NotNull QuickAccessService s) {
|
||||
if (getMountPoint() instanceof Mountpoint.WithPath mp) {
|
||||
try {
|
||||
var entry = s.add(mp.path(), getDisplayName());
|
||||
quickAccessEntry.set(entry);
|
||||
} catch (QuickAccessServiceException e) {
|
||||
LOG.error("Adding vault to quick access area failed", e);
|
||||
}
|
||||
} else {
|
||||
LOG.warn("Unable to add vault to quick access area: Vault is not mounted to local system path.");
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void removeFromQuickAccess() {
|
||||
if (quickAccessEntry.get() == null) {
|
||||
LOG.debug("Removing vault from quick access area: Entry not found, nothing to do.");
|
||||
return;
|
||||
}
|
||||
removeFromQuickAccessInternal();
|
||||
}
|
||||
|
||||
private void removeFromQuickAccessInternal() {
|
||||
try {
|
||||
quickAccessEntry.get().remove();
|
||||
quickAccessEntry.set(null);
|
||||
} catch (QuickAccessServiceException e) {
|
||||
LOG.error("Removing vault from quick access area failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void consumeVaultEvent(FilesystemEvent e) {
|
||||
fileSystemEventAggregator.put(this, e);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
// Observable Properties
|
||||
// *******************************************************************************
|
||||
@@ -346,6 +425,17 @@ public class Vault {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cleartext name from a given path to an encrypted vault file
|
||||
*/
|
||||
public String getCleartextName(Path ciphertextPath) throws IOException {
|
||||
if (!state.getValue().equals(VaultState.Value.UNLOCKED)) {
|
||||
throw new IllegalStateException("Vault is not unlocked");
|
||||
}
|
||||
var fs = cryptoFileSystem.get();
|
||||
return fs.getCleartextName(ciphertextPath);
|
||||
}
|
||||
|
||||
public VaultConfigCache getVaultConfigCache() {
|
||||
return configCache;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptofs.DirStructure;
|
||||
import org.cryptomator.cryptofs.migration.Migrators;
|
||||
import org.cryptomator.integrations.mount.MountService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -24,6 +26,8 @@ import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@@ -38,14 +42,21 @@ public class VaultListManager {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultListManager.class);
|
||||
|
||||
private final AutoLocker autoLocker;
|
||||
private final List<MountService> mountServices;
|
||||
private final VaultComponent.Factory vaultComponentFactory;
|
||||
private final ObservableList<Vault> vaultList;
|
||||
private final String defaultVaultName;
|
||||
|
||||
@Inject
|
||||
public VaultListManager(ObservableList<Vault> vaultList, AutoLocker autoLocker, VaultComponent.Factory vaultComponentFactory, ResourceBundle resourceBundle, Settings settings) {
|
||||
public VaultListManager(ObservableList<Vault> vaultList, //
|
||||
AutoLocker autoLocker, //
|
||||
List<MountService> mountServices, //
|
||||
VaultComponent.Factory vaultComponentFactory, //
|
||||
ResourceBundle resourceBundle, //
|
||||
Settings settings) {
|
||||
this.vaultList = vaultList;
|
||||
this.autoLocker = autoLocker;
|
||||
this.mountServices = mountServices;
|
||||
this.vaultComponentFactory = vaultComponentFactory;
|
||||
this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName");
|
||||
|
||||
@@ -76,6 +87,15 @@ public class VaultListManager {
|
||||
} else {
|
||||
vaultSettings.displayName.set(defaultVaultName);
|
||||
}
|
||||
|
||||
//due to https://github.com/cryptomator/cryptomator/issues/2880#issuecomment-1680313498
|
||||
var nameOfWinfspLocalMounter = "org.cryptomator.frontend.fuse.mount.WinFspMountProvider";
|
||||
if (SystemUtils.IS_OS_WINDOWS //
|
||||
&& vaultSettings.path.get().toString().contains("Dropbox") //
|
||||
&& mountServices.stream().anyMatch(s -> s.getClass().getName().equals(nameOfWinfspLocalMounter))) {
|
||||
vaultSettings.mountService.setValue(nameOfWinfspLocalMounter);
|
||||
}
|
||||
|
||||
return vaultSettings;
|
||||
}
|
||||
|
||||
@@ -95,6 +115,10 @@ public class VaultListManager {
|
||||
private Vault create(VaultSettings vaultSettings) {
|
||||
var wrapper = new VaultConfigCache(vaultSettings);
|
||||
try {
|
||||
if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
|
||||
var keyIdScheme = wrapper.get().getKeyId().getScheme();
|
||||
vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
|
||||
}
|
||||
var vaultState = determineVaultState(vaultSettings.path.get());
|
||||
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
|
||||
wrapper.reloadConfig();
|
||||
|
||||
14
src/main/java/org/cryptomator/event/Answer.java
Normal file
14
src/main/java/org/cryptomator/event/Answer.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
public sealed interface Answer permits Answer.DoNothing, Answer.DoSomething {
|
||||
|
||||
|
||||
record DoNothing() implements Answer {}
|
||||
|
||||
record DoSomething(Runnable action) implements Answer {
|
||||
|
||||
void run() {
|
||||
action.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/main/java/org/cryptomator/event/FSEventBucket.java
Normal file
8
src/main/java/org/cryptomator/event/FSEventBucket.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public record FSEventBucket(Vault vault, Path idPath, Class<? extends FilesystemEvent> c) {}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
public record FSEventBucketContent(FilesystemEvent mostRecentEvent, int count) {}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
|
||||
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
|
||||
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Aggregator for {@link FilesystemEvent}s.
|
||||
* <p>
|
||||
* The aggregator groups filesystem events by the vault where the event occurred, an identifying path (clear- or ciphertext) and the event class (aka type).
|
||||
* A group is called an {@link FSEventBucket}, its {@link FSEventBucketContent} is the most recent event object and a count of how often the event already occurred.
|
||||
*/
|
||||
@Singleton
|
||||
public class FileSystemEventAggregator {
|
||||
|
||||
private final ConcurrentHashMap<FSEventBucket, FSEventBucketContent> map;
|
||||
private final AtomicBoolean hasUpdates;
|
||||
|
||||
@Inject
|
||||
public FileSystemEventAggregator() {
|
||||
this.map = new ConcurrentHashMap<>();
|
||||
this.hasUpdates = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given event to the map. If a bucket for this event already exists, only the count is updated and the event set as the most recent one.
|
||||
*
|
||||
* @param v Vault where the event occurred
|
||||
* @param e Actual {@link FilesystemEvent}
|
||||
*/
|
||||
public void put(Vault v, FilesystemEvent e) {
|
||||
var key = computeKey(v, e);
|
||||
map.compute(key, (k, val) -> {
|
||||
if (val == null) {
|
||||
return new FSEventBucketContent(e, 1);
|
||||
} else {
|
||||
return new FSEventBucketContent(e, val.count() + 1);
|
||||
}
|
||||
});
|
||||
hasUpdates.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event bucket from the map.
|
||||
*/
|
||||
public FSEventBucketContent remove(FSEventBucket key) {
|
||||
var content = map.remove(key);
|
||||
hasUpdates.set(true);
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the event map.
|
||||
*/
|
||||
public void clear() {
|
||||
map.clear();
|
||||
hasUpdates.set(true);
|
||||
}
|
||||
|
||||
|
||||
public boolean hasMaybeUpdates() {
|
||||
return hasUpdates.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the map entries into a collection.
|
||||
* <p>
|
||||
* The collection is first cleared, then all map entries are added in one bulk operation. Cleans the hasUpdates status.
|
||||
*
|
||||
* @param target collection which is first cleared and then the EntrySet copied to.
|
||||
*/
|
||||
public void cloneTo(Collection<Map.Entry<FSEventBucket, FSEventBucketContent>> target) {
|
||||
hasUpdates.set(false);
|
||||
target.clear();
|
||||
target.addAll(map.entrySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to compute the identifying key for a given filesystem event
|
||||
*
|
||||
* @param v Vault where the event occurred
|
||||
* @param event Actual {@link FilesystemEvent}
|
||||
* @return a {@link FSEventBucket} used in the map and lru cache
|
||||
*/
|
||||
private static FSEventBucket computeKey(Vault v, FilesystemEvent event) {
|
||||
var p = switch (event) {
|
||||
case DecryptionFailedEvent(_, Path ciphertextPath, _) -> ciphertextPath;
|
||||
case ConflictResolvedEvent(_, _, _, _, Path resolvedCiphertext) -> resolvedCiphertext;
|
||||
case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
|
||||
case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
|
||||
case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
|
||||
};
|
||||
return new FSEventBucket(v, p, event.getClass());
|
||||
}
|
||||
}
|
||||
15
src/main/java/org/cryptomator/event/NotificationHandler.java
Normal file
15
src/main/java/org/cryptomator/event/NotificationHandler.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.integrations.common.IntegrationsLoader;
|
||||
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface NotificationHandler {
|
||||
|
||||
Answer handle(VaultEvent e);
|
||||
|
||||
static Stream<NotificationHandler> loadAll() {
|
||||
return IntegrationsLoader.loadAll(ServiceLoader.load(NotificationHandler.class), NotificationHandler.class);
|
||||
}
|
||||
}
|
||||
27
src/main/java/org/cryptomator/event/VaultEvent.java
Normal file
27
src/main/java/org/cryptomator/event/VaultEvent.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.cryptomator.event;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.event.FilesystemEvent;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record VaultEvent(Vault v, FilesystemEvent actualEvent, int count) implements Comparable<VaultEvent> {
|
||||
|
||||
public VaultEvent(Vault v, FilesystemEvent actualEvent) {
|
||||
this(v, actualEvent, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(VaultEvent other) {
|
||||
var timeResult = actualEvent.getTimestamp().compareTo(other.actualEvent().getTimestamp());
|
||||
if(timeResult != 0) {
|
||||
return timeResult;
|
||||
} else {
|
||||
return this.equals(other) ? 0 : this.actualEvent.getClass().getName().compareTo(other.actualEvent.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public VaultEvent incrementCount(FilesystemEvent update) {
|
||||
return new VaultEvent(v, update, count+1);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,9 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import dagger.Lazy;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.SubstitutingProperties;
|
||||
import org.cryptomator.common.ShutdownHook;
|
||||
import org.cryptomator.common.SubstitutingProperties;
|
||||
import org.cryptomator.networking.SSLContextProvider;
|
||||
import org.cryptomator.ipc.IpcCommunicator;
|
||||
import org.cryptomator.logging.DebugMode;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationComponent;
|
||||
@@ -19,8 +20,10 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -48,14 +51,16 @@ public class Cryptomator {
|
||||
private final Environment env;
|
||||
private final Lazy<IpcMessageHandler> ipcMessageHandler;
|
||||
private final ShutdownHook shutdownHook;
|
||||
private final SecureRandom csprng;
|
||||
|
||||
@Inject
|
||||
Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, ShutdownHook shutdownHook) {
|
||||
Cryptomator(DebugMode debugMode, SupportedLanguages supportedLanguages, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, ShutdownHook shutdownHook, SecureRandom csprng) {
|
||||
this.debugMode = debugMode;
|
||||
this.supportedLanguages = supportedLanguages;
|
||||
this.env = env;
|
||||
this.ipcMessageHandler = ipcMessageHandler;
|
||||
this.shutdownHook = shutdownHook;
|
||||
this.csprng = csprng;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
@@ -89,7 +94,7 @@ public class Cryptomator {
|
||||
LOG.info("Starting Cryptomator {} on {} {} ({})", env.getAppVersion(), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
|
||||
debugMode.initialize();
|
||||
supportedLanguages.applyPreferred();
|
||||
|
||||
changeDefaultSSLContext();
|
||||
/*
|
||||
* Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args.
|
||||
* If no external process could be reached, the args will be handled by the loopback IPC endpoint.
|
||||
@@ -115,6 +120,17 @@ public class Cryptomator {
|
||||
}
|
||||
}
|
||||
|
||||
private void changeDefaultSSLContext() {
|
||||
SSLContextProvider.loadAll().findFirst().ifPresent(p -> {
|
||||
try {
|
||||
var context = p.getContext(csprng);
|
||||
SSLContext.setDefault(context);
|
||||
} catch (SSLContextProvider.SSLContextBuildException e) {
|
||||
LOG.warn("Failed to change default SSL context with provider {}", p.getClass().getName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the JavaFX application, blocking the main thread until shuts down.
|
||||
*
|
||||
|
||||
@@ -20,7 +20,7 @@ public class SupportedLanguages {
|
||||
// "en" is not part of this list - it is always inserted at the top.
|
||||
public static final List<String> LANGUAGE_TAGS = List.of("ar", "be", "bn", "bs", "ca", "cs", "da", "de", "el", "es", "fr", "gl", "he", //
|
||||
"hi", "hr", "hu", "id", "it", "ja", "ko", "lv", "nb", "nl", "nn", "pa", "pl", "pt", "pt-BR", "ro", "ru", "sk", "sr", "sr-Latn", "sv", "sw", //
|
||||
"ta", "th", "tr", "uk", "vi", "zh", "zh-HK", "zh-TW");
|
||||
"ta", "th", "tr", "ug", "uk", "vi", "zh", "zh-HK", "zh-TW");
|
||||
public static final String ENGLISH = "en";
|
||||
|
||||
private final List<String> sortedLanguageTags;
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
package org.cryptomator.networking;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.Key;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.KeyStoreSpi;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class CombinedKeyStoreSpi extends KeyStoreSpi {
|
||||
|
||||
private final KeyStore primary;
|
||||
private final KeyStore fallback;
|
||||
|
||||
public static CombinedKeyStoreSpi create(KeyStore primary, KeyStore fallback) {
|
||||
checkIfLoaded(primary);
|
||||
checkIfLoaded(fallback);
|
||||
return new CombinedKeyStoreSpi(primary, fallback);
|
||||
}
|
||||
|
||||
private static void checkIfLoaded(KeyStore s) {
|
||||
try {
|
||||
s.aliases();
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalArgumentException("Keystore %s is not loaded.".formatted(s.getType()));
|
||||
}
|
||||
}
|
||||
|
||||
private CombinedKeyStoreSpi(KeyStore primary, KeyStore fallback) {
|
||||
this.primary = primary;
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
try {
|
||||
Key key = primary.getKey(alias, password);
|
||||
if (key == null) {
|
||||
key = fallback.getKey(alias, password);
|
||||
}
|
||||
return key;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] engineGetCertificateChain(String alias) {
|
||||
try {
|
||||
Certificate[] chain = primary.getCertificateChain(alias);
|
||||
if (chain == null) {
|
||||
chain = fallback.getCertificateChain(alias);
|
||||
}
|
||||
return chain;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate engineGetCertificate(String alias) {
|
||||
try {
|
||||
Certificate cert = primary.getCertificate(alias);
|
||||
if (cert == null) {
|
||||
cert = fallback.getCertificate(alias);
|
||||
}
|
||||
return cert;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date engineGetCreationDate(String alias) {
|
||||
try {
|
||||
Date date = primary.getCreationDate(alias);
|
||||
if (date == null) {
|
||||
date = fallback.getCreationDate(alias);
|
||||
}
|
||||
return date;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineDeleteEntry(String alias) throws KeyStoreException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> engineAliases() {
|
||||
var aliases = new LinkedHashSet<String>();
|
||||
try {
|
||||
primary.aliases().asIterator().forEachRemaining(aliases::add);
|
||||
fallback.aliases().asIterator().forEachRemaining(aliases::add);
|
||||
return Collections.enumeration(aliases);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineContainsAlias(String alias) {
|
||||
try {
|
||||
return primary.containsAlias(alias) || fallback.containsAlias(alias);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int engineSize() {
|
||||
var aliases = engineAliases();
|
||||
var i = new AtomicInteger(0);
|
||||
aliases.asIterator().forEachRemaining(_ -> i.incrementAndGet());
|
||||
return i.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineIsKeyEntry(String alias) {
|
||||
try {
|
||||
return primary.isKeyEntry(alias) || fallback.isKeyEntry(alias);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean engineIsCertificateEntry(String alias) {
|
||||
try {
|
||||
return primary.isCertificateEntry(alias) || fallback.isCertificateEntry(alias);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String engineGetCertificateAlias(Certificate cert) {
|
||||
try {
|
||||
String alias = primary.getCertificateAlias(cert);
|
||||
if (alias == null) {
|
||||
alias = fallback.getCertificateAlias(cert);
|
||||
}
|
||||
return alias;
|
||||
} catch (KeyStoreException e) {
|
||||
throw new IllegalStateException("At least one keystore of [%s, %s] is not initialized.".formatted(primary.getType(), fallback.getType()), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineStore(OutputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException {
|
||||
throw new UnsupportedOperationException("Read-only KeyStore");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void engineLoad(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException {
|
||||
// Nothing to do; the real keystores are already loaded.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.cryptomator.networking;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
abstract class SSLContextDifferentTrustStoreBase implements SSLContextProvider {
|
||||
|
||||
abstract KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException;
|
||||
|
||||
@Override
|
||||
public SSLContext getContext(SecureRandom csprng) throws SSLContextBuildException {
|
||||
try {
|
||||
KeyStore truststore = getTruststore();
|
||||
truststore.load(null, null);
|
||||
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(truststore);
|
||||
|
||||
SSLContext context = SSLContext.getInstance("TLS");
|
||||
context.init(null, tmf.getTrustManagers(), csprng);
|
||||
return context;
|
||||
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | KeyManagementException | IOException e) {
|
||||
throw new SSLContextBuildException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.cryptomator.networking;
|
||||
|
||||
import org.cryptomator.integrations.common.IntegrationsLoader;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface SSLContextProvider {
|
||||
|
||||
SSLContext getContext(SecureRandom csprng) throws SSLContextBuildException;
|
||||
|
||||
class SSLContextBuildException extends Exception {
|
||||
|
||||
SSLContextBuildException(Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
}
|
||||
|
||||
static Stream<SSLContextProvider> loadAll() {
|
||||
return IntegrationsLoader.loadAll(ServiceLoader.load(SSLContextProvider.class), SSLContextProvider.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.cryptomator.networking;
|
||||
|
||||
import org.cryptomator.integrations.common.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
/**
|
||||
* SSLContextProvider for macOS using the macOS Keychain as truststore
|
||||
*/
|
||||
@OperatingSystem(OperatingSystem.Value.MAC)
|
||||
public class SSLContextWithMacKeychain extends SSLContextDifferentTrustStoreBase implements SSLContextProvider {
|
||||
|
||||
@Override
|
||||
KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {
|
||||
var userKeyStore = KeyStore.getInstance("KeychainStore");
|
||||
var systemRootKeyStore = KeyStore.getInstance("KeychainStore-ROOT");
|
||||
userKeyStore.load(null);
|
||||
systemRootKeyStore.load(null);
|
||||
try {
|
||||
CombinedKeyStoreSpi spi = CombinedKeyStoreSpi.create(userKeyStore, systemRootKeyStore);
|
||||
Provider dummyProvider = new Provider("CombinedKeyStoreProvider", "1.0", "Provides a combined, read-only KeyStore") {};
|
||||
return new KeyStore(spi, dummyProvider, "CombinedKeyStoreProvider") {};
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new KeyStoreException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.cryptomator.networking;
|
||||
|
||||
import org.cryptomator.integrations.common.CheckAvailability;
|
||||
import org.cryptomator.integrations.common.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* SSLContextProvider for Linux using a PKCS#12 file as trust store
|
||||
*/
|
||||
@OperatingSystem(OperatingSystem.Value.LINUX)
|
||||
@CheckAvailability
|
||||
public class SSLContextWithPKCS12TrustStore extends SSLContextDifferentTrustStoreBase implements SSLContextProvider {
|
||||
|
||||
private static final String CERT_FILE_LOCATION_PROPERTY = "cryptomator.networking.truststore.p12Path";
|
||||
|
||||
@Override
|
||||
KeyStore getTruststore() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {
|
||||
var pkcs12FilePath = Path.of(System.getProperty(CERT_FILE_LOCATION_PROPERTY));
|
||||
try {
|
||||
return KeyStore.getInstance(pkcs12FilePath.toFile(), new char[]{});
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new NoSuchFileException(pkcs12FilePath.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@CheckAvailability
|
||||
public static boolean isSupported() {
|
||||
var pkcs12Path = System.getProperty(CERT_FILE_LOCATION_PROPERTY);
|
||||
return Optional.ofNullable(pkcs12Path) //
|
||||
.map(Path::of) //
|
||||
.map(Files::exists).orElse(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.cryptomator.networking;
|
||||
|
||||
import org.cryptomator.integrations.common.OperatingSystem;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
|
||||
/**
|
||||
* SSLContextProvider for Windows using the Windows certificate store as trust store
|
||||
* <p>
|
||||
* In order to work, the jdk.crypto.mscapi jmod is needed
|
||||
*/
|
||||
@OperatingSystem(OperatingSystem.Value.WINDOWS)
|
||||
public class SSLContextWithWindowsCertStore extends SSLContextDifferentTrustStoreBase implements SSLContextProvider {
|
||||
|
||||
@Override
|
||||
KeyStore getTruststore() throws KeyStoreException {
|
||||
return KeyStore.getInstance("WINDOWS-ROOT");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public class CreateNewVaultExpertSettingsController implements FxController {
|
||||
|
||||
public static final int MAX_SHORTENING_THRESHOLD = 220;
|
||||
public static final int MIN_SHORTENING_THRESHOLD = 36;
|
||||
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/en/1.7/security/architecture/#name-shortening";
|
||||
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/security/architecture/#name-shortening";
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Application> application;
|
||||
|
||||
@@ -13,55 +13,64 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.WindowEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class CreateNewVaultLocationController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultLocationController.class);
|
||||
private static final Path DEFAULT_CUSTOM_VAULT_PATH = Paths.get(System.getProperty("user.home"));
|
||||
private static final String TEMP_FILE_FORMAT = ".locationTest.cryptomator.tmp";
|
||||
private static final String TEMP_FILE_PREFIX = ".locationTest.cryptomator";
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> chooseNameScene;
|
||||
private final Lazy<Scene> chooseExpertSettingsScene;
|
||||
private final List<RadioButton> locationPresetBtns;
|
||||
private final ObjectProperty<Path> vaultPath;
|
||||
private final StringProperty vaultName;
|
||||
private final ExecutorService backgroundExecutor;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final ObservableValue<VaultPathStatus> vaultPathStatus;
|
||||
private final ObservableValue<Boolean> validVaultPath;
|
||||
private final BooleanProperty usePresetPath;
|
||||
private final BooleanProperty loadingPresetLocations = new SimpleBooleanProperty(false);
|
||||
private final ObservableList<Node> radioButtons;
|
||||
private final ObservableList<Node> sortedRadioButtons;
|
||||
|
||||
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
|
||||
|
||||
//FXML
|
||||
public ToggleGroup locationPresetsToggler;
|
||||
public VBox radioButtonVBox;
|
||||
public HBox customLocationRadioBtn;
|
||||
public RadioButton customRadioButton;
|
||||
public Label locationStatusLabel;
|
||||
public FontAwesome5IconView goodLocation;
|
||||
@@ -73,25 +82,20 @@ public class CreateNewVaultLocationController implements FxController {
|
||||
@FxmlScene(FxmlFile.ADDVAULT_NEW_EXPERT_SETTINGS) Lazy<Scene> chooseExpertSettingsScene, //
|
||||
ObjectProperty<Path> vaultPath, //
|
||||
@Named("vaultName") StringProperty vaultName, //
|
||||
ResourceBundle resourceBundle) {
|
||||
ExecutorService backgroundExecutor, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.chooseNameScene = chooseNameScene;
|
||||
this.chooseExpertSettingsScene = chooseExpertSettingsScene;
|
||||
this.vaultPath = vaultPath;
|
||||
this.vaultName = vaultName;
|
||||
this.backgroundExecutor = backgroundExecutor;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.vaultPathStatus = ObservableUtil.mapWithDefault(vaultPath, this::validatePath, new VaultPathStatus(false, "error.message"));
|
||||
this.validVaultPath = ObservableUtil.mapWithDefault(vaultPathStatus, VaultPathStatus::valid, false);
|
||||
this.vaultPathStatus.addListener(this::updateStatusLabel);
|
||||
this.usePresetPath = new SimpleBooleanProperty();
|
||||
this.locationPresetBtns = LocationPresetsProvider.loadAll(LocationPresetsProvider.class) //
|
||||
.flatMap(LocationPresetsProvider::getLocations) //
|
||||
.sorted(Comparator.comparing(LocationPreset::name)) //
|
||||
.map(preset -> { //
|
||||
var btn = new RadioButton(preset.name());
|
||||
btn.setUserData(preset.path());
|
||||
return btn;
|
||||
}).toList();
|
||||
this.radioButtons = FXCollections.observableArrayList();
|
||||
this.sortedRadioButtons = radioButtons.sorted(this::compareLocationPresets);
|
||||
}
|
||||
|
||||
private VaultPathStatus validatePath(Path p) throws NullPointerException {
|
||||
@@ -121,28 +125,64 @@ public class CreateNewVaultLocationController implements FxController {
|
||||
|
||||
|
||||
private boolean isActuallyWritable(Path p) {
|
||||
Path tmpFile = p.resolve(TEMP_FILE_FORMAT);
|
||||
try (var chan = Files.newByteChannel(tmpFile, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) {
|
||||
Path tmpDir = null;
|
||||
try {
|
||||
tmpDir = Files.createTempDirectory(p, TEMP_FILE_PREFIX );
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
Files.deleteIfExists(tmpFile);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unable to delete temporary file {}. Needs to be deleted manually.", tmpFile);
|
||||
if (tmpDir != null) {
|
||||
try {
|
||||
Files.deleteIfExists(tmpDir);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Unable to delete temporary directory {}. Needs to be deleted manually.", tmpDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
radioButtonVBox.getChildren().addAll(1, locationPresetBtns); //first item is the list header
|
||||
locationPresetsToggler.getToggles().addAll(locationPresetBtns);
|
||||
var task = backgroundExecutor.submit(this::loadLocationPresets);
|
||||
window.addEventHandler(WindowEvent.WINDOW_HIDING, _ -> task.cancel(true));
|
||||
locationPresetsToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
|
||||
usePresetPath.bind(locationPresetsToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
|
||||
radioButtons.add(customLocationRadioBtn);
|
||||
Bindings.bindContent(radioButtonVBox.getChildren(), sortedRadioButtons); //to prevent garbage collection of the binding, we bind explicitly to the sorted list
|
||||
}
|
||||
|
||||
private void loadLocationPresets() {
|
||||
Platform.runLater(() -> loadingPresetLocations.set(true));
|
||||
try {
|
||||
LocationPresetsProvider.loadAll(LocationPresetsProvider.class) //
|
||||
.flatMap(LocationPresetsProvider::getLocations) //we do not use sorted(), because it evaluates the stream elements, blocking until all elements are gathered
|
||||
.forEach(this::createRadioButtonFor);
|
||||
} finally {
|
||||
Platform.runLater(() -> loadingPresetLocations.set(false));
|
||||
}
|
||||
}
|
||||
|
||||
private void createRadioButtonFor(LocationPreset preset) {
|
||||
Platform.runLater(() -> {
|
||||
var btn = new RadioButton(preset.name());
|
||||
btn.setUserData(preset.path());
|
||||
radioButtons.add(btn);
|
||||
locationPresetsToggler.getToggles().add(btn);
|
||||
});
|
||||
}
|
||||
|
||||
private int compareLocationPresets(Node left, Node right) {
|
||||
if (customLocationRadioBtn.getId().equals(left.getId())) {
|
||||
return 1;
|
||||
} else if (customLocationRadioBtn.getId().equals(right.getId())) {
|
||||
return -1;
|
||||
} else {
|
||||
return ((RadioButton) left).getText().compareToIgnoreCase(((RadioButton) right).getText());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
||||
var storagePath = Optional.ofNullable((Path) newValue.getUserData()).orElse(customVaultPath);
|
||||
vaultPath.set(storagePath.resolve(vaultName.get()));
|
||||
@@ -197,7 +237,15 @@ public class CreateNewVaultLocationController implements FxController {
|
||||
}
|
||||
|
||||
public boolean isValidVaultPath() {
|
||||
return validVaultPath.getValue();
|
||||
return Boolean.TRUE.equals(validVaultPath.getValue());
|
||||
}
|
||||
|
||||
public boolean isLoadingPresetLocations() {
|
||||
return loadingPresetLocations.getValue();
|
||||
}
|
||||
|
||||
public BooleanProperty loadingPresetLocationsProperty() {
|
||||
return loadingPresetLocations;
|
||||
}
|
||||
|
||||
public BooleanProperty usePresetPathProperty() {
|
||||
|
||||
@@ -76,8 +76,10 @@ public class ReadmeGenerator {
|
||||
input.chars().forEachOrdered(c -> {
|
||||
if (c < 128) {
|
||||
sb.append((char) c);
|
||||
} else if (c <= 0xFF) {
|
||||
sb.append("\\'").append(String.format("%02X", c));
|
||||
} else if (c < 0xFFFF) {
|
||||
sb.append("\\u").append(c);
|
||||
sb.append("\\uc1\\u").append(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ public enum FxmlFile {
|
||||
CONVERTVAULT_HUBTOPASSWORD_START("/fxml/convertvault_hubtopassword_start.fxml"), //
|
||||
CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), //
|
||||
CONVERTVAULT_HUBTOPASSWORD_SUCCESS("/fxml/convertvault_hubtopassword_success.fxml"), //
|
||||
DECRYPTNAMES("/fxml/decryptnames.fxml"), //
|
||||
ERROR("/fxml/error.fxml"), //
|
||||
EVENT_VIEW("/fxml/eventview.fxml"), //
|
||||
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
|
||||
HEALTH_START("/fxml/health_start.fxml"), //
|
||||
HEALTH_CHECK_LIST("/fxml/health_check_list.fxml"), //
|
||||
@@ -44,8 +46,8 @@ public enum FxmlFile {
|
||||
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
|
||||
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
|
||||
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
|
||||
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
|
||||
SHARE_VAULT("/fxml/share_vault.fxml"), //
|
||||
SIMPLE_DIALOG("/fxml/simple_dialog.fxml"), //
|
||||
UPDATE_REMINDER("/fxml/update_reminder.fxml"), //
|
||||
UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"),
|
||||
UNLOCK_REQUIRES_RESTART("/fxml/unlock_requires_restart.fxml"), //
|
||||
|
||||
@@ -7,6 +7,7 @@ public enum FontAwesome5Icon {
|
||||
ANCHOR("\uF13D"), //
|
||||
ARROW_UP("\uF062"), //
|
||||
BAN("\uF05E"), //
|
||||
BELL("\uF0F3"), //
|
||||
BUG("\uF188"), //
|
||||
CARET_DOWN("\uF0D7"), //
|
||||
CARET_RIGHT("\uF0Da"), //
|
||||
@@ -15,9 +16,12 @@ public enum FontAwesome5Icon {
|
||||
CLIPBOARD("\uF328"), //
|
||||
COG("\uF013"), //
|
||||
COGS("\uF085"), //
|
||||
COMPRESS_ALT("\uF422"), //
|
||||
COPY("\uF0C5"), //
|
||||
CROWN("\uF521"), //
|
||||
DONATE("\uF4B9"), //
|
||||
EDIT("\uF044"), //
|
||||
ELLIPSIS_V("\uF142"), //
|
||||
EXCHANGE_ALT("\uF362"), //
|
||||
EXCLAMATION("\uF12A"), //
|
||||
EXCLAMATION_CIRCLE("\uF06A"), //
|
||||
@@ -49,6 +53,7 @@ public enum FontAwesome5Icon {
|
||||
SEARCH("\uF002"), //
|
||||
SHARE("\uF064"), //
|
||||
SPINNER("\uF110"), //
|
||||
SPONSORS("\uF2B5"), //
|
||||
STETHOSCOPE("\uF0f1"), //
|
||||
SYNC("\uF021"), //
|
||||
TIMES("\uF00D"), //
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.cryptomator.ui.controls;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public class NotificationBar extends HBox {
|
||||
|
||||
@FXML
|
||||
private Label notificationLabel;
|
||||
|
||||
private final BooleanProperty dismissable = new SimpleBooleanProperty();
|
||||
private final BooleanProperty notify = new SimpleBooleanProperty();
|
||||
|
||||
|
||||
public NotificationBar() {
|
||||
setAlignment(Pos.CENTER);
|
||||
setStyle("-fx-alignment: center;");
|
||||
|
||||
Region spacer = new Region();
|
||||
spacer.setMinWidth(40);
|
||||
|
||||
Region leftRegion = new Region();
|
||||
HBox.setHgrow(leftRegion, javafx.scene.layout.Priority.ALWAYS);
|
||||
|
||||
Region rightRegion = new Region();
|
||||
HBox.setHgrow(rightRegion, javafx.scene.layout.Priority.ALWAYS);
|
||||
|
||||
VBox vbox = new VBox();
|
||||
vbox.setAlignment(Pos.CENTER);
|
||||
HBox.setHgrow(vbox, javafx.scene.layout.Priority.ALWAYS);
|
||||
|
||||
notificationLabel = new Label();
|
||||
notificationLabel.getStyleClass().add("notification-label");
|
||||
notificationLabel.setStyle("-fx-alignment: center;");
|
||||
vbox.getChildren().add(notificationLabel);
|
||||
|
||||
Button closeButton = new Button("X");
|
||||
closeButton.setMinWidth(40);
|
||||
closeButton.setStyle("-fx-background-color: transparent; -fx-text-fill: white; -fx-font-weight: bold;");
|
||||
closeButton.visibleProperty().bind(dismissable);
|
||||
|
||||
closeButton.setOnAction(_ -> {
|
||||
visibleProperty().unbind();
|
||||
managedProperty().unbind();
|
||||
visibleProperty().set(false);
|
||||
managedProperty().set(false);
|
||||
});
|
||||
closeButton.visibleProperty().bind(dismissable);
|
||||
|
||||
getChildren().addAll(spacer, leftRegion, vbox, rightRegion, closeButton);
|
||||
|
||||
visibleProperty().bind(notifyProperty());
|
||||
managedProperty().bind(notifyProperty());
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return notificationLabel.getText();
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
notificationLabel.setText(text);
|
||||
}
|
||||
|
||||
public void setStyleClass(String styleClass) {
|
||||
getStyleClass().clear();
|
||||
getStyleClass().add(styleClass);
|
||||
}
|
||||
|
||||
public boolean isDismissable() {
|
||||
return dismissable.get();
|
||||
}
|
||||
|
||||
public void setDismissable(boolean value) {
|
||||
dismissable.set(value);
|
||||
}
|
||||
|
||||
public boolean getNotify() {
|
||||
return notify.get();
|
||||
}
|
||||
|
||||
public void setNotify(boolean value) {
|
||||
notify.set(value);
|
||||
}
|
||||
|
||||
public BooleanProperty notifyProperty() {
|
||||
return notify;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingStrategy;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
@@ -108,6 +109,7 @@ public class HubToPasswordConvertController implements FxController {
|
||||
.thenRunAsync(this::convertInternal, backgroundExecutorService) //
|
||||
.whenCompleteAsync((result, exception) -> {
|
||||
if (exception == null) {
|
||||
vault.getVaultSettings().lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
|
||||
LOG.info("Conversion of vault {} succeeded.", vault.getPath());
|
||||
window.setScene(successScene.get());
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public record CipherAndCleartext(Path ciphertext, String cleartextName) {
|
||||
|
||||
public String getCiphertextFilename() {
|
||||
return ciphertext.getFileName().toString();
|
||||
}
|
||||
|
||||
public ObservableValue<String> ciphertextFilenameProperty() {
|
||||
return new ReadOnlyStringWrapper(getCiphertextFilename());
|
||||
}
|
||||
|
||||
public String getCleartextName() {
|
||||
return cleartextName;
|
||||
}
|
||||
|
||||
public ObservableValue<String> cleartextNameProperty() {
|
||||
return new ReadOnlyStringWrapper(getCleartextName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.common.Constants;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.DataFormat;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@DecryptNameScoped
|
||||
public class DecryptFileNamesViewController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DecryptFileNamesViewController.class);
|
||||
private static final KeyCodeCombination COPY_TO_CLIPBOARD_SHORTCUT = new KeyCodeCombination(KeyCode.C, KeyCodeCombination.SHORTCUT_DOWN);
|
||||
private static final String COPY_TO_CLIPBOARD_SHORTCUT_STRING_WIN = "CTRL+C";
|
||||
private static final String COPY_TO_CLIPBOARD_SHORTCUT_STRING_MAC = "⌘C";
|
||||
private static final String COPY_TO_CLIPBOARD_SHORTCUT_STRING_LINUX = "CTRL+C";
|
||||
|
||||
private final ListProperty<CipherAndCleartext> mapping;
|
||||
private final StringProperty dropZoneText = new SimpleStringProperty();
|
||||
private final ObjectProperty<FontAwesome5Icon> dropZoneIcon = new SimpleObjectProperty<>();
|
||||
private final BooleanProperty wrongFilesSelected = new SimpleBooleanProperty(false);
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final List<Path> initialList;
|
||||
|
||||
@FXML
|
||||
public TableColumn<CipherAndCleartext, String> ciphertextColumn;
|
||||
@FXML
|
||||
public TableColumn<CipherAndCleartext, String> cleartextColumn;
|
||||
@FXML
|
||||
public TableView<CipherAndCleartext> cipherToCleartextTable;
|
||||
|
||||
@Inject
|
||||
public DecryptFileNamesViewController(@DecryptNameWindow Stage window, @DecryptNameWindow Vault vault, @DecryptNameWindow List<Path> pathsToDecrypt, ResourceBundle resourceBundle) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.mapping = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
this.initialList = pathsToDecrypt;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
cipherToCleartextTable.setItems(mapping);
|
||||
cipherToCleartextTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS);
|
||||
//DragNDrop
|
||||
cipherToCleartextTable.setOnDragEntered(event -> {
|
||||
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
cipherToCleartextTable.setItems(FXCollections.emptyObservableList());
|
||||
}
|
||||
});
|
||||
cipherToCleartextTable.setOnDragOver(event -> {
|
||||
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
if (SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_MAC) {
|
||||
event.acceptTransferModes(TransferMode.LINK);
|
||||
} else {
|
||||
event.acceptTransferModes(TransferMode.ANY);
|
||||
}
|
||||
}
|
||||
});
|
||||
cipherToCleartextTable.setOnDragDropped(event -> {
|
||||
if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||
checkAndDecrypt(event.getDragboard().getFiles().stream().map(File::toPath).toList());
|
||||
cipherToCleartextTable.setItems(mapping);
|
||||
}
|
||||
});
|
||||
cipherToCleartextTable.setOnDragExited(_ -> cipherToCleartextTable.setItems(mapping));
|
||||
//selectionModel and copy-to-clipboard action
|
||||
cipherToCleartextTable.getSelectionModel().setCellSelectionEnabled(true);
|
||||
cipherToCleartextTable.setOnKeyPressed(keyEvent -> {
|
||||
if (COPY_TO_CLIPBOARD_SHORTCUT.match(keyEvent)) {
|
||||
copySingleCelltoClipboard();
|
||||
}
|
||||
});
|
||||
ciphertextColumn.setCellValueFactory(new PropertyValueFactory<>("ciphertextFilename"));
|
||||
cleartextColumn.setCellValueFactory(new PropertyValueFactory<>("cleartextName"));
|
||||
|
||||
dropZoneText.setValue(resourceBundle.getString("decryptNames.dropZone.message"));
|
||||
dropZoneIcon.setValue(FontAwesome5Icon.FILE_IMPORT);
|
||||
|
||||
wrongFilesSelected.addListener((_, _, areWrongFiles) -> {
|
||||
if (areWrongFiles) {
|
||||
CompletableFuture.delayedExecutor(5, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
|
||||
dropZoneText.setValue(resourceBundle.getString("decryptNames.dropZone.message"));
|
||||
dropZoneIcon.setValue(FontAwesome5Icon.FILE_IMPORT);
|
||||
wrongFilesSelected.setValue(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!initialList.isEmpty()) {
|
||||
checkAndDecrypt(initialList);
|
||||
}
|
||||
}
|
||||
|
||||
private void copySingleCelltoClipboard() {
|
||||
cipherToCleartextTable.getSelectionModel().getSelectedCells().stream().findFirst().ifPresent(tablePosition -> {
|
||||
var selectedItem = cipherToCleartextTable.getSelectionModel().getSelectedItem();
|
||||
//TODO: give user feedback, if content is copied -> must be done via a custom cell factory to access the actual table cell!
|
||||
if (tablePosition.getTableColumn().equals(ciphertextColumn)) {
|
||||
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, selectedItem.ciphertext().toString()));
|
||||
} else {
|
||||
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, selectedItem.cleartextName()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void selectFiles() {
|
||||
var fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(resourceBundle.getString("decryptNames.filePicker.title"));
|
||||
fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter(resourceBundle.getString("decryptNames.filePicker.extensionDescription"), List.of("*.c9r")));
|
||||
fileChooser.setInitialDirectory(vault.getPath().toFile());
|
||||
var ciphertextNodes = fileChooser.showOpenMultipleDialog(window);
|
||||
if (ciphertextNodes != null) {
|
||||
checkAndDecrypt(ciphertextNodes.stream().map(File::toPath).toList());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAndDecrypt(List<Path> pathsToDecrypt) {
|
||||
mapping.clear();
|
||||
//Assumption: All files are in the same directory
|
||||
var testPath = pathsToDecrypt.getFirst();
|
||||
if (!testPath.startsWith(vault.getPath())) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.foreignFiles").formatted(vault.getDisplayName()));
|
||||
return;
|
||||
}
|
||||
if (pathsToDecrypt.size() == 1 && testPath.endsWith(Constants.DIR_ID_BACKUP_FILE_NAME)) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.vaultInternalFiles"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var newMapping = pathsToDecrypt.stream().filter(p -> !p.endsWith(Constants.DIR_ID_BACKUP_FILE_NAME)).map(this::getCleartextName).toList();
|
||||
mapping.addAll(newMapping);
|
||||
} catch (UncheckedIOException e) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.generic"));
|
||||
LOG.info("Failed to decrypt filenames for directory {}", testPath.getParent(), e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.vaultInternalFiles"));
|
||||
} catch (UnsupportedOperationException e) {
|
||||
setDropZoneError(resourceBundle.getString("decryptNames.dropZone.error.noDirIdBackup"));
|
||||
}
|
||||
}
|
||||
|
||||
private void setDropZoneError(String text) {
|
||||
dropZoneIcon.setValue(FontAwesome5Icon.TIMES);
|
||||
dropZoneText.setValue(text);
|
||||
wrongFilesSelected.setValue(true);
|
||||
}
|
||||
|
||||
private CipherAndCleartext getCleartextName(Path ciphertextNode) {
|
||||
try {
|
||||
var cleartextName = vault.getCleartextName(ciphertextNode);
|
||||
return new CipherAndCleartext(ciphertextNode, cleartextName);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
//obvservable getter
|
||||
|
||||
public ObservableValue<String> dropZoneTextProperty() {
|
||||
return dropZoneText;
|
||||
}
|
||||
|
||||
public String getDropZoneText() {
|
||||
return dropZoneText.get();
|
||||
}
|
||||
|
||||
public ObservableValue<FontAwesome5Icon> dropZoneIconProperty() {
|
||||
return dropZoneIcon;
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getDropZoneIcon() {
|
||||
return dropZoneIcon.get();
|
||||
}
|
||||
|
||||
public void clearTable() {
|
||||
mapping.clear();
|
||||
}
|
||||
|
||||
public void copyTableToClipboard() {
|
||||
var csv = mapping.stream().map(cipherAndClear -> "\"" + cipherAndClear.ciphertext() + "\", \"" + cipherAndClear.cleartextName() + "\"").collect(Collectors.joining("\n"));
|
||||
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, csv));
|
||||
}
|
||||
|
||||
public String getCopyToClipboardShortcutString() {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
return COPY_TO_CLIPBOARD_SHORTCUT_STRING_WIN;
|
||||
} else if (SystemUtils.IS_OS_MAC) {
|
||||
return COPY_TO_CLIPBOARD_SHORTCUT_STRING_MAC;
|
||||
} else {
|
||||
return COPY_TO_CLIPBOARD_SHORTCUT_STRING_LINUX;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Lazy;
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
@DecryptNameScoped
|
||||
@Subcomponent(modules = DecryptNameModule.class)
|
||||
public interface DecryptNameComponent {
|
||||
|
||||
Logger LOG = LoggerFactory.getLogger(DecryptNameComponent.class);
|
||||
|
||||
@DecryptNameWindow
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.DECRYPTNAMES)
|
||||
Lazy<Scene> decryptNamesView();
|
||||
|
||||
@DecryptNameWindow
|
||||
Vault vault();
|
||||
|
||||
default void showDecryptFileNameWindow() {
|
||||
Stage s = window();
|
||||
s.setScene(decryptNamesView().get());
|
||||
s.sizeToScene();
|
||||
if (vault().isUnlocked()) {
|
||||
s.show();
|
||||
} else {
|
||||
LOG.error("Aborted showing DecryptFileName window: vault state is not {}, but {}.", VaultState.Value.UNLOCKED, vault().getState());
|
||||
}
|
||||
}
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
DecryptNameComponent create(@BindsInstance @DecryptNameWindow Vault vault, @BindsInstance @Named("windowOwner") Stage owner, @BindsInstance @DecryptNameWindow List<Path> pathsToDecrypt);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.cryptomator.ui.removevault;
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
@@ -12,8 +12,8 @@ import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.fxapp.PrimaryStage;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Modality;
|
||||
@@ -22,38 +22,38 @@ import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
abstract class RemoveVaultModule {
|
||||
public abstract class DecryptNameModule {
|
||||
|
||||
@Provides
|
||||
@RemoveVaultWindow
|
||||
@RemoveVaultScoped
|
||||
@DecryptNameScoped
|
||||
@DecryptNameWindow
|
||||
static Stage provideStage(StageFactory factory, @Named("windowOwner") Stage owner, @DecryptNameWindow Vault vault, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create();
|
||||
stage.setResizable(true);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.setTitle(resourceBundle.getString("decryptNames.title"));
|
||||
vault.stateProperty().addListener(((_, _, _) -> stage.close())); //as soon as the state changes from unlocked, close the window
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@DecryptNameScoped
|
||||
@DecryptNameWindow
|
||||
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
|
||||
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@RemoveVaultWindow
|
||||
@RemoveVaultScoped
|
||||
static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, @RemoveVaultWindow Vault vault, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(String.format(resourceBundle.getString("removeVault.title"), vault.getDisplayName()));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(primaryStage);
|
||||
return stage;
|
||||
@FxmlScene(FxmlFile.DECRYPTNAMES)
|
||||
@DecryptNameScoped
|
||||
static Scene provideDecryptNamesViewScene(@DecryptNameWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.DECRYPTNAMES);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.REMOVE_VAULT)
|
||||
@RemoveVaultScoped
|
||||
static Scene provideRemoveVaultScene(@RemoveVaultWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene(FxmlFile.REMOVE_VAULT);
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(RemoveVaultController.class)
|
||||
abstract FxController bindRemoveVaultController(RemoveVaultController controller);
|
||||
@FxControllerKey(DecryptFileNamesViewController.class)
|
||||
abstract FxController bindDecryptNamesViewController(DecryptFileNamesViewController controller);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface DecryptNameScoped {}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.cryptomator.ui.decryptname;
|
||||
|
||||
import javax.inject.Qualifier;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@interface DecryptNameWindow {}
|
||||
90
src/main/java/org/cryptomator/ui/dialogs/Dialogs.java
Normal file
90
src/main/java/org/cryptomator/ui/dialogs/Dialogs.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package org.cryptomator.ui.dialogs;
|
||||
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.stage.Stage;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class Dialogs {
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final StageFactory stageFactory;
|
||||
|
||||
@Inject
|
||||
public Dialogs(ResourceBundle resourceBundle, StageFactory stageFactory) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.stageFactory = stageFactory;
|
||||
}
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Dialogs.class);
|
||||
|
||||
private SimpleDialog.Builder createDialogBuilder() {
|
||||
return new SimpleDialog.Builder(resourceBundle, stageFactory);
|
||||
}
|
||||
|
||||
public SimpleDialog.Builder prepareRemoveVaultDialog(Stage window, Vault vault, ObservableList<Vault> vaults) {
|
||||
return createDialogBuilder().setOwner(window) //
|
||||
.setTitleKey("removeVault.title", vault.getDisplayName()) //
|
||||
.setMessageKey("removeVault.message") //
|
||||
.setDescriptionKey("removeVault.description") //
|
||||
.setIcon(FontAwesome5Icon.QUESTION) //
|
||||
.setOkButtonKey("generic.button.remove") //
|
||||
.setCancelButtonKey("generic.button.cancel") //
|
||||
.setOkAction(stage -> {
|
||||
LOG.debug("Removing vault {}.", vault.getDisplayName());
|
||||
vaults.remove(vault);
|
||||
stage.close();
|
||||
});
|
||||
}
|
||||
|
||||
public SimpleDialog.Builder prepareRemoveCertDialog(Stage window, Settings settings) {
|
||||
return createDialogBuilder() //
|
||||
.setOwner(window) //
|
||||
.setTitleKey("removeCert.title") //
|
||||
.setMessageKey("removeCert.message") //
|
||||
.setDescriptionKey("removeCert.description") //
|
||||
.setIcon(FontAwesome5Icon.QUESTION) //
|
||||
.setOkButtonKey("generic.button.remove") //
|
||||
.setCancelButtonKey("generic.button.cancel") //
|
||||
.setOkAction(stage -> {
|
||||
settings.licenseKey.set(null);
|
||||
stage.close();
|
||||
});
|
||||
}
|
||||
|
||||
public SimpleDialog.Builder prepareDokanySupportEndDialog(Stage window, Consumer<Stage> cancelAction) {
|
||||
return createDialogBuilder() //
|
||||
.setOwner(window) //
|
||||
.setTitleKey("dokanySupportEnd.title") //
|
||||
.setMessageKey("dokanySupportEnd.message") //
|
||||
.setDescriptionKey("dokanySupportEnd.description") //
|
||||
.setIcon(FontAwesome5Icon.EXCLAMATION) //
|
||||
.setOkButtonKey("generic.button.close") //
|
||||
.setCancelButtonKey("dokanySupportEnd.preferencesBtn") //
|
||||
.setOkAction(Stage::close) //
|
||||
.setCancelAction(cancelAction);
|
||||
}
|
||||
|
||||
public SimpleDialog.Builder prepareRetryIfReadonlyDialog(Stage window, Consumer<Stage> okAction) {
|
||||
return createDialogBuilder() //
|
||||
.setOwner(window) //
|
||||
.setTitleKey("retryIfReadonly.title") //
|
||||
.setMessageKey("retryIfReadonly.message") //
|
||||
.setDescriptionKey("retryIfReadonly.description") //
|
||||
.setIcon(FontAwesome5Icon.EXCLAMATION) //
|
||||
.setOkButtonKey("retryIfReadonly.retry") //
|
||||
.setCancelButtonKey("generic.button.close") //
|
||||
.setOkAction(okAction) //
|
||||
.setCancelAction(Stage::close);
|
||||
}
|
||||
}
|
||||
139
src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java
Normal file
139
src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package org.cryptomator.ui.dialogs;
|
||||
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.IllegalFormatException;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class SimpleDialog {
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final Stage dialogStage;
|
||||
|
||||
SimpleDialog(Builder builder) throws IOException {
|
||||
this.resourceBundle = builder.resourceBundle;
|
||||
dialogStage = builder.stageFactory.create();
|
||||
dialogStage.initOwner(builder.owner);
|
||||
dialogStage.initModality(Modality.WINDOW_MODAL);
|
||||
dialogStage.setTitle(resolveText(builder.titleKey, builder.titleArgs));
|
||||
dialogStage.setResizable(false);
|
||||
|
||||
FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( //
|
||||
new SimpleDialogController(resolveText(builder.messageKey, null), //
|
||||
resolveText(builder.descriptionKey, null), //
|
||||
builder.icon, //
|
||||
resolveText(builder.okButtonKey, null), //
|
||||
builder.cancelButtonKey != null ? resolveText(builder.cancelButtonKey, null) : null, //
|
||||
() -> builder.okAction.accept(dialogStage), //
|
||||
() -> builder.cancelAction.accept(dialogStage)), //
|
||||
Scene::new, builder.resourceBundle);
|
||||
|
||||
dialogStage.setScene(new Scene(loaderFactory.load(FxmlFile.SIMPLE_DIALOG.getRessourcePathString()).getRoot()));
|
||||
}
|
||||
|
||||
public void showAndWait() {
|
||||
dialogStage.showAndWait();
|
||||
}
|
||||
|
||||
private String resolveText(String key, String[] args) {
|
||||
if (key == null || key.isEmpty() || !resourceBundle.containsKey(key)) {
|
||||
throw new IllegalArgumentException(String.format("Invalid key: '%s'. Key not found in ResourceBundle.", key));
|
||||
}
|
||||
String text = resourceBundle.getString(key);
|
||||
try {
|
||||
return args != null && args.length > 0 ? String.format(text, (Object[]) args) : text;
|
||||
} catch (IllegalFormatException e) {
|
||||
throw new IllegalArgumentException("Formatting error: Check if arguments match placeholders in the text.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private Stage owner;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final StageFactory stageFactory;
|
||||
private String titleKey;
|
||||
private String[] titleArgs;
|
||||
private String messageKey;
|
||||
private String descriptionKey;
|
||||
private String okButtonKey;
|
||||
private String cancelButtonKey;
|
||||
private FontAwesome5Icon icon;
|
||||
private Consumer<Stage> okAction = Stage::close;
|
||||
private Consumer<Stage> cancelAction = Stage::close;
|
||||
|
||||
public Builder(ResourceBundle resourceBundle, StageFactory stageFactory) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.stageFactory = stageFactory;
|
||||
}
|
||||
|
||||
public Builder setOwner(Stage owner) {
|
||||
this.owner = owner;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTitleKey(String titleKey, String... args) {
|
||||
this.titleKey = titleKey;
|
||||
this.titleArgs = args;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMessageKey(String messageKey) {
|
||||
this.messageKey = messageKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDescriptionKey(String descriptionKey) {
|
||||
this.descriptionKey = descriptionKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setIcon(FontAwesome5Icon icon) {
|
||||
this.icon = icon;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOkButtonKey(String okButtonKey) {
|
||||
this.okButtonKey = okButtonKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCancelButtonKey(String cancelButtonKey) {
|
||||
this.cancelButtonKey = cancelButtonKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOkAction(Consumer<Stage> okAction) {
|
||||
this.okAction = okAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCancelAction(Consumer<Stage> cancelAction) {
|
||||
this.cancelAction = cancelAction;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SimpleDialog build() {
|
||||
Objects.requireNonNull(titleKey, "SimpleDialog titleKey must be set.");
|
||||
Objects.requireNonNull(messageKey, "SimpleDialog messageKey must be set.");
|
||||
Objects.requireNonNull(descriptionKey, "SimpleDialog descriptionKey must be set.");
|
||||
Objects.requireNonNull(okButtonKey, "SimpleDialog okButtonKey must be set.");
|
||||
|
||||
try {
|
||||
return new SimpleDialog(this);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to create SimpleDialog.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.cryptomator.ui.dialogs;
|
||||
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
|
||||
public class SimpleDialogController implements FxController {
|
||||
|
||||
private final String message;
|
||||
private final String description;
|
||||
private final FontAwesome5Icon icon;
|
||||
private final String okButtonText;
|
||||
private final String cancelButtonText;
|
||||
private final Runnable okAction;
|
||||
private final Runnable cancelAction;
|
||||
private final boolean cancelButtonVisible;
|
||||
|
||||
public SimpleDialogController(String message, String description, FontAwesome5Icon icon, String okButtonText, String cancelButtonText, Runnable okAction, Runnable cancelAction) {
|
||||
this.message = message;
|
||||
this.description = description;
|
||||
this.icon = icon;
|
||||
this.okButtonText = okButtonText;
|
||||
this.cancelButtonText = cancelButtonText;
|
||||
this.okAction = okAction;
|
||||
this.cancelAction = cancelAction;
|
||||
this.cancelButtonVisible = cancelButtonText != null && !cancelButtonText.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isCancelButtonVisible() {
|
||||
return cancelButtonVisible;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public String getOkButtonText() {
|
||||
return okButtonText;
|
||||
}
|
||||
|
||||
public String getCancelButtonText() {
|
||||
return cancelButtonText;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleOk() {
|
||||
if (okAction != null) {
|
||||
okAction.run();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleCancel() {
|
||||
if (cancelAction != null) {
|
||||
cancelAction.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ public interface ErrorComponent {
|
||||
default Stage show() {
|
||||
Stage stage = window();
|
||||
stage.setScene(scene());
|
||||
stage.setMinWidth(420);
|
||||
stage.setMinHeight(300);
|
||||
stage.show();
|
||||
return stage;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ public class ErrorController implements FxController {
|
||||
private final BooleanExpression errorSolutionFound = matchingErrorDiscussion.isNotNull();
|
||||
private final BooleanProperty isLoadingHttpResponse = new SimpleBooleanProperty();
|
||||
private final BooleanProperty askedForLookupDatabasePermission = new SimpleBooleanProperty();
|
||||
private final boolean formerSceneWasResizable;
|
||||
|
||||
@Inject
|
||||
ErrorController(Application application, @Named("stackTrace") String stackTrace, ErrorCode errorCode, @Nullable Scene previousScene, Stage window, Environment environment, ExecutorService executorService) {
|
||||
@@ -85,12 +86,14 @@ public class ErrorController implements FxController {
|
||||
this.window = window;
|
||||
this.environment = environment;
|
||||
this.executorService = executorService;
|
||||
this.formerSceneWasResizable = window.isResizable();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void back() {
|
||||
if (previousScene != null) {
|
||||
window.setScene(previousScene);
|
||||
window.setResizable(formerSceneWasResizable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.event.FSEventBucket;
|
||||
import org.cryptomator.event.FSEventBucketContent;
|
||||
import org.cryptomator.event.FileSystemEventAggregator;
|
||||
import org.cryptomator.common.Nullable;
|
||||
import org.cryptomator.common.ObservableUtil;
|
||||
import org.cryptomator.cryptofs.CryptoPath;
|
||||
import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
|
||||
import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
|
||||
import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
|
||||
import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
|
||||
import org.cryptomator.integrations.revealpath.RevealFailedException;
|
||||
import org.cryptomator.integrations.revealpath.RevealPathService;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.FontAwesome5Icon;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.util.Duration;
|
||||
import java.nio.file.Path;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class EventListCellController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(EventListCellController.class);
|
||||
private static final DateTimeFormatter LOCAL_DATE_FORMATTER = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
|
||||
private static final DateTimeFormatter LOCAL_TIME_FORMATTER = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withZone(ZoneId.systemDefault());
|
||||
|
||||
private final FileSystemEventAggregator fileSystemEventAggregator;
|
||||
@Nullable
|
||||
private final RevealPathService revealService;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final ObjectProperty<Map.Entry<FSEventBucket, FSEventBucketContent>> eventEntry;
|
||||
private final StringProperty eventMessage;
|
||||
private final StringProperty eventDescription;
|
||||
private final ObjectProperty<FontAwesome5Icon> eventIcon;
|
||||
private final ObservableValue<String> eventCount;
|
||||
private final ObservableValue<Boolean> vaultUnlocked;
|
||||
private final ObservableValue<String> readableTime;
|
||||
private final ObservableValue<String> readableDate;
|
||||
private final ObservableValue<String> message;
|
||||
private final ObservableValue<String> description;
|
||||
private final ObservableValue<FontAwesome5Icon> icon;
|
||||
private final BooleanProperty actionsButtonVisible;
|
||||
private final Tooltip eventTooltip;
|
||||
|
||||
@FXML
|
||||
HBox root;
|
||||
@FXML
|
||||
ContextMenu eventActionsMenu;
|
||||
@FXML
|
||||
Button eventActionsButton;
|
||||
|
||||
@Inject
|
||||
public EventListCellController(FileSystemEventAggregator fileSystemEventAggregator, Optional<RevealPathService> revealService, ResourceBundle resourceBundle) {
|
||||
this.fileSystemEventAggregator = fileSystemEventAggregator;
|
||||
this.revealService = revealService.orElseGet(() -> null);
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.eventEntry = new SimpleObjectProperty<>(null);
|
||||
this.eventMessage = new SimpleStringProperty();
|
||||
this.eventDescription = new SimpleStringProperty();
|
||||
this.eventIcon = new SimpleObjectProperty<>();
|
||||
this.eventCount = ObservableUtil.mapWithDefault(eventEntry, e -> e.getValue().count() == 1? "" : "("+ e.getValue().count() +")", "");
|
||||
this.vaultUnlocked = ObservableUtil.mapWithDefault(eventEntry.flatMap(e -> e.getKey().vault().unlockedProperty()), Function.identity(), false);
|
||||
this.readableTime = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_TIME_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), "");
|
||||
this.readableDate = ObservableUtil.mapWithDefault(eventEntry, e -> LOCAL_DATE_FORMATTER.format(e.getValue().mostRecentEvent().getTimestamp()), "");
|
||||
this.message = Bindings.createStringBinding(this::selectMessage, vaultUnlocked, eventMessage);
|
||||
this.description = Bindings.createStringBinding(this::selectDescription, vaultUnlocked, eventDescription);
|
||||
this.icon = Bindings.createObjectBinding(this::selectIcon, vaultUnlocked, eventIcon);
|
||||
this.actionsButtonVisible = new SimpleBooleanProperty();
|
||||
this.eventTooltip = new Tooltip();
|
||||
eventTooltip.setShowDelay(Duration.millis(500.0));
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
actionsButtonVisible.bind(Bindings.createBooleanBinding(this::determineActionsButtonVisibility, root.hoverProperty(), eventActionsMenu.showingProperty(), vaultUnlocked));
|
||||
vaultUnlocked.addListener((_, _, newValue) -> eventActionsMenu.hide());
|
||||
Tooltip.install(root, eventTooltip);
|
||||
}
|
||||
|
||||
private boolean determineActionsButtonVisibility() {
|
||||
return vaultUnlocked.getValue() && (eventActionsMenu.isShowing() || root.isHover());
|
||||
}
|
||||
|
||||
public void setEventEntry(@NotNull Map.Entry<FSEventBucket, FSEventBucketContent> item) {
|
||||
eventEntry.set(item);
|
||||
eventActionsMenu.hide();
|
||||
eventActionsMenu.getItems().clear();
|
||||
eventTooltip.setText(item.getKey().vault().getDisplayName());
|
||||
addAction("generic.action.dismiss", () -> {
|
||||
fileSystemEventAggregator.remove(item.getKey());
|
||||
});
|
||||
switch (item.getValue().mostRecentEvent()) {
|
||||
case ConflictResolvedEvent fse -> this.adjustToConflictResolvedEvent(fse);
|
||||
case ConflictResolutionFailedEvent fse -> this.adjustToConflictEvent(fse);
|
||||
case DecryptionFailedEvent fse -> this.adjustToDecryptionFailedEvent(fse);
|
||||
case BrokenDirFileEvent fse -> this.adjustToBrokenDirFileEvent(fse);
|
||||
case BrokenFileNodeEvent fse -> this.adjustToBrokenFileNodeEvent(fse);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void adjustToBrokenFileNodeEvent(BrokenFileNodeEvent bfe) {
|
||||
eventIcon.setValue(FontAwesome5Icon.TIMES);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenFileNode.message"));
|
||||
eventDescription.setValue(bfe.ciphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.brokenFileNode.showEncrypted", () -> reveal(revealService, convertVaultPathToSystemPath(bfe.ciphertextPath())));
|
||||
} else {
|
||||
addAction("eventView.entry.brokenFileNode.copyEncrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.ciphertextPath()).toString()));
|
||||
}
|
||||
addAction("eventView.entry.brokenFileNode.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(bfe.cleartextPath()).toString()));
|
||||
}
|
||||
|
||||
private void adjustToConflictResolvedEvent(ConflictResolvedEvent cre) {
|
||||
eventIcon.setValue(FontAwesome5Icon.CHECK);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflictResolved.message"));
|
||||
eventDescription.setValue(cre.resolvedCiphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.conflictResolved.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cre.resolvedCleartextPath())));
|
||||
} else {
|
||||
addAction("eventView.entry.conflictResolved.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cre.resolvedCleartextPath()).toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustToConflictEvent(ConflictResolutionFailedEvent cfe) {
|
||||
eventIcon.setValue(FontAwesome5Icon.COMPRESS_ALT);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.conflict.message"));
|
||||
eventDescription.setValue(cfe.conflictingCiphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.conflict.showDecrypted", () -> reveal(revealService, convertVaultPathToSystemPath(cfe.canonicalCleartextPath())));
|
||||
addAction("eventView.entry.conflict.showEncrypted", () -> reveal(revealService, cfe.conflictingCiphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.conflict.copyDecrypted", () -> copyToClipboard(convertVaultPathToSystemPath(cfe.canonicalCleartextPath()).toString()));
|
||||
addAction("eventView.entry.conflict.copyEncrypted", () -> copyToClipboard(cfe.conflictingCiphertextPath().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustToDecryptionFailedEvent(DecryptionFailedEvent dfe) {
|
||||
eventIcon.setValue(FontAwesome5Icon.BAN);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.decryptionFailed.message"));
|
||||
eventDescription.setValue(dfe.ciphertextPath().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.decryptionFailed.showEncrypted", () -> reveal(revealService, dfe.ciphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.decryptionFailed.copyEncrypted", () -> copyToClipboard(dfe.ciphertextPath().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustToBrokenDirFileEvent(BrokenDirFileEvent bde) {
|
||||
eventIcon.setValue(FontAwesome5Icon.TIMES);
|
||||
eventMessage.setValue(resourceBundle.getString("eventView.entry.brokenDirFile.message"));
|
||||
eventDescription.setValue(bde.ciphertextPath().getParent().getFileName().toString());
|
||||
if (revealService != null) {
|
||||
addAction("eventView.entry.brokenDirFile.showEncrypted", () -> reveal(revealService, bde.ciphertextPath()));
|
||||
} else {
|
||||
addAction("eventView.entry.brokenDirFile.copyEncrypted", () -> copyToClipboard(bde.ciphertextPath().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void addAction(String localizationKey, Runnable action) {
|
||||
var entry = new MenuItem(resourceBundle.getString(localizationKey));
|
||||
entry.getStyleClass().addLast("dropdown-button-context-menu-item");
|
||||
entry.setOnAction(_ -> action.run());
|
||||
eventActionsMenu.getItems().addLast(entry);
|
||||
}
|
||||
|
||||
|
||||
private FontAwesome5Icon selectIcon() {
|
||||
if (vaultUnlocked.getValue()) {
|
||||
return eventIcon.getValue();
|
||||
} else {
|
||||
return FontAwesome5Icon.LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
private String selectMessage() {
|
||||
if (vaultUnlocked.getValue()) {
|
||||
return eventMessage.getValue();
|
||||
} else {
|
||||
return "***********";
|
||||
}
|
||||
}
|
||||
|
||||
private String selectDescription() {
|
||||
if (vaultUnlocked.getValue()) {
|
||||
return eventDescription.getValue();
|
||||
} else if (eventEntry.getValue() != null) {
|
||||
var e = eventEntry.getValue().getKey();
|
||||
return resourceBundle.getString("eventView.entry.vaultLocked.description").formatted(e != null ? e.vault().getDisplayName() : "");
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
public void toggleEventActionsMenu() {
|
||||
var e = eventEntry.get();
|
||||
if (e != null) {
|
||||
if (eventActionsMenu.isShowing()) {
|
||||
eventActionsMenu.hide();
|
||||
} else {
|
||||
eventActionsMenu.show(eventActionsButton, Side.BOTTOM, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Path convertVaultPathToSystemPath(Path p) {
|
||||
if (!(p instanceof CryptoPath)) {
|
||||
throw new IllegalArgumentException("Path " + p + " is not a vault path");
|
||||
}
|
||||
var v = eventEntry.getValue().getKey().vault();
|
||||
if (!v.isUnlocked()) {
|
||||
return Path.of(System.getProperty("user.home"));
|
||||
}
|
||||
|
||||
var mountUri = v.getMountPoint().uri();
|
||||
var internalPath = p.toString().substring(1);
|
||||
return Path.of(mountUri.getPath().concat(internalPath).substring(1));
|
||||
}
|
||||
|
||||
private void reveal(RevealPathService s, Path p) {
|
||||
try {
|
||||
s.reveal(p);
|
||||
} catch (RevealFailedException e) {
|
||||
LOG.warn("Failed to show path {}", p, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyToClipboard(String s) {
|
||||
var content = new ClipboardContent();
|
||||
content.putString(s);
|
||||
Clipboard.getSystemClipboard().setContent(content);
|
||||
}
|
||||
|
||||
//-- property accessors --
|
||||
public ObservableValue<String> messageProperty() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<String> countProperty() {
|
||||
return eventCount;
|
||||
}
|
||||
|
||||
public String getCount() {
|
||||
return eventCount.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<String> descriptionProperty() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<FontAwesome5Icon> iconProperty() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public FontAwesome5Icon getIcon() {
|
||||
return icon.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> actionsButtonVisibleProperty() {
|
||||
return actionsButtonVisible;
|
||||
}
|
||||
|
||||
public boolean isActionsButtonVisible() {
|
||||
return actionsButtonVisible.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<String> eventLocalTimeProperty() {
|
||||
return readableTime;
|
||||
}
|
||||
|
||||
public String getEventLocalTime() {
|
||||
return readableTime.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<String> eventLocalDateProperty() {
|
||||
return readableDate;
|
||||
}
|
||||
|
||||
public String getEventLocalDate() {
|
||||
return readableDate.getValue();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> vaultUnlockedProperty() {
|
||||
return vaultUnlocked;
|
||||
}
|
||||
|
||||
public boolean isVaultUnlocked() {
|
||||
return vaultUnlocked.getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import org.cryptomator.event.FSEventBucket;
|
||||
import org.cryptomator.event.FSEventBucketContent;
|
||||
import org.cryptomator.ui.common.FxmlLoaderFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.util.Callback;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Map;
|
||||
|
||||
@EventViewScoped
|
||||
public class EventListCellFactory implements Callback<ListView<Map.Entry<FSEventBucket, FSEventBucketContent>>, ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>>> {
|
||||
|
||||
private static final String FXML_PATH = "/fxml/eventview_cell.fxml";
|
||||
|
||||
private final FxmlLoaderFactory fxmlLoaders;
|
||||
|
||||
@Inject
|
||||
EventListCellFactory(@EventViewWindow FxmlLoaderFactory fxmlLoaders) {
|
||||
this.fxmlLoaders = fxmlLoaders;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> call(ListView<Map.Entry<FSEventBucket, FSEventBucketContent>> eventListView) {
|
||||
try {
|
||||
FXMLLoader fxmlLoader = fxmlLoaders.load(FXML_PATH);
|
||||
return new Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to load %s.".formatted(FXML_PATH), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Cell extends ListCell<Map.Entry<FSEventBucket, FSEventBucketContent>> {
|
||||
|
||||
private final Parent root;
|
||||
private final EventListCellController controller;
|
||||
|
||||
public Cell(Parent root, EventListCellController controller) {
|
||||
this.root = root;
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Map.Entry<FSEventBucket, FSEventBucketContent> item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
setGraphic(null);
|
||||
this.getStyleClass().remove("list-cell");
|
||||
} else {
|
||||
this.getStyleClass().addLast("list-cell");
|
||||
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
|
||||
setGraphic(root);
|
||||
controller.setEventEntry(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.cryptomator.ui.eventview;
|
||||
|
||||
import dagger.Lazy;
|
||||
import dagger.Subcomponent;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@EventViewScoped
|
||||
@Subcomponent(modules = {EventViewModule.class})
|
||||
public interface EventViewComponent {
|
||||
|
||||
@EventViewWindow
|
||||
Stage window();
|
||||
|
||||
@FxmlScene(FxmlFile.EVENT_VIEW)
|
||||
Lazy<Scene> scene();
|
||||
|
||||
default Stage showEventViewerWindow() {
|
||||
Stage stage = window();
|
||||
stage.setScene(scene().get());
|
||||
stage.sizeToScene();
|
||||
stage.show();
|
||||
stage.requestFocus();
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
|
||||
EventViewComponent create();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user