Compare commits
645 Commits
v2.6.4
...
v2.7.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03cddabfd0 | ||
|
|
8c2d77c68f | ||
|
|
d3cd422b46 | ||
|
|
482ab9ad14 | ||
|
|
835cd2ac80 | ||
|
|
36e2813169 | ||
|
|
35f8103b40 | ||
|
|
f4febe04bc | ||
|
|
c2f5f156f0 | ||
|
|
7136d92011 | ||
|
|
a35aa58943 | ||
|
|
e7f7ad7eb3 | ||
|
|
c0863ede02 | ||
|
|
45b39cdbc1 | ||
|
|
9447a10716 | ||
|
|
2039872ee5 | ||
|
|
016a90d7b0 | ||
|
|
9b5bb1365c | ||
|
|
b0dfdca76f | ||
|
|
de74057cc8 | ||
|
|
e2188b109a | ||
|
|
3ae55c2555 | ||
|
|
b47adb7aeb | ||
|
|
889cb636d3 | ||
|
|
c9719f44dc | ||
|
|
599dae0486 | ||
|
|
1c5cdc4d36 | ||
|
|
0156edf320 | ||
|
|
f425dfdfea | ||
|
|
7ea2f3fa59 | ||
|
|
1c22070a74 | ||
|
|
21f3cb8f7d | ||
|
|
9abc1eb921 | ||
|
|
94325359d7 | ||
|
|
585094a719 | ||
|
|
4b79c597dc | ||
|
|
1bc8ec59aa | ||
|
|
b9a350e201 | ||
|
|
cd80c749d6 | ||
|
|
06fdde8f1e | ||
|
|
9f259bda27 | ||
|
|
d7a9940973 | ||
|
|
db92d5bfff | ||
|
|
246f7e9395 | ||
|
|
b5549bf00c | ||
|
|
7fe138330e | ||
|
|
3488fd7c7f | ||
|
|
a3aab00ca9 | ||
|
|
d10bc1bb14 | ||
|
|
18a88fcecf | ||
|
|
c92f520423 | ||
|
|
00d73a9389 | ||
|
|
ddf67ff948 | ||
|
|
9b32ebdae4 | ||
|
|
0ec2291bd7 | ||
|
|
026bf02c85 | ||
|
|
42ebc7e995 | ||
|
|
b4b6366ba8 | ||
|
|
7b740f5e9a | ||
|
|
cb328d6cea | ||
|
|
b57b8cfb66 | ||
|
|
cb1c68a36e | ||
|
|
29138cc533 | ||
|
|
c09dd9287c | ||
|
|
65b47d4613 | ||
|
|
aab2d01a7c | ||
|
|
f748a4bb50 | ||
|
|
e4b19d0cb5 | ||
|
|
eeefbe57af | ||
|
|
84066634e7 | ||
|
|
9e82ba60b4 | ||
|
|
96a74d9ef0 | ||
|
|
1ebcd3a0fb | ||
|
|
7565c547ae | ||
|
|
8f9101773c | ||
|
|
17fcfd4e41 | ||
|
|
9e6df7a4dc | ||
|
|
9319210c4c | ||
|
|
43fa27887d | ||
|
|
58f1c3234d | ||
|
|
715924cac6 | ||
|
|
163ca3b58a | ||
|
|
1c2916052d | ||
|
|
b76da25ef1 | ||
|
|
7f35178c70 | ||
|
|
212e1f80f5 | ||
|
|
a2c9e369c3 | ||
|
|
78f12de997 | ||
|
|
8ec7cfa700 | ||
|
|
bc8f64bb98 | ||
|
|
c7cc716e57 | ||
|
|
578a3b4ef4 | ||
|
|
cc671dfad3 | ||
|
|
86b915d42e | ||
|
|
738127525f | ||
|
|
6485ef480b | ||
|
|
9af7d4ad06 | ||
|
|
32741162d9 | ||
|
|
3da982f073 | ||
|
|
a021f62e96 | ||
|
|
80e21560eb | ||
|
|
e38b98c81a | ||
|
|
9c0221a0fa | ||
|
|
afbed7dbd7 | ||
|
|
05083e32c9 | ||
|
|
c6bd8c1221 | ||
|
|
63c7844a27 | ||
|
|
762032ebbc | ||
|
|
4a64e8da83 | ||
|
|
5c6af99f11 | ||
|
|
6ac1a4a353 | ||
|
|
330ff96ee2 | ||
|
|
a90995cf15 | ||
|
|
84693b95ec | ||
|
|
aa9921c6f5 | ||
|
|
d653618b0a | ||
|
|
dc9744448c | ||
|
|
ed9c06583a | ||
|
|
ff92adf3e0 | ||
|
|
bf95c03847 | ||
|
|
e0641df727 | ||
|
|
f01c47e0ec | ||
|
|
faf46fcf60 | ||
|
|
687a99279d | ||
|
|
25e09815be | ||
|
|
02cb43180c | ||
|
|
b32310b8a6 | ||
|
|
556a1a5ef2 | ||
|
|
36467c1e3a | ||
|
|
d4b334636e | ||
|
|
7653c5fa60 | ||
|
|
bd61cd3142 | ||
|
|
1a0f7221d9 | ||
|
|
7303c726f9 | ||
|
|
44bfc79caa | ||
|
|
6f8ffc0357 | ||
|
|
bb45a5f67e | ||
|
|
baa0a5b8af | ||
|
|
9081556e1d | ||
|
|
1b14f55c3c | ||
|
|
89a5e34414 | ||
|
|
e2bc7d1307 | ||
|
|
768cf7e1ae | ||
|
|
d87a88e39f | ||
|
|
314c00a8b7 | ||
|
|
16ed62d548 | ||
|
|
2b2810511d | ||
|
|
e07859fb3c | ||
|
|
9b034a2eb0 | ||
|
|
bd9652b24c | ||
|
|
8b5f09305c | ||
|
|
6d033f2964 | ||
|
|
0bcac1882a | ||
|
|
b6b04aeff8 | ||
|
|
a69aed80e6 | ||
|
|
e81f972270 | ||
|
|
542590db7c | ||
|
|
e04aae94bc | ||
|
|
aa18667905 | ||
|
|
addb27a085 | ||
|
|
be10d5200f | ||
|
|
e8348612b4 | ||
|
|
3a160a4dce | ||
|
|
ccd20f0172 | ||
|
|
c03bc8540c | ||
|
|
003acb7254 | ||
|
|
b961b683d6 | ||
|
|
f82ef95866 | ||
|
|
ed9000bb07 | ||
|
|
6afe2b124d | ||
|
|
629d23d832 | ||
|
|
d837feca71 | ||
|
|
a7dade979c | ||
|
|
3a2caf61e5 | ||
|
|
b5ed16088a | ||
|
|
e4a20b9e72 | ||
|
|
37e5fe786f | ||
|
|
643995528b | ||
|
|
ecd17f2ea2 | ||
|
|
bf0bf2c1b6 | ||
|
|
acedf362b6 | ||
|
|
3b580eeca7 | ||
|
|
6adfff1f13 | ||
|
|
7d542d7989 | ||
|
|
b6a8195cb0 | ||
|
|
cc21d175f1 | ||
|
|
ade4b198cf | ||
|
|
32b65f576f | ||
|
|
f2bbb90d52 | ||
|
|
ffbf06755f | ||
|
|
58716bb406 | ||
|
|
782d68afa8 | ||
|
|
1c1f291778 | ||
|
|
b754eacd74 | ||
|
|
7ce8a6a201 | ||
|
|
789a2a7ae3 | ||
|
|
be4fc6b887 | ||
|
|
2dae31486a | ||
|
|
71f66c6229 | ||
|
|
b4f926ded7 | ||
|
|
438a445353 | ||
|
|
6a04fe8ab6 | ||
|
|
ca7bdf245e | ||
|
|
c45d2212a5 | ||
|
|
66bfccc738 | ||
|
|
ae7eddf7c9 | ||
|
|
36a59f09ac | ||
|
|
5869b93acb | ||
|
|
b15eb27aa9 | ||
|
|
336b64a569 | ||
|
|
6a49e787bb | ||
|
|
0c5f4a1525 | ||
|
|
6ef9f3cc26 | ||
|
|
72be80cbd9 | ||
|
|
99a27d19b9 | ||
|
|
877fd7abb9 | ||
|
|
363e62f8fa | ||
|
|
9a3aa55b29 | ||
|
|
ab9897b397 | ||
|
|
30829d0669 | ||
|
|
c18611d4af | ||
|
|
5eff8608fe | ||
|
|
c25a56b2be | ||
|
|
3a4ca5e190 | ||
|
|
d1d1f9bb58 | ||
|
|
0c81d25e96 | ||
|
|
90ace3fedc | ||
|
|
3a0ccf3697 | ||
|
|
ea1e42cb83 | ||
|
|
4e0997dbdf | ||
|
|
50b4fdc03d | ||
|
|
57084fbd3e | ||
|
|
4478399282 | ||
|
|
53abf5a316 | ||
|
|
9ce2491d67 | ||
|
|
ec637217f2 | ||
|
|
91a5395e1a | ||
|
|
dac1c9b413 | ||
|
|
c21e4f5982 | ||
|
|
478281d853 | ||
|
|
08d4c56886 | ||
|
|
4987add452 | ||
|
|
6a9f155856 | ||
|
|
407864c40e | ||
|
|
03c0d8ba5a | ||
|
|
76c7ab499f | ||
|
|
dcc84d3508 | ||
|
|
8ae18d9935 | ||
|
|
013e08f80a | ||
|
|
33dd60107d | ||
|
|
2fc76c2b24 | ||
|
|
923abd8d0b | ||
|
|
dc6c638f34 | ||
|
|
2081a265b1 | ||
|
|
a72c3ba8f0 | ||
|
|
363a865263 | ||
|
|
b40e753696 | ||
|
|
58e99184bb | ||
|
|
79ba95ce04 | ||
|
|
93319a6a61 | ||
|
|
7fb0a82e69 | ||
|
|
e9e6b85bd9 | ||
|
|
3b5966bba0 | ||
|
|
7fc592cb22 | ||
|
|
c7299dec78 | ||
|
|
fce0a50e37 | ||
|
|
c047232c3b | ||
|
|
a846f0276d | ||
|
|
e2cb3aa078 | ||
|
|
801098f546 | ||
|
|
4fc8936553 | ||
|
|
dd0135ce2e | ||
|
|
d830a1c5f7 | ||
|
|
403df7cebc | ||
|
|
45bc034261 | ||
|
|
bbbead5769 | ||
|
|
fca7b32405 | ||
|
|
d558af31c9 | ||
|
|
5131463644 | ||
|
|
9e8498ad4e | ||
|
|
3777ae4624 | ||
|
|
e896c41ef9 | ||
|
|
9d8b8be8d2 | ||
|
|
1c15b97b11 | ||
|
|
8017ee45f5 | ||
|
|
8c1e3cabe0 | ||
|
|
0c6279f86a | ||
|
|
1cffa3b731 | ||
|
|
549acb0785 | ||
|
|
a143b42c2a | ||
|
|
e85ad9de6a | ||
|
|
0ab5bbb08e | ||
|
|
4efa282f80 | ||
|
|
d53cfbd9fb | ||
|
|
94986877d8 | ||
|
|
f3bc2f5e87 | ||
|
|
a5365a6cce | ||
|
|
b9ad82ceeb | ||
|
|
fe959fe0d5 | ||
|
|
6421397083 | ||
|
|
52ae354ca2 | ||
|
|
c6ff66be79 | ||
|
|
73af9b1cac | ||
|
|
cdaee7b933 | ||
|
|
6adbd9bd50 | ||
|
|
6acf3871e4 | ||
|
|
ead2131ba2 | ||
|
|
cf1532acf1 | ||
|
|
17ca7ab5db | ||
|
|
c42332a5ee | ||
|
|
c736f48ac9 | ||
|
|
06b251063e | ||
|
|
cb7b2a3dc6 | ||
|
|
5ac203ebd8 | ||
|
|
6135eb26ad | ||
|
|
d18e2ba339 | ||
|
|
7ed03f1f29 | ||
|
|
5301477747 | ||
|
|
73667a5367 | ||
|
|
09c282d9ea | ||
|
|
f9f1b49298 | ||
|
|
f5ff69f8b3 | ||
|
|
1e43c29484 | ||
|
|
a15cad0088 | ||
|
|
8e8858178b | ||
|
|
528352ba82 | ||
|
|
e54f127a06 | ||
|
|
d9fd9cfef2 | ||
|
|
9618500975 | ||
|
|
25076f2ddc | ||
|
|
66912071a8 | ||
|
|
e9438549f4 | ||
|
|
eff75a2059 | ||
|
|
d001d9d1a3 | ||
|
|
5c3f71c097 | ||
|
|
7f764f8108 | ||
|
|
2dcf594fc6 | ||
|
|
076469f385 | ||
|
|
e178eeb7f5 | ||
|
|
a8cf6e0443 | ||
|
|
725458b63c | ||
|
|
9a11bef263 | ||
|
|
b184cba953 | ||
|
|
8b393ce792 | ||
|
|
f801ffe4f7 | ||
|
|
b293a5b818 | ||
|
|
beffd726bd | ||
|
|
41f6c9e52d | ||
|
|
ce71673824 | ||
|
|
e05f187fcf | ||
|
|
04b5754653 | ||
|
|
44bc3692fd | ||
|
|
3a310f93e0 | ||
|
|
706769026d | ||
|
|
704e34fa99 | ||
|
|
a6fae36cb2 | ||
|
|
4c791abafc | ||
|
|
11e14f1726 | ||
|
|
0273689ca9 | ||
|
|
05a56c2e8e | ||
|
|
325dd8732e | ||
|
|
4d5dd0f49c | ||
|
|
39d85fd008 | ||
|
|
a12164ad53 | ||
|
|
805b85a4d7 | ||
|
|
cacb300ffd | ||
|
|
15e36c535f | ||
|
|
b5e51c7e34 | ||
|
|
e4482ef675 | ||
|
|
09b3be25fc | ||
|
|
7235db2793 | ||
|
|
35005e46b4 | ||
|
|
abd50022d1 | ||
|
|
e9c12c32d2 | ||
|
|
8950417d37 | ||
|
|
8c6e2f84c1 | ||
|
|
d0aec7696a | ||
|
|
a9da2a2277 | ||
|
|
4f1e4e149f | ||
|
|
bd25144390 | ||
|
|
3d1fc20d01 | ||
|
|
921b144d06 | ||
|
|
12d306fa85 | ||
|
|
3b750895b4 | ||
|
|
8c334a1f43 | ||
|
|
6db3b2fb78 | ||
|
|
5775ec1ff1 | ||
|
|
32bb31a417 | ||
|
|
87844f5ff4 | ||
|
|
19491ff85f | ||
|
|
8546d53b05 | ||
|
|
dfb20586ce | ||
|
|
5783c406a2 | ||
|
|
6c56811636 | ||
|
|
08c7be5350 | ||
|
|
ade9c6ce72 | ||
|
|
fdefb19bdb | ||
|
|
ade50d0b92 | ||
|
|
09bae4d6e1 | ||
|
|
0c30d9cfe8 | ||
|
|
94dae8d535 | ||
|
|
e8fc3ecf28 | ||
|
|
b3a03e9c58 | ||
|
|
097b923871 | ||
|
|
f422a63200 | ||
|
|
eec460a32d | ||
|
|
5ccea62ba6 | ||
|
|
529beb328a | ||
|
|
796cc8cd28 | ||
|
|
c8b4145214 | ||
|
|
04a9f0313a | ||
|
|
6166961804 | ||
|
|
4ddc606361 | ||
|
|
836c748cd9 | ||
|
|
3bee0bcf04 | ||
|
|
0d349d54b4 | ||
|
|
29123f4d9d | ||
|
|
a16c1a8957 | ||
|
|
c1cd308940 | ||
|
|
4bba498229 | ||
|
|
40b2fc8848 | ||
|
|
6bc13fcab1 | ||
|
|
c3cb7dfadd | ||
|
|
7750d2198d | ||
|
|
3ced2a6ea3 | ||
|
|
f993e7c555 | ||
|
|
35df5691f2 | ||
|
|
6b71e4ec6f | ||
|
|
9c199cc753 | ||
|
|
57f9b8159f | ||
|
|
a94e116926 | ||
|
|
75444e44b9 | ||
|
|
dbfa5dc786 | ||
|
|
17f39bd09d | ||
|
|
7c8e650c5a | ||
|
|
eaddc55267 | ||
|
|
f6b6765424 | ||
|
|
f3ab9d5fef | ||
|
|
998354b5ab | ||
|
|
852d39e271 | ||
|
|
7deb74cc22 | ||
|
|
ba6d1ab886 | ||
|
|
c72aa457b3 | ||
|
|
6ed6a5e8ea | ||
|
|
fd7ba861a1 | ||
|
|
b25aa99de7 | ||
|
|
dddb233f16 | ||
|
|
04dd02c295 | ||
|
|
3f495a30e0 | ||
|
|
37f74291b5 | ||
|
|
361c5a5a54 | ||
|
|
4114fe68c9 | ||
|
|
4dddf88569 | ||
|
|
7f19d5669b | ||
|
|
c6705ecd9c | ||
|
|
28da954aad | ||
|
|
a8fbbc65c1 | ||
|
|
47ffcfd561 | ||
|
|
481d8d3a0b | ||
|
|
ace142bb23 | ||
|
|
ea14baff6c | ||
|
|
0c4c5c5f8b | ||
|
|
0a407a39b0 | ||
|
|
3ad1c0e7a8 | ||
|
|
a140720901 | ||
|
|
141d31c546 | ||
|
|
f293dbfeeb | ||
|
|
3f533edba6 | ||
|
|
10a5e75cd8 | ||
|
|
58349fae02 | ||
|
|
3269402f48 | ||
|
|
26f7848821 | ||
|
|
08efb40599 | ||
|
|
f81e5be119 | ||
|
|
cf4fcebc53 | ||
|
|
00db94a73c | ||
|
|
a8ecf486d1 | ||
|
|
83918619cf | ||
|
|
f52c330c22 | ||
|
|
67ed54671b | ||
|
|
fc6b895f52 | ||
|
|
6f9256f290 | ||
|
|
f6b03f0186 | ||
|
|
555c513acb | ||
|
|
96da4674f9 | ||
|
|
e5f0ee3b92 | ||
|
|
570041e3b4 | ||
|
|
c0e7120b12 | ||
|
|
82a73e443c | ||
|
|
5d3a8e3725 | ||
|
|
8f616969ab | ||
|
|
cb60e8aa90 | ||
|
|
b9f0e444e8 | ||
|
|
cd89efc24b | ||
|
|
8ebacae846 | ||
|
|
7a033395f2 | ||
|
|
dd90ed9643 | ||
|
|
d392843c12 | ||
|
|
e944aa6f2d | ||
|
|
08c76f5997 | ||
|
|
4c2895c92f | ||
|
|
d8dca83dd5 | ||
|
|
0ff91574d2 | ||
|
|
30cff4e4f8 | ||
|
|
1138118d8e | ||
|
|
be2c1fdb89 | ||
|
|
030e85c06c | ||
|
|
b493f03d42 | ||
|
|
4bfd2a5d77 | ||
|
|
4945dd126a | ||
|
|
8caa997181 | ||
|
|
2b88ea390c | ||
|
|
31f3b322c8 | ||
|
|
55ebd5154c | ||
|
|
dcc33f3417 | ||
|
|
eab8c1eb7e | ||
|
|
5a9fc02f07 | ||
|
|
9d6780d6c1 | ||
|
|
811c3c995a | ||
|
|
44ed5744f1 | ||
|
|
179bbffd76 | ||
|
|
207de071f4 | ||
|
|
0845f25f70 | ||
|
|
916d414543 | ||
|
|
b93fbddf05 | ||
|
|
b40e3e7b4e | ||
|
|
c46ca5c256 | ||
|
|
44d954ade9 | ||
|
|
cdc939ae97 | ||
|
|
a2a753a34b | ||
|
|
d0e25c230a | ||
|
|
82a67a44c4 | ||
|
|
233a423e8c | ||
|
|
5e099adb2f | ||
|
|
a35b346e62 | ||
|
|
7d4406254a | ||
|
|
70091726c9 | ||
|
|
564088680a | ||
|
|
1eb61b7949 | ||
|
|
f6bac6cfdd | ||
|
|
e8c8fe4223 | ||
|
|
3659bca0ec | ||
|
|
ace3da841c | ||
|
|
457b0c3ab1 | ||
|
|
544229d1e8 | ||
|
|
af05db895c | ||
|
|
2958cf2180 | ||
|
|
32c114bd17 | ||
|
|
1d96b102c0 | ||
|
|
d6fce49162 | ||
|
|
ff5bf8634f | ||
|
|
f49cd1850c | ||
|
|
d420242fcb | ||
|
|
c873da75e4 | ||
|
|
9b60573d7e | ||
|
|
907f36bfcb | ||
|
|
04fedf83b4 | ||
|
|
f3d774e2e5 | ||
|
|
8f40161fa2 | ||
|
|
febc035063 | ||
|
|
4902751d02 | ||
|
|
45bd869a15 | ||
|
|
905c1532fe | ||
|
|
c3d8657619 | ||
|
|
51ff031de5 | ||
|
|
6b9395a3c5 | ||
|
|
b1db012094 | ||
|
|
556e8d39aa | ||
|
|
cd5ff96904 | ||
|
|
24a20ec758 | ||
|
|
4edab98b58 | ||
|
|
dd130ede8f | ||
|
|
81bcb46ef2 | ||
|
|
37f532c1f0 | ||
|
|
04f2bd4baa | ||
|
|
07df659a3f | ||
|
|
e89ff9ae61 | ||
|
|
c662ff1902 | ||
|
|
a3825080db | ||
|
|
a237493def | ||
|
|
3a0cd45782 | ||
|
|
f08cc08eb2 | ||
|
|
e3685b951c | ||
|
|
6b04e2f77b | ||
|
|
2e8b7771b0 | ||
|
|
26e98d35e6 | ||
|
|
ab3d0141ec | ||
|
|
39df36c247 | ||
|
|
7473cdf184 | ||
|
|
4f7d7e3601 | ||
|
|
49a6dc311e | ||
|
|
c585e81530 | ||
|
|
c779098772 | ||
|
|
cc07ed1ee8 | ||
|
|
222b2d8645 | ||
|
|
f41eeaf6ec | ||
|
|
48097801e8 | ||
|
|
dca83aad45 | ||
|
|
be7a524557 | ||
|
|
0827ff0995 | ||
|
|
503b9de2a0 | ||
|
|
a2d47cdec4 | ||
|
|
ba74c24d8f | ||
|
|
d60a216982 | ||
|
|
d6af025a46 | ||
|
|
82fa10c227 | ||
|
|
13b2b5253e | ||
|
|
46e0a05078 | ||
|
|
8329de4cee | ||
|
|
b4dee67bf5 | ||
|
|
5ae3435fe6 | ||
|
|
9a256fcbfe | ||
|
|
112d2bfe11 | ||
|
|
69a11a7ec1 | ||
|
|
3960ffea3f | ||
|
|
78543deee4 | ||
|
|
4dd49327e4 | ||
|
|
2121e7116e | ||
|
|
67107a4f5d | ||
|
|
0557d53f5f | ||
|
|
819a006a17 | ||
|
|
65ee5915af | ||
|
|
8ad3b18932 | ||
|
|
c835c39491 | ||
|
|
4cb45d5d6a | ||
|
|
b28ae98d58 | ||
|
|
c389985c2e | ||
|
|
ac24874585 | ||
|
|
a9bea53c89 | ||
|
|
0f7c10a2d6 | ||
|
|
18f51e47d7 | ||
|
|
aba4b722af | ||
|
|
654049d4fd | ||
|
|
cea0d519a4 | ||
|
|
ff7b0aace9 | ||
|
|
9b476a5caa | ||
|
|
2c58bee151 | ||
|
|
c4ffd844f3 | ||
|
|
cbc19e86fb | ||
|
|
10073c1f10 | ||
|
|
8a35bfdc13 | ||
|
|
ddf1ac04e5 | ||
|
|
aa388b86c0 | ||
|
|
f9c6449c05 | ||
|
|
3c00c09ea8 |
63
.drone.yml
@@ -22,11 +22,11 @@ steps:
|
|||||||
source /opt/qt57/bin/qt57-env.sh &&
|
source /opt/qt57/bin/qt57-env.sh &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ctest --output-on-failure' test"
|
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -59,11 +59,11 @@ steps:
|
|||||||
source /opt/qt58/bin/qt58-env.sh &&
|
source /opt/qt58/bin/qt58-env.sh &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ctest --output-on-failure' test"
|
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -96,11 +96,11 @@ steps:
|
|||||||
source /opt/qt59/bin/qt59-env.sh &&
|
source /opt/qt59/bin/qt59-env.sh &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ctest --output-on-failure' test"
|
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -137,11 +137,11 @@ steps:
|
|||||||
source /opt/qt510/bin/qt510-env.sh &&
|
source /opt/qt510/bin/qt510-env.sh &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ctest --output-on-failure' test"
|
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -178,11 +178,11 @@ steps:
|
|||||||
source /opt/qt511/bin/qt511-env.sh &&
|
source /opt/qt511/bin/qt511-env.sh &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ctest --output-on-failure' test"
|
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -219,11 +219,11 @@ steps:
|
|||||||
source /opt/qt511/bin/qt511-env.sh &&
|
source /opt/qt511/bin/qt511-env.sh &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ctest --output-on-failure' test"
|
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -237,13 +237,17 @@ name: qt-5.12
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build and test
|
- name: build and test
|
||||||
image: nextcloudci/client-5.12:client-5.12-2
|
image: nextcloudci/client-5.12:client-5.12-5
|
||||||
commands:
|
commands:
|
||||||
# Install QtKeyChain
|
# Install QtKeyChain
|
||||||
- /bin/bash -c "
|
- /bin/bash -c "
|
||||||
export CC=gcc-7 &&
|
export CC=gcc-7 &&
|
||||||
export CXX=g++-7 &&
|
export CXX=g++-7 &&
|
||||||
source /opt/qt512/bin/qt512-env.sh &&
|
export QT_BASE_DIR=/opt/qt5.12.5 &&
|
||||||
|
export QTDIR=\$QT_BASE_DIR &&
|
||||||
|
export PATH=\$QT_BASE_DIR/bin:\$PATH &&
|
||||||
|
export LD_LIBRARY_PATH=\$QT_BASE_DIR/lib/x86_64-linux-gnu:\$QT_BASE_DIR/lib:/usr/local/lib:\$LD_LIBRARY_PATH &&
|
||||||
|
export PKG_CONFIG_PATH=\$QT_BASE_DIR/lib/pkgconfig:\$PKG_CONFIG_PATH &&
|
||||||
cd /tmp &&
|
cd /tmp &&
|
||||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||||
cd qtkeychain &&
|
cd qtkeychain &&
|
||||||
@@ -257,14 +261,18 @@ steps:
|
|||||||
- /bin/bash -c "
|
- /bin/bash -c "
|
||||||
export CC=gcc-7 &&
|
export CC=gcc-7 &&
|
||||||
export CXX=g++-7 &&
|
export CXX=g++-7 &&
|
||||||
source /opt/qt512/bin/qt512-env.sh &&
|
export QT_BASE_DIR=/opt/qt5.12.5 &&
|
||||||
|
export QTDIR=\$QT_BASE_DIR &&
|
||||||
|
export PATH=\$QT_BASE_DIR/bin:\$PATH &&
|
||||||
|
export LD_LIBRARY_PATH=\$QT_BASE_DIR/lib/x86_64-linux-gnu:\$QT_BASE_DIR/lib:/usr/local/lib:\$LD_LIBRARY_PATH &&
|
||||||
|
export PKG_CONFIG_PATH=\$QT_BASE_DIR/lib/pkgconfig:\$PKG_CONFIG_PATH &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ctest --output-on-failure' test"
|
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -278,13 +286,17 @@ name: qt-5.12-clang
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build and test
|
- name: build and test
|
||||||
image: nextcloudci/client-5.12:client-5.12-2
|
image: nextcloudci/client-5.12:client-5.12-5
|
||||||
commands:
|
commands:
|
||||||
# Install QtKeyChain
|
# Install QtKeyChain
|
||||||
- /bin/bash -c "
|
- /bin/bash -c "
|
||||||
export CC=clang-6.0 &&
|
export CC=clang-6.0 &&
|
||||||
export CXX=clang++-6.0 &&
|
export CXX=clang++-6.0 &&
|
||||||
source /opt/qt512/bin/qt512-env.sh &&
|
export QT_BASE_DIR=/opt/qt5.12.5 &&
|
||||||
|
export QTDIR=\$QT_BASE_DIR &&
|
||||||
|
export PATH=\$QT_BASE_DIR/bin:\$PATH &&
|
||||||
|
export LD_LIBRARY_PATH=\$QT_BASE_DIR/lib/x86_64-linux-gnu:\$QT_BASE_DIR/lib:/usr/local/lib:\$LD_LIBRARY_PATH &&
|
||||||
|
export PKG_CONFIG_PATH=\$QT_BASE_DIR/lib/pkgconfig:\$PKG_CONFIG_PATH &&
|
||||||
cd /tmp &&
|
cd /tmp &&
|
||||||
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
git clone https://github.com/frankosterfeld/qtkeychain.git &&
|
||||||
cd qtkeychain &&
|
cd qtkeychain &&
|
||||||
@@ -298,14 +310,18 @@ steps:
|
|||||||
- /bin/bash -c "
|
- /bin/bash -c "
|
||||||
export CC=clang-6.0 &&
|
export CC=clang-6.0 &&
|
||||||
export CXX=clang++-6.0 &&
|
export CXX=clang++-6.0 &&
|
||||||
source /opt/qt512/bin/qt512-env.sh &&
|
export QT_BASE_DIR=/opt/qt5.12.5 &&
|
||||||
|
export QTDIR=\$QT_BASE_DIR &&
|
||||||
|
export PATH=\$QT_BASE_DIR/bin:\$PATH &&
|
||||||
|
export LD_LIBRARY_PATH=\$QT_BASE_DIR/lib/x86_64-linux-gnu:\$QT_BASE_DIR/lib:/usr/local/lib:\$LD_LIBRARY_PATH &&
|
||||||
|
export PKG_CONFIG_PATH=\$QT_BASE_DIR/lib/pkgconfig:\$PKG_CONFIG_PATH &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ctest --output-on-failure' test"
|
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -319,9 +335,10 @@ name: AppImage
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build
|
- name: build
|
||||||
image: nextcloudci/client-5.12:client-5.12-2
|
image: nextcloudci/client-5.12:client-5.12-5
|
||||||
commands:
|
commands:
|
||||||
- /bin/bash -c "./admin/linux/build-appimage.sh"
|
- /bin/bash -c "./admin/linux/build-appimage.sh"
|
||||||
|
- /bin/bash -c "./admin/linux/upload-appimage.sh"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
|
|||||||
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
# You can add one username per supported platform and one custom link
|
||||||
|
custom: https://www.bountysource.com/teams/nextcloud/issues?tracker_ids=74294474
|
||||||
67
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
|
||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 28
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 14
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- 1. to develop
|
||||||
|
- 2. to review
|
||||||
|
- 3. to release
|
||||||
|
- 4. to test
|
||||||
|
- accessibility
|
||||||
|
- backport-request
|
||||||
|
- bug
|
||||||
|
- design
|
||||||
|
- enhancement
|
||||||
|
- epic
|
||||||
|
- discussion
|
||||||
|
- documentation
|
||||||
|
- overview
|
||||||
|
- good first issue
|
||||||
|
- feature-request
|
||||||
|
- feature: :arrows_counterclockwise: sync engine
|
||||||
|
- feature: :busts_in_silhouette: sharing
|
||||||
|
- feature: :cloud: system tray
|
||||||
|
- feature: :gear: settings
|
||||||
|
- feature: :inbox_tray: install and update
|
||||||
|
- feature: :key: authentication
|
||||||
|
- feature: :lock: end to end encryption
|
||||||
|
- feature: :memo: versions
|
||||||
|
- feature: :minidisc: external storage
|
||||||
|
- feature: :minidisc: virtual drive
|
||||||
|
- feature: :new: versions
|
||||||
|
- feature: :tongue: language l10n and translations
|
||||||
|
- feature: :wheelchair: accessibility
|
||||||
|
- feature: :white_square_button: nextcloudcmd
|
||||||
|
- feature: :zap: activity and :bell: notification
|
||||||
|
- good first issue
|
||||||
|
- help wanted
|
||||||
|
- high
|
||||||
|
- integration
|
||||||
|
- low
|
||||||
|
- medium
|
||||||
|
- needs info
|
||||||
|
- os: :apple: macOS
|
||||||
|
- os: :door: Windows
|
||||||
|
- os: :penguin: Linux
|
||||||
|
- os: :smiling_imp: FreeBSD
|
||||||
|
- overview
|
||||||
|
- package: appimage
|
||||||
|
- package: debian
|
||||||
|
- package: snap
|
||||||
|
- papercut
|
||||||
|
- regression
|
||||||
|
- security
|
||||||
|
- server
|
||||||
|
- spec
|
||||||
|
- technical debt
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: stale
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This request did not receive an update in the last 4 weeks.
|
||||||
|
Please take a look again and update the issue with new details,
|
||||||
|
otherwise the issue will be automatically closed in 2 weeks. Thank you!
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
||||||
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
|||||||
[submodule "src/3rdparty/qtmacgoodies"]
|
|
||||||
path = src/3rdparty/qtmacgoodies
|
|
||||||
url = https://github.com/camilasan/qtmacgoodies.git
|
|
||||||
[submodule "binary"]
|
[submodule "binary"]
|
||||||
path = binary
|
path = binary
|
||||||
url = git://github.com/owncloud/owncloud-client-binary.git
|
url = git://github.com/owncloud/owncloud-client-binary.git
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ X-GNOME-Autostart-Delay=3
|
|||||||
|
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[cs_CZ]=@NAZEV_IKONY_APLIKACE@
|
Icon[cs_CZ]=@APPLICATION_ICON_NAME@
|
||||||
Name[cs_CZ]=@APPLICATION_NAME@ synchronizační klient pro desktop
|
Name[cs_CZ]=@APPLICATION_NAME@ synchronizační klient pro desktop
|
||||||
Comment[cs_CZ]=@APPLICATION_NAME@ synchronizační klient pro desktop
|
Comment[cs_CZ]=@APPLICATION_NAME@ synchronizační klient pro desktop
|
||||||
GenericName[cs_CZ]=Synchronizace složek
|
GenericName[cs_CZ]=Synchronizace složek
|
||||||
|
|||||||
204
.tx/nextcloud.client-desktop/cy_GB_translation
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Categories=Utility;X-SuSE-SyncUtility;
|
||||||
|
Type=Application
|
||||||
|
Exec=@APPLICATION_EXECUTABLE@
|
||||||
|
Name=@APPLICATION_NAME@ desktop sync client
|
||||||
|
Comment=@APPLICATION_NAME@ desktop synchronization client
|
||||||
|
GenericName=Folder Sync
|
||||||
|
Icon=@APPLICATION_ICON_NAME@
|
||||||
|
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||||
|
X-GNOME-Autostart-Delay=3
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
Icon[cy_GB]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[cy_GB]=@APPLICATION_NAME@ cleient cydweddu bwrdd gwaith
|
||||||
|
Comment[cy_GB]=@APPLICATION_NAME@ cleient cydweddu bwrdd gwaith
|
||||||
|
GenericName[cy_GB]=Cydweddu Ffolder
|
||||||
@@ -198,7 +198,7 @@ X-GNOME-Autostart-Delay=3
|
|||||||
|
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[de]=@APPLICATION_ICON_NAME@
|
Icon[de_DE]=@APPLICATION_ICON_NAME@
|
||||||
Name[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
|
Name[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
|
||||||
Comment[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
|
Comment[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
|
||||||
GenericName[de]=Synchronisationsordner
|
GenericName[de_DE]=Synchronisationsordner
|
||||||
|
|||||||
@@ -198,4 +198,7 @@ X-GNOME-Autostart-Delay=3
|
|||||||
|
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
|
Icon[el]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[el]=@APPLICATION_NAME@ πρόγραμμα συγχρονισμού
|
||||||
|
Comment[el]=@APPLICATION_NAME@ πρόγραμμα συγχρονισμού
|
||||||
GenericName[el]=Συγχρονισμός φακέλου
|
GenericName[el]=Συγχρονισμός φακέλου
|
||||||
|
|||||||
204
.tx/nextcloud.client-desktop/es_AR_translation
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Categories=Utility;X-SuSE-SyncUtility;
|
||||||
|
Type=Application
|
||||||
|
Exec=@APPLICATION_EXECUTABLE@
|
||||||
|
Name=@APPLICATION_NAME@ desktop sync client
|
||||||
|
Comment=@APPLICATION_NAME@ desktop synchronization client
|
||||||
|
GenericName=Folder Sync
|
||||||
|
Icon=@APPLICATION_ICON_NAME@
|
||||||
|
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||||
|
X-GNOME-Autostart-Delay=3
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
Icon[es_AR]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[es_AR]=@APPLICATION_NAME@ cliente de sincronización de escritorio
|
||||||
|
Comment[es_AR]=@APPLICATION_NAME@ cliente de sincronización de escritorio
|
||||||
|
GenericName[es_AR]=Sincronización de carpetas
|
||||||
@@ -201,4 +201,4 @@ X-GNOME-Autostart-Delay=3
|
|||||||
Icon[ro]=@APPLICATION_ICON_NAME@
|
Icon[ro]=@APPLICATION_ICON_NAME@
|
||||||
Name[ro]=@APPLICATION_NAME@ client de sincronizare pentru desktop
|
Name[ro]=@APPLICATION_NAME@ client de sincronizare pentru desktop
|
||||||
Comment[ro]=@APPLICATION_NAME@ client de sincronizare pentru desktop
|
Comment[ro]=@APPLICATION_NAME@ client de sincronizare pentru desktop
|
||||||
GenericName[ro]=Sincronizare dosare
|
GenericName[ro]=Sincronizare director
|
||||||
|
|||||||
@@ -201,4 +201,4 @@ X-GNOME-Autostart-Delay=3
|
|||||||
Icon[sk_SK]=@APPLICATION_ICON_NAME@
|
Icon[sk_SK]=@APPLICATION_ICON_NAME@
|
||||||
Name[sk_SK]=@APPLICATION_NAME@ synchronizačný klient pre PC
|
Name[sk_SK]=@APPLICATION_NAME@ synchronizačný klient pre PC
|
||||||
Comment[sk_SK]=@APPLICATION_NAME@ synchronizačný klient pre PC
|
Comment[sk_SK]=@APPLICATION_NAME@ synchronizačný klient pre PC
|
||||||
GenericName[sk_SK]=Synchnonizácia priečinkov
|
GenericName[sk_SK]=Synchronizácia priečinkov
|
||||||
|
|||||||
@@ -199,6 +199,6 @@ X-GNOME-Autostart-Delay=3
|
|||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[sl]=@APPLICATION_ICON_NAME@
|
Icon[sl]=@APPLICATION_ICON_NAME@
|
||||||
Name[sl]=@APPLICATION_NAME@ odjemalec za usklajevanje
|
Name[sl]=@APPLICATION_NAME@ program za usklajevanje
|
||||||
Comment[sl]=@APPLICATION_NAME@ odjemalec za usklajevanje
|
Comment[sl]=@APPLICATION_NAME@ program za usklajevanje
|
||||||
GenericName[sl]=Usklajevanje map
|
GenericName[sl]=Usklajevanje map
|
||||||
|
|||||||
@@ -199,4 +199,6 @@ X-GNOME-Autostart-Delay=3
|
|||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[sv]=@APPLICATION_ICON_NAME@
|
Icon[sv]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[sv]=@APPLICATION_NAME@ desktopssynkklient
|
||||||
|
Comment[sv]=@APPLICATION_NAME@ desktopssynkroniseringsklient
|
||||||
GenericName[sv]=Mappsynkronisering
|
GenericName[sv]=Mappsynkronisering
|
||||||
|
|||||||
204
.tx/nextcloud.client-desktop/sw_translation
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Categories=Utility;X-SuSE-SyncUtility;
|
||||||
|
Type=Application
|
||||||
|
Exec=@APPLICATION_EXECUTABLE@
|
||||||
|
Name=@APPLICATION_NAME@ desktop sync client
|
||||||
|
Comment=@APPLICATION_NAME@ desktop synchronization client
|
||||||
|
GenericName=Folder Sync
|
||||||
|
Icon=@APPLICATION_ICON_NAME@
|
||||||
|
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||||
|
X-GNOME-Autostart-Delay=3
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
Icon[sw]=@APPLICATION_ICON_NAME@
|
||||||
|
Name[sw]=Teja ya @APPLICATION_NAME@ ya kufanana faili kwa seva na faili ziko hapa
|
||||||
|
Comment[sw]=Teja ya @APPLICATION_NAME@ ya kufanana faili kwa seva na faili ziko hapa
|
||||||
|
GenericName[sw]=Fanana Kabrasha
|
||||||
@@ -203,14 +203,28 @@ if( WIN32 )
|
|||||||
add_definitions( -D__USE_MINGW_ANSI_STDIO=1 )
|
add_definitions( -D__USE_MINGW_ANSI_STDIO=1 )
|
||||||
add_definitions( -DNOMINMAX )
|
add_definitions( -DNOMINMAX )
|
||||||
# Get APIs from from Vista onwards.
|
# Get APIs from from Vista onwards.
|
||||||
add_definitions( -D_WIN32_WINNT=0x0600)
|
add_definitions( -D_WIN32_WINNT=0x0601 )
|
||||||
add_definitions( -DWINVER=0x0600)
|
add_definitions( -DWINVER=0x0601 )
|
||||||
|
if( MSVC )
|
||||||
|
# Use automatic overload for suitable CRT safe-functions
|
||||||
|
# See https://docs.microsoft.com/de-de/cpp/c-runtime-library/security-features-in-the-crt?view=vs-2019
|
||||||
|
add_definitions( -D_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1 )
|
||||||
|
# Also: Disable compiler warnings because we don't use Windows CRT safe-functions explicitly and don't intend to
|
||||||
|
# as this is a pure cross-platform source the only alternative would be a ton of ifdefs with calls to the _s version
|
||||||
|
add_definitions( -D_CRT_SECURE_NO_WARNINGS )
|
||||||
|
endif( MSVC )
|
||||||
endif( WIN32 )
|
endif( WIN32 )
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
option(SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF)
|
||||||
|
if (SANITIZE_ADDRESS)
|
||||||
|
include(SanitizerFlags)
|
||||||
|
enable_sanitizer()
|
||||||
|
endif ()
|
||||||
|
|
||||||
# Handle Translations, pick all client_* files from trans directory.
|
# Handle Translations, pick all client_* files from trans directory.
|
||||||
file( GLOB TRANS_FILES ${CMAKE_SOURCE_DIR}/translations/client_*.ts)
|
file( GLOB TRANS_FILES ${CMAKE_SOURCE_DIR}/translations/client_*.ts)
|
||||||
set(TRANSLATIONS ${TRANS_FILES})
|
set(TRANSLATIONS ${TRANS_FILES})
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
Will be tracked going forward here:
|
||||||
|
https://github.com/nextcloud/desktop/releases
|
||||||
|
|
||||||
2.5 Series ChangeLog
|
2.5 Series ChangeLog
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ set( APPLICATION_UPDATE_URL "https://updates.nextcloud.org/client/" CACHE STRING
|
|||||||
set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" )
|
set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" )
|
||||||
set( APPLICATION_ICON_NAME "Nextcloud" )
|
set( APPLICATION_ICON_NAME "Nextcloud" )
|
||||||
set( APPLICATION_SERVER_URL "" CACHE STRING "URL for the server to use. If entered the server can only connect to this instance" )
|
set( APPLICATION_SERVER_URL "" CACHE STRING "URL for the server to use. If entered the server can only connect to this instance" )
|
||||||
|
set( APPLICATION_REV_DOMAIN "com.nextcloud.desktopclient" )
|
||||||
|
|
||||||
set( LINUX_PACKAGE_SHORTNAME "nextcloud" )
|
set( LINUX_PACKAGE_SHORTNAME "nextcloud" )
|
||||||
|
set( LINUX_APPLICATION_ID "${APPLICATION_REV_DOMAIN}.${LINUX_PACKAGE_SHORTNAME}")
|
||||||
|
|
||||||
set( THEME_CLASS "NextcloudTheme" )
|
set( THEME_CLASS "NextcloudTheme" )
|
||||||
set( APPLICATION_REV_DOMAIN "com.nextcloud.desktopclient" )
|
|
||||||
set( WIN_SETUP_BITMAP_PATH "${CMAKE_SOURCE_DIR}/admin/win/nsi" )
|
set( WIN_SETUP_BITMAP_PATH "${CMAKE_SOURCE_DIR}/admin/win/nsi" )
|
||||||
|
|
||||||
set( MAC_INSTALLER_BACKGROUND_FILE "${CMAKE_SOURCE_DIR}/admin/osx/installer-background.png" CACHE STRING "The MacOSX installer background image")
|
set( MAC_INSTALLER_BACKGROUND_FILE "${CMAKE_SOURCE_DIR}/admin/osx/installer-background.png" CACHE STRING "The MacOSX installer background image")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
set( MIRALL_VERSION_MAJOR 2 )
|
set( MIRALL_VERSION_MAJOR 2 )
|
||||||
set( MIRALL_VERSION_MINOR 5 )
|
set( MIRALL_VERSION_MINOR 7 )
|
||||||
set( MIRALL_VERSION_PATCH 3 )
|
set( MIRALL_VERSION_PATCH 0 )
|
||||||
set( MIRALL_VERSION_YEAR 2019 )
|
set( MIRALL_VERSION_YEAR 2020 )
|
||||||
set( MIRALL_SOVERSION 0 )
|
set( MIRALL_SOVERSION 0 )
|
||||||
|
|
||||||
if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ RUN apt-get update -q && DEBIAN_FRONTEND=noninteractive apt-get install -q -y --
|
|||||||
libsqlite3-dev \
|
libsqlite3-dev \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
libcmocka-dev \
|
libcmocka-dev \
|
||||||
|
libcloudproviders-dev \
|
||||||
qt5-default \
|
qt5-default \
|
||||||
qttools5-dev-tools \
|
qttools5-dev-tools \
|
||||||
libqt5webkit5-dev \
|
libqt5webkit5-dev \
|
||||||
|
|||||||
@@ -6,23 +6,26 @@ mkdir /app
|
|||||||
mkdir /build
|
mkdir /build
|
||||||
|
|
||||||
#Set Qt-5.12
|
#Set Qt-5.12
|
||||||
export QT_BASE_DIR=/opt/qt512
|
export QT_BASE_DIR=/opt/qt5.12.5
|
||||||
export QTDIR=$QT_BASE_DIR
|
export QTDIR=$QT_BASE_DIR
|
||||||
export PATH=$QT_BASE_DIR/bin:$PATH
|
export PATH=$QT_BASE_DIR/bin:$PATH
|
||||||
export LD_LIBRARY_PATH=$QT_BASE_DIR/lib/x86_64-linux-gnu:$QT_BASE_DIR/lib:$LD_LIBRARY_PATH
|
export LD_LIBRARY_PATH=$QT_BASE_DIR/lib/x86_64-linux-gnu:$QT_BASE_DIR/lib:$LD_LIBRARY_PATH
|
||||||
export PKG_CONFIG_PATH=$QT_BASE_DIR/lib/pkgconfig:$PKG_CONFIG_PATH
|
export PKG_CONFIG_PATH=$QT_BASE_DIR/lib/pkgconfig:$PKG_CONFIG_PATH
|
||||||
|
|
||||||
|
#Set APPID for .desktop file processing
|
||||||
|
export LINUX_APPLICATION_ID=com.nextcloud.desktopclient.nextcloud
|
||||||
|
|
||||||
#set defaults
|
#set defaults
|
||||||
export SUFFIX=${DRONE_PULL_REQUEST:=master}
|
export SUFFIX=${DRONE_PULL_REQUEST:=master}
|
||||||
if [ $SUFFIX != "master" ]; then
|
if [ $SUFFIX != "master" ]; then
|
||||||
SUFFIX="PR-$SUFFIX"
|
SUFFIX="PR-$SUFFIX"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#QtKeyChain 0.9.1
|
#QtKeyChain master
|
||||||
cd /build
|
cd /build
|
||||||
git clone https://github.com/frankosterfeld/qtkeychain.git
|
git clone https://github.com/frankosterfeld/qtkeychain.git
|
||||||
cd qtkeychain
|
cd qtkeychain
|
||||||
git checkout v0.9.1
|
git checkout master
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake -D CMAKE_INSTALL_PREFIX=/usr ../
|
cmake -D CMAKE_INSTALL_PREFIX=/usr ../
|
||||||
@@ -62,11 +65,12 @@ rm -rf ./usr/share/caja-python/
|
|||||||
rm -rf ./usr/share/nautilus-python/
|
rm -rf ./usr/share/nautilus-python/
|
||||||
rm -rf ./usr/share/nemo-python/
|
rm -rf ./usr/share/nemo-python/
|
||||||
|
|
||||||
# Move sync exlucde to right location
|
# Move sync exclude to right location
|
||||||
mv ./etc/Nextcloud/sync-exclude.lst ./usr/bin/
|
mv ./etc/Nextcloud/sync-exclude.lst ./usr/bin/
|
||||||
rm -rf ./etc
|
rm -rf ./etc
|
||||||
|
|
||||||
sed -i -e 's|Icon=nextcloud|Icon=Nextcloud|g' usr/share/applications/nextcloud.desktop # Bug in desktop file?
|
DESKTOP_FILE=/app/usr/share/applications/${LINUX_APPLICATION_ID}.desktop
|
||||||
|
sed -i -e 's|Icon=nextcloud|Icon=Nextcloud|g' ${DESKTOP_FILE} # Bug in desktop file?
|
||||||
cp ./usr/share/icons/hicolor/512x512/apps/Nextcloud.png . # Workaround for linuxeployqt bug, FIXME
|
cp ./usr/share/icons/hicolor/512x512/apps/Nextcloud.png . # Workaround for linuxeployqt bug, FIXME
|
||||||
|
|
||||||
|
|
||||||
@@ -87,17 +91,12 @@ chmod a+x linuxdeployqt*.AppImage
|
|||||||
rm ./linuxdeployqt-continuous-x86_64.AppImage
|
rm ./linuxdeployqt-continuous-x86_64.AppImage
|
||||||
unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
|
unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
|
||||||
export LD_LIBRARY_PATH=/app/usr/lib/
|
export LD_LIBRARY_PATH=/app/usr/lib/
|
||||||
./squashfs-root/AppRun /app/usr/share/applications/nextcloud.desktop -bundle-non-qt-libs
|
./squashfs-root/AppRun ${DESKTOP_FILE} -bundle-non-qt-libs -qmldir=$DRONE_WORKSPACE/src/gui
|
||||||
|
|
||||||
# Set origin
|
# Set origin
|
||||||
./squashfs-root/usr/bin/patchelf --set-rpath '$ORIGIN/' /app/usr/lib/libnextcloudsync.so.0
|
./squashfs-root/usr/bin/patchelf --set-rpath '$ORIGIN/' /app/usr/lib/libnextcloudsync.so.0
|
||||||
|
|
||||||
# Build AppImage
|
# Build AppImage
|
||||||
./squashfs-root/AppRun /app/usr/share/applications/nextcloud.desktop -appimage
|
./squashfs-root/AppRun ${DESKTOP_FILE} -appimage
|
||||||
|
|
||||||
mv Nextcloud*.AppImage Nextcloud-${SUFFIX}-${DRONE_COMMIT}-x86_64.AppImage
|
mv Nextcloud*.AppImage Nextcloud-${SUFFIX}-${DRONE_COMMIT}-x86_64.AppImage
|
||||||
|
|
||||||
curl --upload-file $(readlink -f ./Nextcloud*.AppImage) https://transfer.sh/Nextcloud-${SUFFIX}-${DRONE_COMMIT}-x86_64.AppImage
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "Get the AppImage at the link above!"
|
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ Build-Depends: cmake,
|
|||||||
cdbs,
|
cdbs,
|
||||||
dh-python,
|
dh-python,
|
||||||
extra-cmake-modules (>= 5.16),
|
extra-cmake-modules (>= 5.16),
|
||||||
kdelibs5-dev,
|
|
||||||
libkf5kio-dev,
|
libkf5kio-dev,
|
||||||
libcmocka-dev,
|
libcmocka-dev,
|
||||||
|
libcloudproviders-dev,
|
||||||
|
libdbus-1-dev,
|
||||||
libhttp-dav-perl,
|
libhttp-dav-perl,
|
||||||
libinotify-dev [kfreebsd-any],
|
libinotify-dev [kfreebsd-any],
|
||||||
libqt5svg5-dev,
|
libqt5svg5-dev,
|
||||||
|
|||||||
48
admin/linux/debian/debian.oldstable/changelog
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
nextcloud-client (2.3.3-1.0~oldstable1) oldstable; urgency=medium
|
||||||
|
|
||||||
|
* Debian build support for the forked client.
|
||||||
|
|
||||||
|
-- István Váradi <ivaradi@varadiistvan.hu> Mon, 6 Nov 2017 20:20:04 +0100
|
||||||
|
|
||||||
|
nextcloud-client (2.3.1-1.0) oldstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version
|
||||||
|
|
||||||
|
-- István Váradi <ivaradi@varadiistvan.hu> Thu, 23 Mar 2017 19:07:36 +0100
|
||||||
|
|
||||||
|
nextcloud-client (2.3.0-1.0) oldstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version
|
||||||
|
|
||||||
|
-- István Váradi <ivaradi@varadiistvan.hu> Tue, 21 Mar 2017 19:34:13 +0100
|
||||||
|
|
||||||
|
nextcloud-client (2.2.4-1.4) oldstable; urgency=medium
|
||||||
|
|
||||||
|
* The locale-specific icon names are correct too
|
||||||
|
|
||||||
|
-- István Váradi <ivaradi@varadiistvan.hu> Tue, 7 Feb 2017 19:55:40 +0100
|
||||||
|
|
||||||
|
nextcloud-client (2.2.4-1.3) oldstable; urgency=medium
|
||||||
|
|
||||||
|
* Caja syncstate plugin is built.
|
||||||
|
* The syncstate plugin has application-specific name
|
||||||
|
|
||||||
|
-- István Váradi <ivaradi@varadiistvan.hu> Fri, 27 Jan 2017 19:34:18 +0100
|
||||||
|
|
||||||
|
nextcloud-client (2.2.4-1.2) oldstable; urgency=medium
|
||||||
|
|
||||||
|
* Fixed appname in the Nemo syncstate extension.
|
||||||
|
|
||||||
|
-- István Váradi <ivaradi@varadiistvan.hu> Thu, 19 Jan 2017 16:46:50 +0100
|
||||||
|
|
||||||
|
nextcloud-client (2.2.4-1.1) oldstable; urgency=medium
|
||||||
|
|
||||||
|
* Added Nautilus and Nemo syncstate extensions.
|
||||||
|
|
||||||
|
-- István Váradi <ivaradi@varadiistvan.hu> Tue, 17 Jan 2017 19:55:32 +0100
|
||||||
|
|
||||||
|
nextcloud-client (2.2.4-1.0) oldstable; urgency=medium
|
||||||
|
|
||||||
|
* Initial release.
|
||||||
|
|
||||||
|
-- István Váradi <ivaradi@varadiistvan.hu> Wed, 14 Dec 2016 20:07:46 +0100
|
||||||
84
admin/linux/debian/debian.oldstable/control
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
Source: nextcloud-client
|
||||||
|
Section: contrib/devel
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: István Váradi <ivaradi@varadiistvan.hu>
|
||||||
|
Build-Depends: cmake,
|
||||||
|
debhelper,
|
||||||
|
cdbs,
|
||||||
|
dh-python,
|
||||||
|
extra-cmake-modules (>= 5.16),
|
||||||
|
kdelibs5-dev,
|
||||||
|
kio-dev,
|
||||||
|
libcmocka-dev,
|
||||||
|
libdbus-1-dev,
|
||||||
|
libhttp-dav-perl,
|
||||||
|
libinotify-dev [kfreebsd-any],
|
||||||
|
libqt5webkit5-dev,
|
||||||
|
libqt5svg5-dev,
|
||||||
|
libsqlite3-dev,
|
||||||
|
libssl-dev (>= 1.1.0),
|
||||||
|
zlib1g-dev,
|
||||||
|
optipng,
|
||||||
|
pkg-kde-tools,
|
||||||
|
python-sphinx | python3-sphinx,
|
||||||
|
python3-all,
|
||||||
|
qt5keychain-dev,
|
||||||
|
qtwebengine5-dev,
|
||||||
|
qtdeclarative5-dev,
|
||||||
|
qttools5-dev,
|
||||||
|
qttools5-dev-tools,
|
||||||
|
xvfb
|
||||||
|
Standards-Version: 3.9.8
|
||||||
|
Homepage: https://github.com/nextcloud/client_theming
|
||||||
|
#Vcs-Git: git://anonscm.debian.org/collab-maint/nextcloud-client.git
|
||||||
|
#Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/nextcloud-client.git
|
||||||
|
|
||||||
|
Package: nextcloud-client
|
||||||
|
Architecture: any
|
||||||
|
Depends: libnextcloudsync0 (=${binary:Version}), ${shlibs:Depends}, ${misc:Depends}, nextcloud-client-l10n
|
||||||
|
Recommends: libgnome-keyring0
|
||||||
|
Description: Nextcloud desktop sync client
|
||||||
|
Use the desktop client to keep your files synchronized
|
||||||
|
between your Nextcloud server and your desktop. Select
|
||||||
|
one or more directories on your local machine and always
|
||||||
|
have access to your latest files wherever you are.
|
||||||
|
|
||||||
|
Package: libnextcloudsync0
|
||||||
|
Architecture: any
|
||||||
|
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||||
|
Description: Nextcloud sync library
|
||||||
|
Used by the Nextcloud desktop client as the synchronization engine.
|
||||||
|
|
||||||
|
Package: libnextcloudsync-dev
|
||||||
|
Architecture: any
|
||||||
|
Section: contrib/libdevel
|
||||||
|
Depends: libnextcloudsync0 (=${binary:Version}), ${misc:Depends}
|
||||||
|
Description: Nextcloud sync library development files
|
||||||
|
The headers and development library for the Nextcloud sync library.
|
||||||
|
|
||||||
|
Package: nextcloud-client-l10n
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${misc:Depends}
|
||||||
|
Description: Nextcloud client internatialization files
|
||||||
|
The translation files.
|
||||||
|
|
||||||
|
Package: nextcloud-client-nautilus
|
||||||
|
Architecture: all
|
||||||
|
Depends: nextcloud-client (>=${binary:Version}), libnextcloudsync0, python-nautilus, nautilus, ${misc:Depends}
|
||||||
|
Description: Nautilus plugin for Nextcloud
|
||||||
|
This package contains a Nautilus plugin to display
|
||||||
|
synchronization status icons for Nextcloud files.
|
||||||
|
|
||||||
|
Package: nextcloud-client-nemo
|
||||||
|
Architecture: all
|
||||||
|
Depends: nextcloud-client (>=${binary:Version}), libnextcloudsync0, python-nemo, nemo, ${misc:Depends}
|
||||||
|
Description: Nemo plugin for Nextcloud
|
||||||
|
This package contains a Nemo plugin to display
|
||||||
|
synchronization status icons for Nextcloud files.
|
||||||
|
|
||||||
|
Package: nextcloud-client-caja
|
||||||
|
Architecture: all
|
||||||
|
Depends: nextcloud-client (>=${binary:Version}), libnextcloudsync0, python-caja, caja, ${misc:Depends}
|
||||||
|
Description: Caja plugin for Nextcloud
|
||||||
|
This package contains a Caja plugin to display
|
||||||
|
synchronization status icons for Nextcloud files.
|
||||||
@@ -9,7 +9,10 @@ Build-Depends: cmake,
|
|||||||
extra-cmake-modules (>= 5.16),
|
extra-cmake-modules (>= 5.16),
|
||||||
kdelibs5-dev,
|
kdelibs5-dev,
|
||||||
kio-dev,
|
kio-dev,
|
||||||
|
libavcodec58,
|
||||||
libcmocka-dev,
|
libcmocka-dev,
|
||||||
|
libcloudproviders-dev,
|
||||||
|
libdbus-1-dev,
|
||||||
libhttp-dav-perl,
|
libhttp-dav-perl,
|
||||||
libinotify-dev [kfreebsd-any],
|
libinotify-dev [kfreebsd-any],
|
||||||
libqt5webkit5-dev,
|
libqt5webkit5-dev,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ Build-Depends: cmake,
|
|||||||
kdelibs5-dev,
|
kdelibs5-dev,
|
||||||
libkf5kio-dev,
|
libkf5kio-dev,
|
||||||
libcmocka-dev,
|
libcmocka-dev,
|
||||||
|
libcloudproviders-dev,
|
||||||
|
libdbus-1-dev,
|
||||||
libhttp-dav-perl,
|
libhttp-dav-perl,
|
||||||
libinotify-dev [kfreebsd-any],
|
libinotify-dev [kfreebsd-any],
|
||||||
libqt5svg5-dev,
|
libqt5svg5-dev,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ if ! wget http://ppa.launchpad.net/${repo}/ubuntu/pool/main/n/nextcloud-client/n
|
|||||||
origsourceopt="-sa"
|
origsourceopt="-sa"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for distribution in xenial bionic disco eoan stable; do
|
for distribution in xenial bionic disco eoan stable oldstable; do
|
||||||
rm -rf nextcloud-client_${basever}
|
rm -rf nextcloud-client_${basever}
|
||||||
cp -a ${DRONE_WORKSPACE} nextcloud-client_${basever}
|
cp -a ${DRONE_WORKSPACE} nextcloud-client_${basever}
|
||||||
|
|
||||||
@@ -98,26 +98,46 @@ if test "${pull_request}" = "master"; then
|
|||||||
PPA=$PPA_BETA
|
PPA=$PPA_BETA
|
||||||
OBS_PROJECT=$OBS_PROJECT_BETA
|
OBS_PROJECT=$OBS_PROJECT_BETA
|
||||||
fi
|
fi
|
||||||
OBS_SUBDIR="${OBS_PROJECT}/${OBS_PACKAGE}"
|
|
||||||
|
|
||||||
if test -f ~/.has_ppa_keys; then
|
if test -f ~/.has_ppa_keys; then
|
||||||
for changes in nextcloud-client_*~+([a-z])1_source.changes; do
|
for changes in nextcloud-client_*~+([a-z])1_source.changes; do
|
||||||
dput $PPA $changes > /dev/null
|
case "${changes}" in
|
||||||
|
*oldstable1*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
dput $PPA $changes > /dev/null
|
||||||
|
;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
mkdir osc
|
for distribution in stable oldstable; do
|
||||||
cd osc
|
if test "${distribution}" = "oldstable"; then
|
||||||
osc co ${OBS_PROJECT} ${OBS_PACKAGE}
|
pkgsuffix=".${distribution}"
|
||||||
if test "$(ls ${OBS_SUBDIR})"; then
|
pkgvertag="~${distribution}1"
|
||||||
osc delete ${OBS_SUBDIR}/*
|
else
|
||||||
fi
|
pkgsuffix=""
|
||||||
cp ../nextcloud-client*.orig.tar.* ${OBS_SUBDIR}/
|
pkgvertag=""
|
||||||
cp ../nextcloud-client_*[0-9.][0-9].dsc ${OBS_SUBDIR}/
|
fi
|
||||||
cp ../nextcloud-client_*[0-9.][0-9].debian.tar* ${OBS_SUBDIR}/
|
|
||||||
cp ../nextcloud-client_*[0-9.][0-9]_source.changes ${OBS_SUBDIR}/
|
|
||||||
osc add ${OBS_SUBDIR}/*
|
|
||||||
|
|
||||||
cd ${OBS_SUBDIR}
|
package="${OBS_PACKAGE}${pkgsuffix}"
|
||||||
osc commit -m "Travis update"
|
OBS_SUBDIR="${OBS_PROJECT}/${package}"
|
||||||
|
|
||||||
|
mkdir -p osc
|
||||||
|
pushd osc
|
||||||
|
osc co ${OBS_PROJECT} ${package}
|
||||||
|
if test "$(ls ${OBS_SUBDIR})"; then
|
||||||
|
osc delete ${OBS_SUBDIR}/*
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp ../nextcloud-client*.orig.tar.* ${OBS_SUBDIR}/
|
||||||
|
cp ../nextcloud-client_*[0-9.][0-9]${pkgvertag}.dsc ${OBS_SUBDIR}/
|
||||||
|
cp ../nextcloud-client_*[0-9.][0-9]${pkgvertag}.debian.tar* ${OBS_SUBDIR}/
|
||||||
|
cp ../nextcloud-client_*[0-9.][0-9]${pkgvertag}_source.changes ${OBS_SUBDIR}/
|
||||||
|
osc add ${OBS_SUBDIR}/*
|
||||||
|
|
||||||
|
cd ${OBS_SUBDIR}
|
||||||
|
osc commit -m "Travis update"
|
||||||
|
popd
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
21
admin/linux/upload-appimage.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
set -xe
|
||||||
|
|
||||||
|
cd /build
|
||||||
|
|
||||||
|
# Upload AppImage
|
||||||
|
APPIMAGE=$(readlink -f ./Nextcloud*.AppImage)
|
||||||
|
BASENAME=$(basename ${APPIMAGE})
|
||||||
|
|
||||||
|
if curl --max-time 900 --upload-file ${APPIMAGE} https://transfer.sh/${BASENAME}
|
||||||
|
then
|
||||||
|
echo
|
||||||
|
echo "Get the AppImage at the link above!"
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "Upload failed, however this is an optional step."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Don't let the Drone build fail
|
||||||
|
exit 0
|
||||||
@@ -49,7 +49,7 @@ fi
|
|||||||
if [ ! -z "$identity" ]; then
|
if [ ! -z "$identity" ]; then
|
||||||
echo "Will try to sign the installer"
|
echo "Will try to sign the installer"
|
||||||
pushd $install_path
|
pushd $install_path
|
||||||
productsign --sign "$identity" "$installer_file" "$installer_file.new"
|
productsign --timestamp --sign "$identity" "$installer_file" "$installer_file.new"
|
||||||
mv "$installer_file".new "$installer_file"
|
mv "$installer_file".new "$installer_file"
|
||||||
popd
|
popd
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -32,11 +32,12 @@ FRAMEWORK_SEARCH_PATH=[
|
|||||||
os.path.join(os.environ['HOME'], 'Library/Frameworks')
|
os.path.join(os.environ['HOME'], 'Library/Frameworks')
|
||||||
]
|
]
|
||||||
|
|
||||||
LIBRARY_SEARCH_PATH=['/usr/local/lib', '/usr/local/Qt-5.6.2/lib', '.']
|
LIBRARY_SEARCH_PATH=['/usr/local/lib', '/usr/local/Qt-5.12.5/lib', '.']
|
||||||
|
|
||||||
QT_PLUGINS = [
|
QT_PLUGINS = [
|
||||||
'sqldrivers/libqsqlite.dylib',
|
'sqldrivers/libqsqlite.dylib',
|
||||||
'platforms/libqcocoa.dylib',
|
'platforms/libqcocoa.dylib',
|
||||||
|
'styles/libqmacstyle.dylib',
|
||||||
'imageformats/libqgif.dylib',
|
'imageformats/libqgif.dylib',
|
||||||
'imageformats/libqico.dylib',
|
'imageformats/libqico.dylib',
|
||||||
'imageformats/libqjpeg.dylib',
|
'imageformats/libqjpeg.dylib',
|
||||||
@@ -46,7 +47,7 @@ QT_PLUGINS = [
|
|||||||
QT_PLUGINS_SEARCH_PATH=[
|
QT_PLUGINS_SEARCH_PATH=[
|
||||||
# os.path.join(os.environ['QTDIR'], 'plugins'),
|
# os.path.join(os.environ['QTDIR'], 'plugins'),
|
||||||
# '/usr/local/Cellar/qt/5.2.1/plugins',
|
# '/usr/local/Cellar/qt/5.2.1/plugins',
|
||||||
'/usr/local/Qt-5.6.2/plugins',
|
'/usr/local/Qt-5.12.5/plugins',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 254 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
2
binary
19
client.qrc
@@ -1,16 +1,8 @@
|
|||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/client">
|
<qresource prefix="/client">
|
||||||
<file>resources/dialog-close.png</file>
|
|
||||||
<file>resources/dialog-ok.png</file>
|
|
||||||
<file>resources/dialog-cancel.png</file>
|
|
||||||
<file>resources/folder-sync.png</file>
|
|
||||||
<file>resources/folder-sync@2x.png</file>
|
|
||||||
<file>resources/task-ongoing.png</file>
|
|
||||||
<file>resources/view-refresh.png</file>
|
|
||||||
<file>resources/warning.png</file>
|
|
||||||
<file>resources/warning@2x.png</file>
|
|
||||||
<file>resources/settings.png</file>
|
<file>resources/settings.png</file>
|
||||||
<file>resources/settings@2x.png</file>
|
<file>resources/settings@2x.png</file>
|
||||||
|
<file>resources/activity.svg</file>
|
||||||
<file>resources/activity.png</file>
|
<file>resources/activity.png</file>
|
||||||
<file>resources/activity@2x.png</file>
|
<file>resources/activity@2x.png</file>
|
||||||
<file>resources/network.png</file>
|
<file>resources/network.png</file>
|
||||||
@@ -20,13 +12,13 @@
|
|||||||
<file>resources/lock-https.png</file>
|
<file>resources/lock-https.png</file>
|
||||||
<file>resources/lock-https@2x.png</file>
|
<file>resources/lock-https@2x.png</file>
|
||||||
<file>resources/account.png</file>
|
<file>resources/account.png</file>
|
||||||
|
<file>resources/account.svg</file>
|
||||||
<file>resources/more.svg</file>
|
<file>resources/more.svg</file>
|
||||||
<file>resources/delete.png</file>
|
<file>resources/delete.png</file>
|
||||||
<file>resources/close.svg</file>
|
<file>resources/close.svg</file>
|
||||||
<file>resources/bell.svg</file>
|
<file>resources/bell.svg</file>
|
||||||
<file>resources/link.svg</file>
|
<file>resources/link.svg</file>
|
||||||
<file>resources/files.svg</file>
|
<file>resources/files.svg</file>
|
||||||
<file>resources/folder-grey.png</file>
|
|
||||||
<file>resources/state-error.svg</file>
|
<file>resources/state-error.svg</file>
|
||||||
<file>resources/state-warning.svg</file>
|
<file>resources/state-warning.svg</file>
|
||||||
<file>resources/folder.svg</file>
|
<file>resources/folder.svg</file>
|
||||||
@@ -38,7 +30,14 @@
|
|||||||
<file>resources/copy.svg</file>
|
<file>resources/copy.svg</file>
|
||||||
<file>resources/state-sync.svg</file>
|
<file>resources/state-sync.svg</file>
|
||||||
<file>resources/add.png</file>
|
<file>resources/add.png</file>
|
||||||
|
<file>resources/add-color.svg</file>
|
||||||
<file>resources/state-info.svg</file>
|
<file>resources/state-info.svg</file>
|
||||||
|
<file>resources/change.svg</file>
|
||||||
|
<file>resources/delete-color.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/"/>
|
<qresource prefix="/"/>
|
||||||
|
<qresource prefix="/qml">
|
||||||
|
<file>src/gui/tray/Window.qml</file>
|
||||||
|
<file>src/gui/tray/UserLine.qml</file>
|
||||||
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>@MIRALL_VERSION_STRING@</string>
|
<string>@MIRALL_VERSION_STRING@</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>(C) 2014-2018 @APPLICATION_VENDOR@</string>
|
<string>(C) 2014-2020 @APPLICATION_VENDOR@</string>
|
||||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>SUShowReleaseNotes</key>
|
<key>SUShowReleaseNotes</key>
|
||||||
|
|||||||
17
cmake/modules/SanitizerFlags.cmake
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
# Enable address sanitizer (gcc/clang only)
|
||||||
|
macro(ENABLE_SANITIZER)
|
||||||
|
|
||||||
|
if (NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||||
|
message(FATAL_ERROR "Sanitizer supported only for gcc/clang")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(SANITIZER_FLAGS "-fsanitize=address -fsanitize=leak -g")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SANITIZER_FLAGS}")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZER_FLAGS}")
|
||||||
|
|
||||||
|
set(LINKER_FLAGS "-fsanitize=address,undefined -fuse-ld=gold")
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINKER_FLAGS}")
|
||||||
|
|
||||||
|
endmacro()
|
||||||
|
|
||||||
@@ -3,7 +3,11 @@
|
|||||||
# For details see the accompanying COPYING* file.
|
# For details see the accompanying COPYING* file.
|
||||||
|
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wno-long-long -Wno-gnu-zero-variadic-macro-arguments")
|
|
||||||
|
# Use this only for Clang
|
||||||
|
if (CMAKE_CXX_COMPILER MATCHES "Clang")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wno-long-long -Wno-gnu-zero-variadic-macro-arguments")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Fix sqlite compilation on macOS
|
# Fix sqlite compilation on macOS
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-incompatible-pointer-types-discards-qualifiers")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-incompatible-pointer-types-discards-qualifiers")
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
#cmakedefine CRASHREPORTER_EXECUTABLE "@CRASHREPORTER_EXECUTABLE@"
|
#cmakedefine CRASHREPORTER_EXECUTABLE "@CRASHREPORTER_EXECUTABLE@"
|
||||||
#define SOCKETAPI_TEAM_IDENTIFIER_PREFIX "@SOCKETAPI_TEAM_IDENTIFIER_PREFIX@"
|
#define SOCKETAPI_TEAM_IDENTIFIER_PREFIX "@SOCKETAPI_TEAM_IDENTIFIER_PREFIX@"
|
||||||
|
|
||||||
#cmakedefine APPLICATION_DOMAIN @APPLICATION_DOMAIN@
|
|
||||||
#cmakedefine THEME_CLASS @THEME_CLASS@
|
#cmakedefine THEME_CLASS @THEME_CLASS@
|
||||||
#cmakedefine THEME_INCLUDE @THEME_INCLUDE@
|
#cmakedefine THEME_INCLUDE @THEME_INCLUDE@
|
||||||
|
|
||||||
#cmakedefine APPLICATION_NAME "@APPLICATION_NAME@"
|
#cmakedefine APPLICATION_NAME "@APPLICATION_NAME@"
|
||||||
#cmakedefine APPLICATION_VENDOR "@APPLICATION_VENDOR@"
|
#cmakedefine APPLICATION_VENDOR "@APPLICATION_VENDOR@"
|
||||||
|
#cmakedefine APPLICATION_DOMAIN "@APPLICATION_DOMAIN@"
|
||||||
#cmakedefine APPLICATION_REV_DOMAIN "@APPLICATION_REV_DOMAIN@"
|
#cmakedefine APPLICATION_REV_DOMAIN "@APPLICATION_REV_DOMAIN@"
|
||||||
#cmakedefine APPLICATION_SHORTNAME "@APPLICATION_SHORTNAME@"
|
#cmakedefine APPLICATION_SHORTNAME "@APPLICATION_SHORTNAME@"
|
||||||
#cmakedefine APPLICATION_EXECUTABLE "@APPLICATION_EXECUTABLE@"
|
#cmakedefine APPLICATION_EXECUTABLE "@APPLICATION_EXECUTABLE@"
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
#cmakedefine APPLICATION_HELP_URL "@APPLICATION_HELP_URL@"
|
#cmakedefine APPLICATION_HELP_URL "@APPLICATION_HELP_URL@"
|
||||||
#cmakedefine APPLICATION_ICON_NAME "@APPLICATION_ICON_NAME@"
|
#cmakedefine APPLICATION_ICON_NAME "@APPLICATION_ICON_NAME@"
|
||||||
#cmakedefine APPLICATION_SERVER_URL "@APPLICATION_SERVER_URL@"
|
#cmakedefine APPLICATION_SERVER_URL "@APPLICATION_SERVER_URL@"
|
||||||
|
#cmakedefine LINUX_APPLICATION_ID "@LINUX_APPLICATION_ID@"
|
||||||
#cmakedefine APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR "@APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR@"
|
#cmakedefine APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR "@APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR@"
|
||||||
#cmakedefine APPLICATION_WIZARD_HEADER_TITLE_COLOR "@APPLICATION_WIZARD_HEADER_TITLE_COLOR@"
|
#cmakedefine APPLICATION_WIZARD_HEADER_TITLE_COLOR "@APPLICATION_WIZARD_HEADER_TITLE_COLOR@"
|
||||||
#cmakedefine APPLICATION_WIZARD_USE_CUSTOM_LOGO "@APPLICATION_WIZARD_USE_CUSTOM_LOGO@"
|
#cmakedefine APPLICATION_WIZARD_USE_CUSTOM_LOGO "@APPLICATION_WIZARD_USE_CUSTOM_LOGO@"
|
||||||
|
|||||||
@@ -13,11 +13,19 @@ desktop client.
|
|||||||
|
|
||||||
These instructions are updated to work with version |version| of the Nextcloud Client.
|
These instructions are updated to work with version |version| of the Nextcloud Client.
|
||||||
|
|
||||||
Getting Source Code
|
You have two possibilities to clone the repo.
|
||||||
-------------------
|
|
||||||
|
|
||||||
The :ref:`generic-build-instructions` pull the latest code directly from
|
First option is As [remote URL](https://help.github.com/en/articles/which-remote-url-should-i-use) you can choose between cloning with HTTPS URL's, which is recommended or cloning with SSH URL's.
|
||||||
GitHub, and work on Linux, macOS, and Windows.
|
|
||||||
|
[https://github.com/nextcloud/desktop.git](https://github.com/nextcloud/desktop.git)
|
||||||
|
|
||||||
|
When you don't have SSH key added to your GitHub account, than use HTTPS.
|
||||||
|
|
||||||
|
When you no part of the nextcloud organisation, clone with HTTPS:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone git@github.com:nextcloud/desktop.git
|
||||||
|
```
|
||||||
|
|
||||||
macOS
|
macOS
|
||||||
-----
|
-----
|
||||||
@@ -47,7 +55,7 @@ To set up your build environment for development using HomeBrew_:
|
|||||||
|
|
||||||
5. Install a Qt5 version with qtwebkit support::
|
5. Install a Qt5 version with qtwebkit support::
|
||||||
|
|
||||||
brew install qt5 --with-qtwebkit
|
brew install qt5
|
||||||
|
|
||||||
6. Install any missing dependencies::
|
6. Install any missing dependencies::
|
||||||
|
|
||||||
@@ -67,10 +75,23 @@ To set up your build environment for development using HomeBrew_:
|
|||||||
|
|
||||||
10. Install the Packages_ package creation tool.
|
10. Install the Packages_ package creation tool.
|
||||||
|
|
||||||
11. In the build directory, run ``admin/osx/create_mac.sh <build_dir> <install_dir>``.
|
11. Enable git submodules:
|
||||||
If you have a developer signing certificate, you can specify
|
```
|
||||||
its Common Name as a third parameter (use quotes) to have the package
|
$ cd desktop
|
||||||
signed automatically.
|
$ git submodule init
|
||||||
|
$ git submodule update
|
||||||
|
```
|
||||||
|
|
||||||
|
12. Generate the build files:
|
||||||
|
```
|
||||||
|
$ cd build
|
||||||
|
$ cmake .. -DCMAKE_INSTALL_PREFIX=~/nextcloud-desktop-client -DCMAKE_BUILD_TYPE=Debug -DNO_SHIBBOLETH=1
|
||||||
|
```
|
||||||
|
|
||||||
|
13. Compile and install:
|
||||||
|
```
|
||||||
|
$ make install
|
||||||
|
```
|
||||||
|
|
||||||
.. note:: Contrary to earlier versions, Nextcloud 1.7 and later are packaged
|
.. note:: Contrary to earlier versions, Nextcloud 1.7 and later are packaged
|
||||||
as a ``pkg`` installer. Do not call "make package" at any time when
|
as a ``pkg`` installer. Do not call "make package" at any time when
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ download page.
|
|||||||
System Requirements
|
System Requirements
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
- Windows 7+
|
- Windows 8.1+
|
||||||
- macOS 10.7+ (**64-bit only**)
|
- macOS 10.7+ (**64-bit only**)
|
||||||
- CentOS 6 & 7 (64-bit only)
|
- CentOS 6 & 7 (64-bit only)
|
||||||
- Debian 8.0 & 9.0
|
- Debian 8.0 & 9.0
|
||||||
|
|||||||
1
resources/add-color.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewbox="0 0 16 16"><path fill="#00d400" d="M9.02 13.98h-2v-5h-5v-2h5v-5h2v5l5-.028V8.98h-5z"/></svg>
|
||||||
|
After Width: | Height: | Size: 179 B |
1
resources/change.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" version="1.1" height="16"><path d="m8 2c-2.142 0-4.125 1.145-5.196 3l1.948 1.125c0.671-1.162 1.906-1.875 3.2476-1.875 1.1906 0 2.297 0.56157 3 1.5l-1.5 1.5h4.5v-4.5l-1.406 1.406c-1.129-1.348-2.802-2.1563-4.594-2.1563z"/><path d="m2 8.75v4.5l1.408-1.41c1.116 1.334 2.817 2.145 4.592 2.16 2.16 0.01827 4.116-1.132 5.196-3.002l-1.948-1.125c-0.677 1.171-1.9005 1.886-3.248 1.875-1.18-0.01-2.3047-0.572-3-1.5l1.5-1.5z"/></svg>
|
||||||
|
After Width: | Height: | Size: 493 B |
1
resources/delete-color.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewBox="0 0 16 16"><path d="m3.0503 4.4645 3.5355 3.5355-3.5355 3.536 1.4142 1.414 3.5355-3.5358 3.536 3.5358 1.414-1.414-3.5358-3.536 3.5358-3.5355-1.414-1.4142-3.536 3.5355-3.5355-3.5355-1.4142 1.4142z" fill="#d40000"/></svg>
|
||||||
|
After Width: | Height: | Size: 306 B |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 668 B |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 596 B |
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -467,6 +467,7 @@
|
|||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = FinderSyncExt/FinderSyncExt.entitlements;
|
CODE_SIGN_ENTITLEMENTS = FinderSyncExt/FinderSyncExt.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "-";
|
CODE_SIGN_IDENTITY = "-";
|
||||||
|
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ public:
|
|||||||
|
|
||||||
QString contextMenuTitle() const
|
QString contextMenuTitle() const
|
||||||
{
|
{
|
||||||
return _strings.value("CONTEXT_MENU_TITLE", "ownCloud");
|
return _strings.value("CONTEXT_MENU_TITLE", "Nextcloud");
|
||||||
}
|
}
|
||||||
QString shareActionTitle() const
|
QString shareActionTitle() const
|
||||||
{
|
{
|
||||||
return _strings.value("SHARE_MENU_TITLE", "Share...");
|
return _strings.value("SHARE_MENU_TITLE", "Share …");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString copyPrivateLinkTitle() const { return _strings["COPY_PRIVATE_LINK_MENU_TITLE"]; }
|
QString copyPrivateLinkTitle() const { return _strings["COPY_PRIVATE_LINK_MENU_TITLE"]; }
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ IFACEMETHODIMP OCContextMenu::Initialize(
|
|||||||
HDROP hDrop = static_cast<HDROP>(GlobalLock(stm.hGlobal));
|
HDROP hDrop = static_cast<HDROP>(GlobalLock(stm.hGlobal));
|
||||||
if (hDrop) {
|
if (hDrop) {
|
||||||
UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
|
UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
|
||||||
for (int i = 0; i < nFiles; ++i) {
|
for (UINT i = 0; i < nFiles; ++i) {
|
||||||
// Get the path of the file.
|
// Get the path of the file.
|
||||||
wchar_t buffer[MAX_PATH];
|
wchar_t buffer[MAX_PATH];
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
|
// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
|
||||||
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
|
// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
|
||||||
|
|
||||||
#define WINVER 0x0501
|
// Note: Here was a #define for windows target version
|
||||||
#define _WIN32_WINNT 0x0501
|
// e.g. WINVER / _WIN32_WINNT, see https://devblogs.microsoft.com/oldnewthing/20070411-00/?p=27283
|
||||||
|
// Unnecessary because we define both in desktop/CMakeLists.txt
|
||||||
|
|
||||||
#include <SDKDDKVer.h>
|
#include <SDKDDKVer.h>
|
||||||
|
|||||||
@@ -13,8 +13,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#define WINVER 0x0501
|
|
||||||
#define _WIN32_WINNT 0x0501
|
// Note: Here was a #define for windows target version
|
||||||
|
// e.g. WINVER / _WIN32_WINNT, see https://devblogs.microsoft.com/oldnewthing/20070411-00/?p=27283
|
||||||
|
// Unnecessary because we define both in desktop/CMakeLists.txt
|
||||||
|
|
||||||
#include "CommunicationSocket.h"
|
#include "CommunicationSocket.h"
|
||||||
#include "RegistryUtil.h"
|
#include "RegistryUtil.h"
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#define WINVER 0x0501
|
|
||||||
#define _WIN32_WINNT 0x0501
|
// Note: Here was a #define for windows target version
|
||||||
|
// e.g. WINVER / _WIN32_WINNT, see https://devblogs.microsoft.com/oldnewthing/20070411-00/?p=27283
|
||||||
|
// Unnecessary because we define both in desktop/CMakeLists.txt
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|||||||
2
src/3rdparty/libcrashreporter-qt
vendored
1
src/3rdparty/qtmacgoodies
vendored
@@ -29,12 +29,17 @@ if(NOT MSVC)
|
|||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FORTIFY_SOURCE=2")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FORTIFY_SOURCE=2")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORTIFY_SOURCE=2")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORTIFY_SOURCE=2")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Calling Qt's qCWarning(category, ...) with no params for "..." is a GNU
|
||||||
|
# extension (C++11 §16.3/4 forbids them). Silence clang's warnings.
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-gnu-zero-variadic-macro-arguments")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-gnu-zero-variadic-macro-arguments")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
# Enable DEP & ASLR
|
# Enable DEP & ASLR
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /nxcompat /dynamicbase")
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /nxcompat /dynamicbase")
|
||||||
elseif(UNIX AND NOT APPLE)
|
elseif(UNIX AND NOT APPLE)
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")
|
||||||
|
|||||||
@@ -499,7 +499,7 @@ restart_sync:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Cmd cmd;
|
Cmd cmd;
|
||||||
QString dbPath = options.source_dir + SyncJournalDb::makeDbName(options.source_dir, credentialFreeUrl, folder, user);
|
QString dbPath = options.source_dir + SyncJournalDb::makeDbName(credentialFreeUrl, folder, user);
|
||||||
SyncJournalDb db(dbPath);
|
SyncJournalDb db(dbPath);
|
||||||
|
|
||||||
if (!selectiveSyncList.empty()) {
|
if (!selectiveSyncList.empty()) {
|
||||||
|
|||||||
@@ -11,43 +11,50 @@
|
|||||||
|
|
||||||
// For overloading macros by argument count
|
// For overloading macros by argument count
|
||||||
// See stackoverflow.com/questions/16683146/can-macros-be-overloaded-by-number-of-arguments
|
// See stackoverflow.com/questions/16683146/can-macros-be-overloaded-by-number-of-arguments
|
||||||
#define OC_ASSERT_CAT(A, B) A##B
|
// Bugfix 08/09/2019: Broken arg expansion led to always collapsing to 1 arg (XXXX_1 overload result)
|
||||||
#define OC_ASSERT_SELECT(NAME, NUM) OC_ASSERT_CAT(NAME##_, NUM)
|
// See also: https://stackoverflow.com/questions/9183993/msvc-variadic-macro-expansion
|
||||||
#define OC_ASSERT_GET_COUNT(_1, _2, _3, COUNT, ...) COUNT
|
#define OC_ASSERT_GLUE(x, y) x y
|
||||||
#define OC_ASSERT_VA_SIZE(...) OC_ASSERT_GET_COUNT(__VA_ARGS__, 3, 2, 1, 0)
|
|
||||||
|
|
||||||
#define OC_ASSERT_OVERLOAD(NAME, ...) OC_ASSERT_SELECT(NAME, OC_ASSERT_VA_SIZE(__VA_ARGS__)) \
|
#define OC_ASSERT_GET_COUNT(_1, _2, _3, COUNT, ...) COUNT
|
||||||
(__VA_ARGS__)
|
#define OC_ASSERT_EXPAND_ARGS(args) OC_ASSERT_GET_COUNT args
|
||||||
|
#define OC_ASSERT_VA_SIZE(...) OC_ASSERT_EXPAND_ARGS((__VA_ARGS__, 3, 2, 1, 0))
|
||||||
|
|
||||||
|
#define OC_ASSERT_SELECT2(NAME, COUNT) NAME##COUNT
|
||||||
|
#define OC_ASSERT_SELECT1(NAME, COUNT) OC_ASSERT_SELECT2(NAME, COUNT)
|
||||||
|
#define OC_ASSERT_SELECT(NAME, COUNT) OC_ASSERT_SELECT1(NAME, COUNT)
|
||||||
|
|
||||||
|
#define OC_ASSERT_OVERLOAD(NAME, ...) OC_ASSERT_GLUE(OC_ASSERT_SELECT(NAME, OC_ASSERT_VA_SIZE(__VA_ARGS__)), \
|
||||||
|
(__VA_ARGS__))
|
||||||
|
|
||||||
// Default assert: If the condition is false in debug builds, terminate.
|
// Default assert: If the condition is false in debug builds, terminate.
|
||||||
//
|
//
|
||||||
// Prints a message on failure, even in release builds.
|
// Prints a message on failure, even in release builds.
|
||||||
#define ASSERT(...) OC_ASSERT_OVERLOAD(ASSERT, __VA_ARGS__)
|
#define ASSERT1(cond) \
|
||||||
#define ASSERT_1(cond) \
|
|
||||||
if (!(cond)) { \
|
if (!(cond)) { \
|
||||||
OC_ASSERT_MSG("ASSERT: \"%s\" in file %s, line %d", #cond, __FILE__, __LINE__); \
|
OC_ASSERT_MSG("ASSERT: \"%s\" in file %s, line %d", #cond, __FILE__, __LINE__); \
|
||||||
} else { \
|
} else { \
|
||||||
}
|
}
|
||||||
#define ASSERT_2(cond, message) \
|
#define ASSERT2(cond, message) \
|
||||||
if (!(cond)) { \
|
if (!(cond)) { \
|
||||||
OC_ASSERT_MSG("ASSERT: \"%s\" in file %s, line %d with message: %s", #cond, __FILE__, __LINE__, message); \
|
OC_ASSERT_MSG("ASSERT: \"%s\" in file %s, line %d with message: %s", #cond, __FILE__, __LINE__, message); \
|
||||||
} else { \
|
} else { \
|
||||||
}
|
}
|
||||||
|
#define ASSERT(...) OC_ASSERT_OVERLOAD(ASSERT, __VA_ARGS__)
|
||||||
|
|
||||||
// Enforce condition to be true, even in release builds.
|
// Enforce condition to be true, even in release builds.
|
||||||
//
|
//
|
||||||
// Prints 'message' and aborts execution if 'cond' is false.
|
// Prints 'message' and aborts execution if 'cond' is false.
|
||||||
#define ENFORCE(...) OC_ASSERT_OVERLOAD(ENFORCE, __VA_ARGS__)
|
#define ENFORCE1(cond) \
|
||||||
#define ENFORCE_1(cond) \
|
|
||||||
if (!(cond)) { \
|
if (!(cond)) { \
|
||||||
qFatal("ENFORCE: \"%s\" in file %s, line %d", #cond, __FILE__, __LINE__); \
|
qFatal("ENFORCE: \"%s\" in file %s, line %d", #cond, __FILE__, __LINE__); \
|
||||||
} else { \
|
} else { \
|
||||||
}
|
}
|
||||||
#define ENFORCE_2(cond, message) \
|
#define ENFORCE2(cond, message) \
|
||||||
if (!(cond)) { \
|
if (!(cond)) { \
|
||||||
qFatal("ENFORCE: \"%s\" in file %s, line %d with message: %s", #cond, __FILE__, __LINE__, message); \
|
qFatal("ENFORCE: \"%s\" in file %s, line %d with message: %s", #cond, __FILE__, __LINE__, message); \
|
||||||
} else { \
|
} else { \
|
||||||
}
|
}
|
||||||
|
#define ENFORCE(...) OC_ASSERT_OVERLOAD(ENFORCE, __VA_ARGS__)
|
||||||
|
|
||||||
// An assert that is only present in debug builds: typically used for
|
// An assert that is only present in debug builds: typically used for
|
||||||
// asserts that are too expensive for release mode.
|
// asserts that are too expensive for release mode.
|
||||||
|
|||||||
@@ -207,7 +207,10 @@ static inline uint64_t c_jhash64(const uint8_t *k, uint64_t length, uint64_t int
|
|||||||
/* handle the last 23 bytes */
|
/* handle the last 23 bytes */
|
||||||
c += length;
|
c += length;
|
||||||
switch(len) {
|
switch(len) {
|
||||||
|
// pragma only for GCC (and clang continues to pretend to be it by defining __GNUC__)
|
||||||
|
#if defined(__GNUC__) && !defined(__clang__)
|
||||||
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||||
|
#endif
|
||||||
case 23: c+=((uint64_t)k[22]<<56);
|
case 23: c+=((uint64_t)k[22]<<56);
|
||||||
case 22: c+=((uint64_t)k[21]<<48);
|
case 22: c+=((uint64_t)k[21]<<48);
|
||||||
case 21: c+=((uint64_t)k[20]<<40);
|
case 21: c+=((uint64_t)k[20]<<40);
|
||||||
|
|||||||
@@ -281,8 +281,8 @@ int SqlQuery::prepare(const QByteArray &sql, bool allow_failure)
|
|||||||
*/
|
*/
|
||||||
static bool startsWithInsensitive(const QByteArray &a, const char *b)
|
static bool startsWithInsensitive(const QByteArray &a, const char *b)
|
||||||
{
|
{
|
||||||
int len = strlen(b);
|
size_t len = strlen(b);
|
||||||
return a.size() >= len && qstrnicmp(a.constData(), b, len) == 0;
|
return a.size() >= len && qstrnicmp(a.constData(), b, Utility::convertSizeToUint(len)) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SqlQuery::isSelect()
|
bool SqlQuery::isSelect()
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QStandardPaths>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
#include "common/syncjournaldb.h"
|
#include "common/syncjournaldb.h"
|
||||||
@@ -102,11 +103,15 @@ SyncJournalDb::SyncJournalDb(const QString &dbFilePath, QObject *parent)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SyncJournalDb::makeDbName(const QString &localPath,
|
QString SyncJournalDb::makeDbName(const QUrl &remoteUrl,
|
||||||
const QUrl &remoteUrl,
|
|
||||||
const QString &remotePath,
|
const QString &remotePath,
|
||||||
const QString &user)
|
const QString &user)
|
||||||
{
|
{
|
||||||
|
const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
if (!QDir(dbPath).exists()) {
|
||||||
|
QDir().mkdir(dbPath);
|
||||||
|
}
|
||||||
|
|
||||||
QString journalPath = QLatin1String("._sync_");
|
QString journalPath = QLatin1String("._sync_");
|
||||||
|
|
||||||
QString key = QString::fromUtf8("%1@%2:%3").arg(user, remoteUrl.toString(), remotePath);
|
QString key = QString::fromUtf8("%1@%2:%3").arg(user, remoteUrl.toString(), remotePath);
|
||||||
@@ -115,17 +120,16 @@ QString SyncJournalDb::makeDbName(const QString &localPath,
|
|||||||
journalPath.append(ba.left(6).toHex());
|
journalPath.append(ba.left(6).toHex());
|
||||||
journalPath.append(".db");
|
journalPath.append(".db");
|
||||||
|
|
||||||
|
journalPath = dbPath + QLatin1Char('/') + journalPath;
|
||||||
|
|
||||||
// If the journal doesn't exist and we can't create a file
|
// If the journal doesn't exist and we can't create a file
|
||||||
// at that location, try again with a journal name that doesn't
|
// at that location, try again with a journal name that doesn't
|
||||||
// have the ._ prefix.
|
// have the ._ prefix.
|
||||||
//
|
//
|
||||||
// The disadvantage of that filename is that it will only be ignored
|
|
||||||
// by client versions >2.3.2.
|
|
||||||
//
|
|
||||||
// See #5633: "._*" is often forbidden on samba shared folders.
|
// See #5633: "._*" is often forbidden on samba shared folders.
|
||||||
|
|
||||||
// If it exists already, the path is clearly usable
|
// If it exists already, the path is clearly usable
|
||||||
QFile file(QDir(localPath).filePath(journalPath));
|
QFile file(QDir(dbPath).filePath(journalPath));
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
return journalPath;
|
return journalPath;
|
||||||
}
|
}
|
||||||
@@ -140,7 +144,7 @@ QString SyncJournalDb::makeDbName(const QString &localPath,
|
|||||||
|
|
||||||
// Can we create it if we drop the underscore?
|
// Can we create it if we drop the underscore?
|
||||||
QString alternateJournalPath = journalPath.mid(2).prepend(".");
|
QString alternateJournalPath = journalPath.mid(2).prepend(".");
|
||||||
QFile file2(QDir(localPath).filePath(alternateJournalPath));
|
QFile file2(QDir(dbPath).filePath(alternateJournalPath));
|
||||||
if (file2.open(QIODevice::ReadWrite)) {
|
if (file2.open(QIODevice::ReadWrite)) {
|
||||||
// The alternative worked, use it
|
// The alternative worked, use it
|
||||||
qCInfo(lcDb) << "Using alternate database path" << alternateJournalPath;
|
qCInfo(lcDb) << "Using alternate database path" << alternateJournalPath;
|
||||||
|
|||||||
@@ -46,8 +46,7 @@ public:
|
|||||||
virtual ~SyncJournalDb();
|
virtual ~SyncJournalDb();
|
||||||
|
|
||||||
/// Create a journal path for a specific configuration
|
/// Create a journal path for a specific configuration
|
||||||
static QString makeDbName(const QString &localPath,
|
static QString makeDbName(const QUrl &remoteUrl,
|
||||||
const QUrl &remoteUrl,
|
|
||||||
const QString &remotePath,
|
const QString &remotePath,
|
||||||
const QString &user);
|
const QString &user);
|
||||||
|
|
||||||
@@ -103,6 +102,7 @@ public:
|
|||||||
: _chunk(0)
|
: _chunk(0)
|
||||||
, _transferid(0)
|
, _transferid(0)
|
||||||
, _size(0)
|
, _size(0)
|
||||||
|
, _modtime(0)
|
||||||
, _errorCount(0)
|
, _errorCount(0)
|
||||||
, _valid(false)
|
, _valid(false)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ void Utility::usleep(int usec)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This can be overriden from the tests
|
// This can be overriden from the tests
|
||||||
OCSYNC_EXPORT bool fsCasePreserving_override = []()-> bool {
|
OCSYNC_EXPORT bool fsCasePreserving_override = []() -> bool {
|
||||||
QByteArray env = qgetenv("OWNCLOUD_TEST_CASE_PRESERVING");
|
QByteArray env = qgetenv("OWNCLOUD_TEST_CASE_PRESERVING");
|
||||||
if (!env.isEmpty())
|
if (!env.isEmpty())
|
||||||
return env.toInt();
|
return env.toInt();
|
||||||
@@ -362,12 +362,12 @@ QString Utility::fileNameForGuiUse(const QString &fName)
|
|||||||
QByteArray Utility::normalizeEtag(QByteArray etag)
|
QByteArray Utility::normalizeEtag(QByteArray etag)
|
||||||
{
|
{
|
||||||
/* strip "XXXX-gzip" */
|
/* strip "XXXX-gzip" */
|
||||||
if(etag.startsWith('"') && etag.endsWith("-gzip\"")) {
|
if (etag.startsWith('"') && etag.endsWith("-gzip\"")) {
|
||||||
etag.chop(6);
|
etag.chop(6);
|
||||||
etag.remove(0, 1);
|
etag.remove(0, 1);
|
||||||
}
|
}
|
||||||
/* strip trailing -gzip */
|
/* strip trailing -gzip */
|
||||||
if(etag.endsWith("-gzip")) {
|
if (etag.endsWith("-gzip")) {
|
||||||
etag.chop(5);
|
etag.chop(5);
|
||||||
}
|
}
|
||||||
/* strip normal quotes */
|
/* strip normal quotes */
|
||||||
@@ -396,6 +396,26 @@ void Utility::crash()
|
|||||||
*a = 1;
|
*a = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use this functions to retrieve uint/int (often required by Qt and WIN32) from size_t
|
||||||
|
// without compiler warnings about possible truncation
|
||||||
|
uint Utility::convertSizeToUint(size_t &convertVar)
|
||||||
|
{
|
||||||
|
if (convertVar > UINT_MAX) {
|
||||||
|
//throw std::bad_cast();
|
||||||
|
convertVar = UINT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD
|
||||||
|
}
|
||||||
|
return static_cast<uint>(convertVar);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint Utility::convertSizeToInt(size_t &convertVar)
|
||||||
|
{
|
||||||
|
if (convertVar > INT_MAX) {
|
||||||
|
//throw std::bad_cast();
|
||||||
|
convertVar = INT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD
|
||||||
|
}
|
||||||
|
return static_cast<int>(convertVar);
|
||||||
|
}
|
||||||
|
|
||||||
// read the output of the owncloud --version command from the owncloud
|
// read the output of the owncloud --version command from the owncloud
|
||||||
// version that is on disk. This works for most versions of the client,
|
// version that is on disk. This works for most versions of the client,
|
||||||
// because clients that do not yet know the --version flag return the
|
// because clients that do not yet know the --version flag return the
|
||||||
@@ -445,7 +465,7 @@ QString Utility::timeAgoInWords(const QDateTime &dt, const QDateTime &from)
|
|||||||
|
|
||||||
if (floor(secs / 3600.0) > 0) {
|
if (floor(secs / 3600.0) > 0) {
|
||||||
int hours = floor(secs / 3600.0);
|
int hours = floor(secs / 3600.0);
|
||||||
if(hours == 1){
|
if (hours == 1) {
|
||||||
return (QObject::tr("%n hour ago", "", hours));
|
return (QObject::tr("%n hour ago", "", hours));
|
||||||
} else {
|
} else {
|
||||||
return (QObject::tr("%n hours ago", "", hours));
|
return (QObject::tr("%n hours ago", "", hours));
|
||||||
@@ -460,7 +480,7 @@ QString Utility::timeAgoInWords(const QDateTime &dt, const QDateTime &from)
|
|||||||
return QObject::tr("Less than a minute ago");
|
return QObject::tr("Less than a minute ago");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if(minutes == 1){
|
} else if (minutes == 1) {
|
||||||
return (QObject::tr("%n minute ago", "", minutes));
|
return (QObject::tr("%n minute ago", "", minutes));
|
||||||
} else {
|
} else {
|
||||||
return (QObject::tr("%n minutes ago", "", minutes));
|
return (QObject::tr("%n minutes ago", "", minutes));
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#ifndef UTILITY_H
|
#ifndef UTILITY_H
|
||||||
#define UTILITY_H
|
#define UTILITY_H
|
||||||
|
|
||||||
|
|
||||||
#include "ocsynclib.h"
|
#include "ocsynclib.h"
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
|
#include <QtQuick/QQuickImageProvider>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -55,6 +57,12 @@ namespace Utility {
|
|||||||
OCSYNC_EXPORT QByteArray userAgentString();
|
OCSYNC_EXPORT QByteArray userAgentString();
|
||||||
OCSYNC_EXPORT bool hasLaunchOnStartup(const QString &appName);
|
OCSYNC_EXPORT bool hasLaunchOnStartup(const QString &appName);
|
||||||
OCSYNC_EXPORT void setLaunchOnStartup(const QString &appName, const QString &guiName, bool launch);
|
OCSYNC_EXPORT void setLaunchOnStartup(const QString &appName, const QString &guiName, bool launch);
|
||||||
|
OCSYNC_EXPORT uint convertSizeToUint(size_t &convertVar);
|
||||||
|
OCSYNC_EXPORT uint convertSizeToInt(size_t &convertVar);
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
OCSYNC_EXPORT DWORD convertSizeToDWORD(size_t &convertVar);
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the amount of free space available.
|
* Return the amount of free space available.
|
||||||
|
|||||||
@@ -47,14 +47,18 @@ QString getUserAutostartDir_private()
|
|||||||
|
|
||||||
bool hasLaunchOnStartup_private(const QString &appName)
|
bool hasLaunchOnStartup_private(const QString &appName)
|
||||||
{
|
{
|
||||||
QString desktopFileLocation = getUserAutostartDir_private() + appName + QLatin1String(".desktop");
|
QString desktopFileLocation = getUserAutostartDir_private()
|
||||||
|
+ QLatin1String(LINUX_APPLICATION_ID)
|
||||||
|
+ QLatin1String(".desktop");
|
||||||
return QFile::exists(desktopFileLocation);
|
return QFile::exists(desktopFileLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLaunchOnStartup_private(const QString &appName, const QString &guiName, bool enable)
|
void setLaunchOnStartup_private(const QString &appName, const QString &guiName, bool enable)
|
||||||
{
|
{
|
||||||
QString userAutoStartPath = getUserAutostartDir_private();
|
QString userAutoStartPath = getUserAutostartDir_private();
|
||||||
QString desktopFileLocation = userAutoStartPath + appName + QLatin1String(".desktop");
|
QString desktopFileLocation = userAutoStartPath
|
||||||
|
+ QLatin1String(LINUX_APPLICATION_ID)
|
||||||
|
+ QLatin1String(".desktop");
|
||||||
if (enable) {
|
if (enable) {
|
||||||
if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) {
|
if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) {
|
||||||
qCWarning(lcUtility) << "Could not create autostart folder" << userAutoStartPath;
|
qCWarning(lcUtility) << "Could not create autostart folder" << userAutoStartPath;
|
||||||
|
|||||||
@@ -90,32 +90,13 @@ void setLaunchOnStartup_private(const QString &appName, const QString &guiName,
|
|||||||
// TODO: Right now only detection on toggle/startup, not when windows theme is switched while nextcloud is running
|
// TODO: Right now only detection on toggle/startup, not when windows theme is switched while nextcloud is running
|
||||||
static inline bool hasDarkSystray_private()
|
static inline bool hasDarkSystray_private()
|
||||||
{
|
{
|
||||||
bool hasDarkSystray = true;
|
if(Utility::registryGetKeyValue( HKEY_CURRENT_USER,
|
||||||
// Open registry key first, continue only on success (may be legitimately absent in earlier windows versions)
|
"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||||
HKEY hKey;
|
"SystemUsesLightTheme" ) == 1) {
|
||||||
LONG lRes = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_READ, &hKey);
|
return false;
|
||||||
|
}
|
||||||
// classical windows function - preserve buff size for DWORD, call ExW version, store regkey value in nResult
|
else {
|
||||||
if (lRes == ERROR_SUCCESS) {
|
return true;
|
||||||
DWORD dwBufferSize(sizeof(DWORD));
|
|
||||||
DWORD nResult(0);
|
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexw
|
|
||||||
LONG nError = ::RegQueryValueExW(hKey,
|
|
||||||
L"SystemUsesLightTheme",
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
reinterpret_cast<LPBYTE>(&nResult),
|
|
||||||
&dwBufferSize);
|
|
||||||
|
|
||||||
// if RegQuery returned no error and light theme was found, change systray return value
|
|
||||||
if (nError == ERROR_SUCCESS && nResult == 1)
|
|
||||||
hasDarkSystray = false;
|
|
||||||
|
|
||||||
return hasDarkSystray;
|
|
||||||
} else {
|
|
||||||
// fallback to true if regkey could not be determined
|
|
||||||
return hasDarkSystray;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,4 +264,13 @@ bool Utility::registryWalkSubKeys(HKEY hRootKey, const QString &subKey, const st
|
|||||||
return retCode != ERROR_NO_MORE_ITEMS;
|
return retCode != ERROR_NO_MORE_ITEMS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DWORD Utility::convertSizeToDWORD(size_t &convertVar)
|
||||||
|
{
|
||||||
|
if( convertVar > UINT_MAX ) {
|
||||||
|
//throw std::bad_cast();
|
||||||
|
convertVar = UINT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD
|
||||||
|
}
|
||||||
|
return static_cast<DWORD>(convertVar);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ static void csync_exclude_expand_escapes(QByteArray &input)
|
|||||||
line[o++] = line[i];
|
line[o++] = line[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input.resize(o);
|
input.resize(OCC::Utility::convertSizeToUint(o));
|
||||||
}
|
}
|
||||||
|
|
||||||
// See http://support.microsoft.com/kb/74496 and
|
// See http://support.microsoft.com/kb/74496 and
|
||||||
@@ -288,7 +288,11 @@ void ExcludedFiles::addManualExclude(const QByteArray &expr)
|
|||||||
|
|
||||||
void ExcludedFiles::addManualExclude(const QByteArray &expr, const QByteArray &basePath)
|
void ExcludedFiles::addManualExclude(const QByteArray &expr, const QByteArray &basePath)
|
||||||
{
|
{
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
Q_ASSERT(basePath.size() >= 2 && basePath.at(1) == ':');
|
||||||
|
#else
|
||||||
Q_ASSERT(basePath.startsWith('/'));
|
Q_ASSERT(basePath.startsWith('/'));
|
||||||
|
#endif
|
||||||
Q_ASSERT(basePath.endsWith('/'));
|
Q_ASSERT(basePath.endsWith('/'));
|
||||||
|
|
||||||
auto key = basePath;
|
auto key = basePath;
|
||||||
@@ -322,7 +326,11 @@ bool ExcludedFiles::loadExcludeFile(const QByteArray & basePath, const QString &
|
|||||||
csync_exclude_expand_escapes(line);
|
csync_exclude_expand_escapes(line);
|
||||||
_allExcludes[basePath].append(line);
|
_allExcludes[basePath].append(line);
|
||||||
}
|
}
|
||||||
prepare(basePath);
|
|
||||||
|
// nothing to prepare if the user decided to not exclude anything
|
||||||
|
if(_allExcludes.size())
|
||||||
|
prepare(basePath);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,8 +346,8 @@ bool ExcludedFiles::reloadExcludeFiles()
|
|||||||
_fullRegexDir.clear();
|
_fullRegexDir.clear();
|
||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
for (auto basePath : _excludeFiles.keys()) {
|
for (const auto& basePath : _excludeFiles.keys()) {
|
||||||
for (auto file : _excludeFiles.value(basePath)) {
|
for (const auto& file : _excludeFiles.value(basePath)) {
|
||||||
success = loadExcludeFile(basePath, file);
|
success = loadExcludeFile(basePath, file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -724,7 +724,8 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
|
|||||||
if (ctx->current == LOCAL_REPLICA) {
|
if (ctx->current == LOCAL_REPLICA) {
|
||||||
ASSERT(dirent->path.startsWith(ctx->local.uri)); // path is relative to uri
|
ASSERT(dirent->path.startsWith(ctx->local.uri)); // path is relative to uri
|
||||||
// "len + 1" to include the slash in-between.
|
// "len + 1" to include the slash in-between.
|
||||||
dirent->path = dirent->path.mid(strlen(ctx->local.uri) + 1);
|
size_t uriLength = strlen(ctx->local.uri);
|
||||||
|
dirent->path = dirent->path.mid(OCC::Utility::convertSizeToInt(uriLength) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
previous_fs = ctx->current_fs;
|
previous_fs = ctx->current_fs;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
#include "c_alloc.h"
|
#include "c_alloc.h"
|
||||||
#include "c_string.h"
|
#include "c_string.h"
|
||||||
#include "common/filesystembase.h"
|
#include "common/filesystembase.h"
|
||||||
|
#include "common/utility.h"
|
||||||
|
|
||||||
/* Convert a locale String to UTF8 */
|
/* Convert a locale String to UTF8 */
|
||||||
QByteArray c_utf8_from_locale(const mbchar_t *wstr)
|
QByteArray c_utf8_from_locale(const mbchar_t *wstr)
|
||||||
@@ -52,10 +53,10 @@ QByteArray c_utf8_from_locale(const mbchar_t *wstr)
|
|||||||
size_t len;
|
size_t len;
|
||||||
len = wcslen(wstr);
|
len = wcslen(wstr);
|
||||||
/* Call once to get the required size. */
|
/* Call once to get the required size. */
|
||||||
size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr, len, NULL, 0, NULL, NULL);
|
size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr, OCC::Utility::convertSizeToInt(len), NULL, 0, NULL, NULL);
|
||||||
if (size_needed > 0) {
|
if (size_needed > 0) {
|
||||||
dst.resize(size_needed);
|
dst.resize(size_needed);
|
||||||
WideCharToMultiByte(CP_UTF8, 0, wstr, len, dst.data(), size_needed, NULL, NULL);
|
WideCharToMultiByte(CP_UTF8, 0, wstr, OCC::Utility::convertSizeToInt(len), dst.data(), size_needed, NULL, NULL);
|
||||||
}
|
}
|
||||||
return dst;
|
return dst;
|
||||||
#else
|
#else
|
||||||
@@ -95,7 +96,7 @@ mbchar_t* c_utf8_string_to_locale(const char *str)
|
|||||||
int size_needed;
|
int size_needed;
|
||||||
|
|
||||||
len = strlen(str);
|
len = strlen(str);
|
||||||
size_needed = MultiByteToWideChar(CP_UTF8, 0, str, len, NULL, 0);
|
size_needed = MultiByteToWideChar(CP_UTF8, 0, str, OCC::Utility::convertSizeToInt(len), NULL, 0);
|
||||||
if (size_needed > 0) {
|
if (size_needed > 0) {
|
||||||
int size_char = (size_needed + 1) * sizeof(mbchar_t);
|
int size_char = (size_needed + 1) * sizeof(mbchar_t);
|
||||||
dst = (mbchar_t*)c_malloc(size_char);
|
dst = (mbchar_t*)c_malloc(size_char);
|
||||||
@@ -114,7 +115,8 @@ mbchar_t* c_utf8_string_to_locale(const char *str)
|
|||||||
return NULL;
|
return NULL;
|
||||||
} else {
|
} else {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
QByteArray unc_str = OCC::FileSystem::pathtoUNC(QByteArray::fromRawData(str, strlen(str)));
|
size_t strLength = strlen(str);
|
||||||
|
QByteArray unc_str = OCC::FileSystem::pathtoUNC(QByteArray::fromRawData(str, OCC::Utility::convertSizeToInt(strLength)));
|
||||||
mbchar_t *dst = c_utf8_string_to_locale(unc_str);
|
mbchar_t *dst = c_utf8_string_to_locale(unc_str);
|
||||||
return dst;
|
return dst;
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ csync_vio_handle_t *csync_vio_local_opendir(const char *name) {
|
|||||||
handle = (dhandle_t*)c_malloc(sizeof(dhandle_t));
|
handle = (dhandle_t*)c_malloc(sizeof(dhandle_t));
|
||||||
|
|
||||||
// the file wildcard has to be attached
|
// the file wildcard has to be attached
|
||||||
int len_name = strlen(name);
|
size_t len_name = strlen(name);
|
||||||
if( len_name ) {
|
if( len_name ) {
|
||||||
char *h = NULL;
|
char *h = NULL;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
project(gui)
|
project(gui)
|
||||||
find_package(Qt5 REQUIRED COMPONENTS Widgets)
|
find_package(Qt5 REQUIRED COMPONENTS Widgets Svg)
|
||||||
set(CMAKE_AUTOMOC TRUE)
|
set(CMAKE_AUTOMOC TRUE)
|
||||||
set(CMAKE_AUTOUIC TRUE)
|
set(CMAKE_AUTOUIC TRUE)
|
||||||
set(CMAKE_AUTORCC TRUE)
|
set(CMAKE_AUTORCC TRUE)
|
||||||
@@ -24,7 +24,6 @@ set(client_UI_SRCS
|
|||||||
ignorelisteditor.ui
|
ignorelisteditor.ui
|
||||||
ignorelisttablewidget.ui
|
ignorelisttablewidget.ui
|
||||||
networksettings.ui
|
networksettings.ui
|
||||||
activitywidget.ui
|
|
||||||
synclogdialog.ui
|
synclogdialog.ui
|
||||||
settingsdialog.ui
|
settingsdialog.ui
|
||||||
sharedialog.ui
|
sharedialog.ui
|
||||||
@@ -35,12 +34,13 @@ set(client_UI_SRCS
|
|||||||
addcertificatedialog.ui
|
addcertificatedialog.ui
|
||||||
proxyauthdialog.ui
|
proxyauthdialog.ui
|
||||||
mnemonicdialog.ui
|
mnemonicdialog.ui
|
||||||
|
tray/Window.qml
|
||||||
|
tray/UserLine.qml
|
||||||
|
wizard/flow2authwidget.ui
|
||||||
wizard/owncloudadvancedsetuppage.ui
|
wizard/owncloudadvancedsetuppage.ui
|
||||||
wizard/owncloudconnectionmethoddialog.ui
|
wizard/owncloudconnectionmethoddialog.ui
|
||||||
wizard/owncloudhttpcredspage.ui
|
wizard/owncloudhttpcredspage.ui
|
||||||
wizard/owncloudoauthcredspage.ui
|
wizard/owncloudoauthcredspage.ui
|
||||||
wizard/flow2authcredspage.ui
|
|
||||||
wizard/flow2authwidget.ui
|
|
||||||
wizard/owncloudsetupnocredspage.ui
|
wizard/owncloudsetupnocredspage.ui
|
||||||
wizard/owncloudwizardresultpage.ui
|
wizard/owncloudwizardresultpage.ui
|
||||||
wizard/webview.ui
|
wizard/webview.ui
|
||||||
@@ -74,10 +74,6 @@ set(client_SRCS
|
|||||||
openfilemanager.cpp
|
openfilemanager.cpp
|
||||||
owncloudgui.cpp
|
owncloudgui.cpp
|
||||||
owncloudsetupwizard.cpp
|
owncloudsetupwizard.cpp
|
||||||
activitydata.cpp
|
|
||||||
activitylistmodel.cpp
|
|
||||||
activitywidget.cpp
|
|
||||||
activityitemdelegate.cpp
|
|
||||||
selectivesyncdialog.cpp
|
selectivesyncdialog.cpp
|
||||||
settingsdialog.cpp
|
settingsdialog.cpp
|
||||||
sharedialog.cpp
|
sharedialog.cpp
|
||||||
@@ -100,14 +96,20 @@ set(client_SRCS
|
|||||||
synclogdialog.cpp
|
synclogdialog.cpp
|
||||||
tooltipupdater.cpp
|
tooltipupdater.cpp
|
||||||
notificationconfirmjob.cpp
|
notificationconfirmjob.cpp
|
||||||
servernotificationhandler.cpp
|
|
||||||
guiutility.cpp
|
guiutility.cpp
|
||||||
elidedlabel.cpp
|
elidedlabel.cpp
|
||||||
|
headerbanner.cpp
|
||||||
iconjob.cpp
|
iconjob.cpp
|
||||||
|
remotewipe.cpp
|
||||||
|
tray/ActivityData.cpp
|
||||||
|
tray/ActivityListModel.cpp
|
||||||
|
tray/UserModel.cpp
|
||||||
|
tray/NotificationHandler.cpp
|
||||||
creds/credentialsfactory.cpp
|
creds/credentialsfactory.cpp
|
||||||
creds/httpcredentialsgui.cpp
|
creds/httpcredentialsgui.cpp
|
||||||
creds/oauth.cpp
|
creds/oauth.cpp
|
||||||
creds/flow2auth.cpp
|
creds/flow2auth.cpp
|
||||||
|
creds/keychainchunk.cpp
|
||||||
creds/webflowcredentials.cpp
|
creds/webflowcredentials.cpp
|
||||||
creds/webflowcredentialsdialog.cpp
|
creds/webflowcredentialsdialog.cpp
|
||||||
wizard/postfixlineedit.cpp
|
wizard/postfixlineedit.cpp
|
||||||
@@ -144,8 +146,6 @@ set(updater_SRCS
|
|||||||
|
|
||||||
IF( APPLE )
|
IF( APPLE )
|
||||||
list(APPEND client_SRCS cocoainitializer_mac.mm)
|
list(APPEND client_SRCS cocoainitializer_mac.mm)
|
||||||
list(APPEND client_SRCS settingsdialogmac.cpp)
|
|
||||||
list(REMOVE_ITEM client_SRCS settingsdialog.cpp)
|
|
||||||
list(APPEND client_SRCS socketapisocket_mac.mm)
|
list(APPEND client_SRCS socketapisocket_mac.mm)
|
||||||
list(APPEND client_SRCS systray.mm)
|
list(APPEND client_SRCS systray.mm)
|
||||||
|
|
||||||
@@ -175,14 +175,6 @@ set(3rdparty_SRC
|
|||||||
../3rdparty/kmessagewidget/kmessagewidget.cpp
|
../3rdparty/kmessagewidget/kmessagewidget.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if (APPLE)
|
|
||||||
list(APPEND 3rdparty_SRC
|
|
||||||
../3rdparty/qtmacgoodies/src/macpreferenceswindow.mm
|
|
||||||
../3rdparty/qtmacgoodies/src/macstandardicon.mm
|
|
||||||
../3rdparty/qtmacgoodies/src/macwindow.mm
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT WIN32)
|
if(NOT WIN32)
|
||||||
list(APPEND 3rdparty_SRC ../3rdparty/qtlockedfile/qtlockedfile_unix.cpp)
|
list(APPEND 3rdparty_SRC ../3rdparty/qtlockedfile/qtlockedfile_unix.cpp)
|
||||||
else()
|
else()
|
||||||
@@ -306,7 +298,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(updater STATIC ${updater_SRCS})
|
add_library(updater STATIC ${updater_SRCS})
|
||||||
target_link_libraries(updater ${synclib_NAME} Qt5::Widgets Qt5::Network Qt5::Xml Qt5::WebEngineWidgets)
|
target_link_libraries(updater ${synclib_NAME} Qt5::Widgets Qt5::Svg Qt5::Network Qt5::Xml Qt5::WebEngineWidgets)
|
||||||
target_include_directories(updater PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(updater PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
|
set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
|
||||||
@@ -316,7 +308,7 @@ set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
|
|||||||
set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
|
set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
|
||||||
INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE};${CMAKE_INSTALL_RPATH}" )
|
INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE};${CMAKE_INSTALL_RPATH}" )
|
||||||
|
|
||||||
target_link_libraries( ${APPLICATION_EXECUTABLE} Qt5::Widgets Qt5::Network Qt5::Xml)
|
target_link_libraries( ${APPLICATION_EXECUTABLE} Qt5::Widgets Qt5::Svg Qt5::Network Qt5::Xml)
|
||||||
target_link_libraries( ${APPLICATION_EXECUTABLE} ${synclib_NAME} )
|
target_link_libraries( ${APPLICATION_EXECUTABLE} ${synclib_NAME} )
|
||||||
target_link_libraries( ${APPLICATION_EXECUTABLE} updater )
|
target_link_libraries( ${APPLICATION_EXECUTABLE} updater )
|
||||||
target_link_libraries( ${APPLICATION_EXECUTABLE} ${OS_SPECIFIC_LINK_LIBRARIES} )
|
target_link_libraries( ${APPLICATION_EXECUTABLE} ${OS_SPECIFIC_LINK_LIBRARIES} )
|
||||||
@@ -338,7 +330,6 @@ ENDIF()
|
|||||||
target_include_directories(${APPLICATION_EXECUTABLE} PRIVATE
|
target_include_directories(${APPLICATION_EXECUTABLE} PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/src/3rdparty/QProgressIndicator
|
${CMAKE_SOURCE_DIR}/src/3rdparty/QProgressIndicator
|
||||||
${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile
|
${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile
|
||||||
${CMAKE_SOURCE_DIR}/src/3rdparty/qtmacgoodies/src
|
|
||||||
${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication
|
${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication
|
||||||
${CMAKE_SOURCE_DIR}/src/3rdparty/kmessagewidget
|
${CMAKE_SOURCE_DIR}/src/3rdparty/kmessagewidget
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
@@ -397,7 +388,7 @@ endif()
|
|||||||
|
|
||||||
if(NOT BUILD_OWNCLOUD_OSX_BUNDLE AND NOT WIN32)
|
if(NOT BUILD_OWNCLOUD_OSX_BUNDLE AND NOT WIN32)
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/mirall.desktop.in
|
configure_file(${CMAKE_SOURCE_DIR}/mirall.desktop.in
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.desktop)
|
${CMAKE_CURRENT_BINARY_DIR}/${LINUX_APPLICATION_ID}.desktop)
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.desktop DESTINATION ${DATADIR}/applications )
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${LINUX_APPLICATION_ID}.desktop DESTINATION ${DATADIR}/applications )
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -368,6 +368,7 @@ void AccountManager::shutdown()
|
|||||||
_accounts.clear();
|
_accounts.clear();
|
||||||
foreach (const auto &acc, accountsCopy) {
|
foreach (const auto &acc, accountsCopy) {
|
||||||
emit accountRemoved(acc.data());
|
emit accountRemoved(acc.data());
|
||||||
|
emit removeAccountFolders(acc.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
void deleteAccount(AccountState *account);
|
void deleteAccount(AccountState *account);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an account and sets up some basic handlers.
|
* Creates an account and sets up some basic handlers.
|
||||||
* Does *not* add the account to the account manager just yet.
|
* Does *not* add the account to the account manager just yet.
|
||||||
@@ -90,6 +89,9 @@ private:
|
|||||||
// Adds an account to the tracked list, emitting accountAdded()
|
// Adds an account to the tracked list, emitting accountAdded()
|
||||||
void addAccountState(AccountState *accountState);
|
void addAccountState(AccountState *accountState);
|
||||||
|
|
||||||
|
AccountManager() {}
|
||||||
|
QList<AccountStatePtr> _accounts;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/// Saves account data, not including the credentials
|
/// Saves account data, not including the credentials
|
||||||
void saveAccount(Account *a);
|
void saveAccount(Account *a);
|
||||||
@@ -104,9 +106,6 @@ public slots:
|
|||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void accountAdded(AccountState *account);
|
void accountAdded(AccountState *account);
|
||||||
void accountRemoved(AccountState *account);
|
void accountRemoved(AccountState *account);
|
||||||
|
void removeAccountFolders(AccountState *account);
|
||||||
private:
|
|
||||||
AccountManager() {}
|
|
||||||
QList<AccountStatePtr> _accounts;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,10 +57,6 @@
|
|||||||
|
|
||||||
#include "account.h"
|
#include "account.h"
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
#include "settingsdialogmac.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(lcAccountSettings, "nextcloud.gui.account.settings", QtInfoMsg)
|
Q_LOGGING_CATEGORY(lcAccountSettings, "nextcloud.gui.account.settings", QtInfoMsg)
|
||||||
@@ -113,13 +109,13 @@ protected:
|
|||||||
|
|
||||||
AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
|
AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, ui(new Ui::AccountSettings)
|
, _ui(new Ui::AccountSettings)
|
||||||
, _wasDisabledBefore(false)
|
, _wasDisabledBefore(false)
|
||||||
, _accountState(accountState)
|
, _accountState(accountState)
|
||||||
, _quotaInfo(accountState)
|
, _quotaInfo(accountState)
|
||||||
, _menuShown(false)
|
, _menuShown(false)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
_ui->setupUi(this);
|
||||||
|
|
||||||
_model = new FolderStatusModel;
|
_model = new FolderStatusModel;
|
||||||
_model->setAccountState(_accountState);
|
_model->setAccountState(_accountState);
|
||||||
@@ -127,35 +123,37 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
|
|||||||
FolderStatusDelegate *delegate = new FolderStatusDelegate;
|
FolderStatusDelegate *delegate = new FolderStatusDelegate;
|
||||||
delegate->setParent(this);
|
delegate->setParent(this);
|
||||||
|
|
||||||
ui->_folderList->header()->hide();
|
// Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching)
|
||||||
ui->_folderList->setItemDelegate(delegate);
|
connect(this, &AccountSettings::styleChanged, delegate, &FolderStatusDelegate::slotStyleChanged);
|
||||||
ui->_folderList->setModel(_model);
|
|
||||||
|
_ui->_folderList->header()->hide();
|
||||||
|
_ui->_folderList->setItemDelegate(delegate);
|
||||||
|
_ui->_folderList->setModel(_model);
|
||||||
#if defined(Q_OS_MAC)
|
#if defined(Q_OS_MAC)
|
||||||
ui->_folderList->setMinimumWidth(400);
|
_ui->_folderList->setMinimumWidth(400);
|
||||||
#else
|
#else
|
||||||
ui->_folderList->setMinimumWidth(300);
|
_ui->_folderList->setMinimumWidth(300);
|
||||||
#endif
|
#endif
|
||||||
new ToolTipUpdater(ui->_folderList);
|
new ToolTipUpdater(_ui->_folderList);
|
||||||
|
|
||||||
auto mouseCursorChanger = new MouseCursorChanger(this);
|
auto mouseCursorChanger = new MouseCursorChanger(this);
|
||||||
mouseCursorChanger->folderList = ui->_folderList;
|
mouseCursorChanger->folderList = _ui->_folderList;
|
||||||
mouseCursorChanger->model = _model;
|
mouseCursorChanger->model = _model;
|
||||||
ui->_folderList->setMouseTracking(true);
|
_ui->_folderList->setMouseTracking(true);
|
||||||
ui->_folderList->setAttribute(Qt::WA_Hover, true);
|
_ui->_folderList->setAttribute(Qt::WA_Hover, true);
|
||||||
ui->_folderList->installEventFilter(mouseCursorChanger);
|
_ui->_folderList->installEventFilter(mouseCursorChanger);
|
||||||
|
|
||||||
createAccountToolbox();
|
connect(this, &AccountSettings::removeAccountFolders,
|
||||||
connect(AccountManager::instance(), &AccountManager::accountAdded,
|
AccountManager::instance(), &AccountManager::removeAccountFolders);
|
||||||
this, &AccountSettings::slotAccountAdded);
|
connect(_ui->_folderList, &QWidget::customContextMenuRequested,
|
||||||
connect(ui->_folderList, &QWidget::customContextMenuRequested,
|
|
||||||
this, &AccountSettings::slotCustomContextMenuRequested);
|
this, &AccountSettings::slotCustomContextMenuRequested);
|
||||||
connect(ui->_folderList, &QAbstractItemView::clicked,
|
connect(_ui->_folderList, &QAbstractItemView::clicked,
|
||||||
this, &AccountSettings::slotFolderListClicked);
|
this, &AccountSettings::slotFolderListClicked);
|
||||||
connect(ui->_folderList, &QTreeView::expanded, this, &AccountSettings::refreshSelectiveSyncStatus);
|
connect(_ui->_folderList, &QTreeView::expanded, this, &AccountSettings::refreshSelectiveSyncStatus);
|
||||||
connect(ui->_folderList, &QTreeView::collapsed, this, &AccountSettings::refreshSelectiveSyncStatus);
|
connect(_ui->_folderList, &QTreeView::collapsed, this, &AccountSettings::refreshSelectiveSyncStatus);
|
||||||
connect(ui->selectiveSyncNotification, &QLabel::linkActivated,
|
connect(_ui->selectiveSyncNotification, &QLabel::linkActivated,
|
||||||
this, &AccountSettings::slotLinkActivated);
|
this, &AccountSettings::slotLinkActivated);
|
||||||
connect(_model, &FolderStatusModel::suggestExpand, ui->_folderList, &QTreeView::expand);
|
connect(_model, &FolderStatusModel::suggestExpand, _ui->_folderList, &QTreeView::expand);
|
||||||
connect(_model, &FolderStatusModel::dirtyChanged, this, &AccountSettings::refreshSelectiveSyncStatus);
|
connect(_model, &FolderStatusModel::dirtyChanged, this, &AccountSettings::refreshSelectiveSyncStatus);
|
||||||
refreshSelectiveSyncStatus();
|
refreshSelectiveSyncStatus();
|
||||||
connect(_model, &QAbstractItemModel::rowsInserted,
|
connect(_model, &QAbstractItemModel::rowsInserted,
|
||||||
@@ -172,20 +170,21 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
|
|||||||
addAction(syncNowWithRemoteDiscovery);
|
addAction(syncNowWithRemoteDiscovery);
|
||||||
|
|
||||||
|
|
||||||
connect(ui->selectiveSyncApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync);
|
connect(_ui->selectiveSyncApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync);
|
||||||
connect(ui->selectiveSyncCancel, &QAbstractButton::clicked, _model, &FolderStatusModel::resetFolders);
|
connect(_ui->selectiveSyncCancel, &QAbstractButton::clicked, _model, &FolderStatusModel::resetFolders);
|
||||||
connect(ui->bigFolderApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync);
|
connect(_ui->bigFolderApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync);
|
||||||
connect(ui->bigFolderSyncAll, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncAllPendingBigFolders);
|
connect(_ui->bigFolderSyncAll, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncAllPendingBigFolders);
|
||||||
connect(ui->bigFolderSyncNone, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncNoPendingBigFolders);
|
connect(_ui->bigFolderSyncNone, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncNoPendingBigFolders);
|
||||||
|
|
||||||
connect(FolderMan::instance(), &FolderMan::folderListChanged, _model, &FolderStatusModel::resetFolders);
|
connect(FolderMan::instance(), &FolderMan::folderListChanged, _model, &FolderStatusModel::resetFolders);
|
||||||
connect(this, &AccountSettings::folderChanged, _model, &FolderStatusModel::resetFolders);
|
connect(this, &AccountSettings::folderChanged, _model, &FolderStatusModel::resetFolders);
|
||||||
|
|
||||||
|
|
||||||
QColor color = palette().highlight().color();
|
// quotaProgressBar style now set in customizeStyle()
|
||||||
ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name()));
|
/*QColor color = palette().highlight().color();
|
||||||
|
_ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name()));*/
|
||||||
|
|
||||||
ui->connectLabel->setText(tr("No account configured."));
|
_ui->connectLabel->setText(tr("No account configured."));
|
||||||
|
|
||||||
connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged);
|
connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged);
|
||||||
slotAccountStateChanged();
|
slotAccountStateChanged();
|
||||||
@@ -202,70 +201,31 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
|
|||||||
{
|
{
|
||||||
slotNewMnemonicGenerated();
|
slotNewMnemonicGenerated();
|
||||||
} else {
|
} else {
|
||||||
ui->encryptionMessage->hide();
|
_ui->encryptionMessage->hide();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
connect(UserModel::instance(), &UserModel::addAccount,
|
||||||
|
this, &AccountSettings::slotOpenAccountWizard);
|
||||||
|
|
||||||
void AccountSettings::createAccountToolbox()
|
customizeStyle();
|
||||||
{
|
|
||||||
QMenu *menu = new QMenu();
|
|
||||||
|
|
||||||
connect(menu, &QMenu::aboutToShow, this, &AccountSettings::slotMenuBeforeShow);
|
|
||||||
|
|
||||||
_addAccountAction = new QAction(tr("Add new"), this);
|
|
||||||
menu->addAction(_addAccountAction);
|
|
||||||
connect(_addAccountAction, &QAction::triggered, this, &AccountSettings::slotOpenAccountWizard);
|
|
||||||
|
|
||||||
_toggleSignInOutAction = new QAction(tr("Log out"), this);
|
|
||||||
connect(_toggleSignInOutAction, &QAction::triggered, this, &AccountSettings::slotToggleSignInState);
|
|
||||||
menu->addAction(_toggleSignInOutAction);
|
|
||||||
|
|
||||||
QAction *action = new QAction(tr("Remove"), this);
|
|
||||||
menu->addAction(action);
|
|
||||||
connect(action, &QAction::triggered, this, &AccountSettings::slotDeleteAccount);
|
|
||||||
|
|
||||||
ui->_accountToolbox->setText(tr("Account") + QLatin1Char(' '));
|
|
||||||
ui->_accountToolbox->setMenu(menu);
|
|
||||||
ui->_accountToolbox->setPopupMode(QToolButton::InstantPopup);
|
|
||||||
|
|
||||||
slotAccountAdded(_accountState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AccountSettings::slotNewMnemonicGenerated()
|
void AccountSettings::slotNewMnemonicGenerated()
|
||||||
{
|
{
|
||||||
ui->encryptionMessage->setText(tr("This account supports end-to-end encryption"));
|
_ui->encryptionMessage->setText(tr("This account supports end-to-end encryption"));
|
||||||
|
|
||||||
QAction *mnemonic = new QAction(tr("Enable encryption"), this);
|
QAction *mnemonic = new QAction(tr("Enable encryption"), this);
|
||||||
connect(mnemonic, &QAction::triggered, this, &AccountSettings::requesetMnemonic);
|
connect(mnemonic, &QAction::triggered, this, &AccountSettings::requesetMnemonic);
|
||||||
connect(mnemonic, &QAction::triggered, ui->encryptionMessage, &KMessageWidget::hide);
|
connect(mnemonic, &QAction::triggered, _ui->encryptionMessage, &KMessageWidget::hide);
|
||||||
|
|
||||||
ui->encryptionMessage->addAction(mnemonic);
|
_ui->encryptionMessage->addAction(mnemonic);
|
||||||
ui->encryptionMessage->show();
|
_ui->encryptionMessage->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountSettings::slotMenuBeforeShow() {
|
|
||||||
if (_menuShown) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto menu = ui->_accountToolbox->menu();
|
|
||||||
|
|
||||||
// We can't check this during the initial creation as there is no account yet then
|
|
||||||
if (_accountState->account()->capabilities().clientSideEncryptionAvaliable()) {
|
|
||||||
QAction *mnemonic = new QAction(tr("Show E2E mnemonic"), this);
|
|
||||||
connect(mnemonic, &QAction::triggered, this, &AccountSettings::requesetMnemonic);
|
|
||||||
menu->addAction(mnemonic);
|
|
||||||
}
|
|
||||||
|
|
||||||
_menuShown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QString AccountSettings::selectedFolderAlias() const
|
QString AccountSettings::selectedFolderAlias() const
|
||||||
{
|
{
|
||||||
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
|
QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
|
||||||
if (!selected.isValid())
|
if (!selected.isValid())
|
||||||
return "";
|
return "";
|
||||||
return _model->data(selected, FolderStatusDelegate::FolderAliasRole).toString();
|
return _model->data(selected, FolderStatusDelegate::FolderAliasRole).toString();
|
||||||
@@ -278,16 +238,7 @@ void AccountSettings::slotOpenAccountWizard()
|
|||||||
if (qgetenv("QT_QPA_PLATFORMTHEME") == "appmenu-qt5" || QSystemTrayIcon::isSystemTrayAvailable()) {
|
if (qgetenv("QT_QPA_PLATFORMTHEME") == "appmenu-qt5" || QSystemTrayIcon::isSystemTrayAvailable()) {
|
||||||
topLevelWidget()->close();
|
topLevelWidget()->close();
|
||||||
}
|
}
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
qCDebug(lcAccountSettings) << parent() << topLevelWidget();
|
|
||||||
SettingsDialogMac *sd = qobject_cast<SettingsDialogMac *>(topLevelWidget());
|
|
||||||
|
|
||||||
if (sd) {
|
|
||||||
sd->showActivityPage();
|
|
||||||
} else {
|
|
||||||
qFatal("nope");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)), nullptr);
|
OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)), nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +254,7 @@ void AccountSettings::slotToggleSignInState()
|
|||||||
|
|
||||||
void AccountSettings::doExpand()
|
void AccountSettings::doExpand()
|
||||||
{
|
{
|
||||||
ui->_folderList->expandToDepth(0);
|
_ui->_folderList->expandToDepth(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountSettings::slotShowMnemonic(const QString &mnemonic) {
|
void AccountSettings::slotShowMnemonic(const QString &mnemonic) {
|
||||||
@@ -334,9 +285,9 @@ void AccountSettings::slotEncryptionFlagError(const QByteArray& fileId, int http
|
|||||||
|
|
||||||
void AccountSettings::slotLockForEncryptionSuccess(const QByteArray& fileId, const QByteArray &token)
|
void AccountSettings::slotLockForEncryptionSuccess(const QByteArray& fileId, const QByteArray &token)
|
||||||
{
|
{
|
||||||
accountsState()->account()->e2e()->setTokenForFolder(fileId, token);
|
accountsState()->account()->e2e()->setTokenForFolder(fileId, token);
|
||||||
|
|
||||||
FolderMetadata emptyMetadata(accountsState()->account());
|
FolderMetadata emptyMetadata(accountsState()->account());
|
||||||
auto encryptedMetadata = emptyMetadata.encryptedMetadata();
|
auto encryptedMetadata = emptyMetadata.encryptedMetadata();
|
||||||
if (encryptedMetadata.isEmpty()) {
|
if (encryptedMetadata.isEmpty()) {
|
||||||
//TODO: Mark the folder as unencrypted as the metadata generation failed.
|
//TODO: Mark the folder as unencrypted as the metadata generation failed.
|
||||||
@@ -348,36 +299,36 @@ void AccountSettings::slotLockForEncryptionSuccess(const QByteArray& fileId, con
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto storeMetadataJob = new StoreMetaDataApiJob(accountsState()->account(), fileId, emptyMetadata.encryptedMetadata());
|
auto storeMetadataJob = new StoreMetaDataApiJob(accountsState()->account(), fileId, emptyMetadata.encryptedMetadata());
|
||||||
connect(storeMetadataJob, &StoreMetaDataApiJob::success,
|
connect(storeMetadataJob, &StoreMetaDataApiJob::success,
|
||||||
this, &AccountSettings::slotUploadMetadataSuccess);
|
this, &AccountSettings::slotUploadMetadataSuccess);
|
||||||
connect(storeMetadataJob, &StoreMetaDataApiJob::error,
|
connect(storeMetadataJob, &StoreMetaDataApiJob::error,
|
||||||
this, &AccountSettings::slotUpdateMetadataError);
|
this, &AccountSettings::slotUpdateMetadataError);
|
||||||
|
|
||||||
storeMetadataJob->start();
|
storeMetadataJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountSettings::slotUploadMetadataSuccess(const QByteArray& folderId)
|
void AccountSettings::slotUploadMetadataSuccess(const QByteArray& folderId)
|
||||||
{
|
{
|
||||||
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
|
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
|
||||||
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
|
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
|
||||||
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
|
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
|
||||||
this, &AccountSettings::slotUnlockFolderSuccess);
|
this, &AccountSettings::slotUnlockFolderSuccess);
|
||||||
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
|
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
|
||||||
this, &AccountSettings::slotUnlockFolderError);
|
this, &AccountSettings::slotUnlockFolderError);
|
||||||
unlockJob->start();
|
unlockJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountSettings::slotUpdateMetadataError(const QByteArray& folderId, int httpReturnCode)
|
void AccountSettings::slotUpdateMetadataError(const QByteArray& folderId, int httpReturnCode)
|
||||||
{
|
{
|
||||||
Q_UNUSED(httpReturnCode);
|
Q_UNUSED(httpReturnCode);
|
||||||
|
|
||||||
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
|
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
|
||||||
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
|
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
|
||||||
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
|
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
|
||||||
this, &AccountSettings::slotUnlockFolderSuccess);
|
this, &AccountSettings::slotUnlockFolderSuccess);
|
||||||
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
|
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
|
||||||
this, &AccountSettings::slotUnlockFolderError);
|
this, &AccountSettings::slotUnlockFolderError);
|
||||||
unlockJob->start();
|
unlockJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountSettings::slotLockForEncryptionError(const QByteArray& fileId, int httpErrorCode)
|
void AccountSettings::slotLockForEncryptionError(const QByteArray& fileId, int httpErrorCode)
|
||||||
@@ -551,7 +502,7 @@ void AccountSettings::slotEditCurrentIgnoredFiles()
|
|||||||
|
|
||||||
void AccountSettings::slotEditCurrentLocalIgnoredFiles()
|
void AccountSettings::slotEditCurrentLocalIgnoredFiles()
|
||||||
{
|
{
|
||||||
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
|
QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
|
||||||
if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder)
|
if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder)
|
||||||
return;
|
return;
|
||||||
QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString();
|
QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString();
|
||||||
@@ -623,7 +574,7 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index
|
|||||||
|
|
||||||
void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
||||||
{
|
{
|
||||||
QTreeView *tv = ui->_folderList;
|
QTreeView *tv = _ui->_folderList;
|
||||||
QModelIndex index = tv->indexAt(pos);
|
QModelIndex index = tv->indexAt(pos);
|
||||||
if (!index.isValid()) {
|
if (!index.isValid()) {
|
||||||
return;
|
return;
|
||||||
@@ -654,7 +605,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
|||||||
ac = menu->addAction(tr("Edit Ignored Files"));
|
ac = menu->addAction(tr("Edit Ignored Files"));
|
||||||
connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentIgnoredFiles);
|
connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentIgnoredFiles);
|
||||||
|
|
||||||
if (!ui->_folderList->isExpanded(index)) {
|
if (!_ui->_folderList->isExpanded(index)) {
|
||||||
ac = menu->addAction(tr("Choose what to sync"));
|
ac = menu->addAction(tr("Choose what to sync"));
|
||||||
ac->setEnabled(folderConnected);
|
ac->setEnabled(folderConnected);
|
||||||
connect(ac, &QAction::triggered, this, &AccountSettings::doExpand);
|
connect(ac, &QAction::triggered, this, &AccountSettings::doExpand);
|
||||||
@@ -693,7 +644,7 @@ void AccountSettings::slotFolderListClicked(const QModelIndex &indx)
|
|||||||
}
|
}
|
||||||
if (_model->classify(indx) == FolderStatusModel::RootFolder) {
|
if (_model->classify(indx) == FolderStatusModel::RootFolder) {
|
||||||
// tries to find if we clicked on the '...' button.
|
// tries to find if we clicked on the '...' button.
|
||||||
QTreeView *tv = ui->_folderList;
|
QTreeView *tv = _ui->_folderList;
|
||||||
auto pos = tv->mapFromGlobal(QCursor::pos());
|
auto pos = tv->mapFromGlobal(QCursor::pos());
|
||||||
if (FolderStatusDelegate::optionsButtonRect(tv->visualRect(indx), layoutDirection()).contains(pos)) {
|
if (FolderStatusDelegate::optionsButtonRect(tv->visualRect(indx), layoutDirection()).contains(pos)) {
|
||||||
slotCustomContextMenuRequested(pos);
|
slotCustomContextMenuRequested(pos);
|
||||||
@@ -706,8 +657,8 @@ void AccountSettings::slotFolderListClicked(const QModelIndex &indx)
|
|||||||
|
|
||||||
// Expand root items on single click
|
// Expand root items on single click
|
||||||
if (_accountState && _accountState->state() == AccountState::Connected) {
|
if (_accountState && _accountState->state() == AccountState::Connected) {
|
||||||
bool expanded = !(ui->_folderList->isExpanded(indx));
|
bool expanded = !(_ui->_folderList->isExpanded(indx));
|
||||||
ui->_folderList->setExpanded(indx, expanded);
|
_ui->_folderList->setExpanded(indx, expanded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -744,7 +695,7 @@ void AccountSettings::slotFolderWizardAccepted()
|
|||||||
qCInfo(lcAccountSettings) << "Creating folder" << definition.localPath;
|
qCInfo(lcAccountSettings) << "Creating folder" << definition.localPath;
|
||||||
if (!dir.mkpath(".")) {
|
if (!dir.mkpath(".")) {
|
||||||
QMessageBox::warning(this, tr("Folder creation failed"),
|
QMessageBox::warning(this, tr("Folder creation failed"),
|
||||||
tr("<p>Could not create local folder <i>%1</i>.")
|
tr("<p>Could not create local folder <i>%1</i>.</p>")
|
||||||
.arg(QDir::toNativeSeparators(definition.localPath)));
|
.arg(QDir::toNativeSeparators(definition.localPath)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -789,7 +740,7 @@ void AccountSettings::slotRemoveCurrentFolder()
|
|||||||
{
|
{
|
||||||
FolderMan *folderMan = FolderMan::instance();
|
FolderMan *folderMan = FolderMan::instance();
|
||||||
auto folder = folderMan->folder(selectedFolderAlias());
|
auto folder = folderMan->folder(selectedFolderAlias());
|
||||||
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
|
QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
|
||||||
if (selected.isValid() && folder) {
|
if (selected.isValid() && folder) {
|
||||||
int row = selected.row();
|
int row = selected.row();
|
||||||
|
|
||||||
@@ -831,7 +782,7 @@ void AccountSettings::slotOpenCurrentFolder()
|
|||||||
|
|
||||||
void AccountSettings::slotOpenCurrentLocalSubFolder()
|
void AccountSettings::slotOpenCurrentLocalSubFolder()
|
||||||
{
|
{
|
||||||
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
|
QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
|
||||||
if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder)
|
if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder)
|
||||||
return;
|
return;
|
||||||
QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString();
|
QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString();
|
||||||
@@ -845,18 +796,21 @@ void AccountSettings::showConnectionLabel(const QString &message, QStringList er
|
|||||||
"border-width: 1px; border-style: solid; border-color: #aaaaaa;"
|
"border-width: 1px; border-style: solid; border-color: #aaaaaa;"
|
||||||
"border-radius:5px;");
|
"border-radius:5px;");
|
||||||
if (errors.isEmpty()) {
|
if (errors.isEmpty()) {
|
||||||
ui->connectLabel->setText(message);
|
QString msg = message;
|
||||||
ui->connectLabel->setToolTip(QString());
|
Theme::replaceLinkColorStringBackgroundAware(msg);
|
||||||
ui->connectLabel->setStyleSheet(QString());
|
_ui->connectLabel->setText(msg);
|
||||||
|
_ui->connectLabel->setToolTip(QString());
|
||||||
|
_ui->connectLabel->setStyleSheet(QString());
|
||||||
} else {
|
} else {
|
||||||
errors.prepend(message);
|
errors.prepend(message);
|
||||||
const QString msg = errors.join(QLatin1String("\n"));
|
QString msg = errors.join(QLatin1String("\n"));
|
||||||
qCDebug(lcAccountSettings) << msg;
|
qCDebug(lcAccountSettings) << msg;
|
||||||
ui->connectLabel->setText(msg);
|
Theme::replaceLinkColorString(msg, QColor("#c1c8e6"));
|
||||||
ui->connectLabel->setToolTip(QString());
|
_ui->connectLabel->setText(msg);
|
||||||
ui->connectLabel->setStyleSheet(errStyle);
|
_ui->connectLabel->setToolTip(QString());
|
||||||
|
_ui->connectLabel->setStyleSheet(errStyle);
|
||||||
}
|
}
|
||||||
ui->accountStatus->setVisible(!message.isEmpty());
|
_ui->accountStatus->setVisible(!message.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountSettings::slotEnableCurrentFolder()
|
void AccountSettings::slotEnableCurrentFolder()
|
||||||
@@ -954,29 +908,29 @@ void AccountSettings::slotOpenOC()
|
|||||||
void AccountSettings::slotUpdateQuota(qint64 total, qint64 used)
|
void AccountSettings::slotUpdateQuota(qint64 total, qint64 used)
|
||||||
{
|
{
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
ui->quotaProgressBar->setVisible(true);
|
_ui->quotaProgressBar->setVisible(true);
|
||||||
ui->quotaProgressBar->setEnabled(true);
|
_ui->quotaProgressBar->setEnabled(true);
|
||||||
// workaround the label only accepting ints (which may be only 32 bit wide)
|
// workaround the label only accepting ints (which may be only 32 bit wide)
|
||||||
const double percent = used / (double)total * 100;
|
const double percent = used / (double)total * 100;
|
||||||
const int percentInt = qMin(qRound(percent), 100);
|
const int percentInt = qMin(qRound(percent), 100);
|
||||||
ui->quotaProgressBar->setValue(percentInt);
|
_ui->quotaProgressBar->setValue(percentInt);
|
||||||
QString usedStr = Utility::octetsToString(used);
|
QString usedStr = Utility::octetsToString(used);
|
||||||
QString totalStr = Utility::octetsToString(total);
|
QString totalStr = Utility::octetsToString(total);
|
||||||
QString percentStr = Utility::compactFormatDouble(percent, 1);
|
QString percentStr = Utility::compactFormatDouble(percent, 1);
|
||||||
QString toolTip = tr("%1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits.").arg(usedStr, totalStr, percentStr);
|
QString toolTip = tr("%1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits.").arg(usedStr, totalStr, percentStr);
|
||||||
ui->quotaInfoLabel->setText(tr("%1 of %2 in use").arg(usedStr, totalStr));
|
_ui->quotaInfoLabel->setText(tr("%1 of %2 in use").arg(usedStr, totalStr));
|
||||||
ui->quotaInfoLabel->setToolTip(toolTip);
|
_ui->quotaInfoLabel->setToolTip(toolTip);
|
||||||
ui->quotaProgressBar->setToolTip(toolTip);
|
_ui->quotaProgressBar->setToolTip(toolTip);
|
||||||
} else {
|
} else {
|
||||||
ui->quotaProgressBar->setVisible(false);
|
_ui->quotaProgressBar->setVisible(false);
|
||||||
ui->quotaInfoLabel->setToolTip(QString());
|
_ui->quotaInfoLabel->setToolTip(QString());
|
||||||
|
|
||||||
/* -1 means not computed; -2 means unknown; -3 means unlimited (#3940)*/
|
/* -1 means not computed; -2 means unknown; -3 means unlimited (#3940)*/
|
||||||
if (total == 0 || total == -1) {
|
if (total == 0 || total == -1) {
|
||||||
ui->quotaInfoLabel->setText(tr("Currently there is no storage usage information available."));
|
_ui->quotaInfoLabel->setText(tr("Currently there is no storage usage information available."));
|
||||||
} else {
|
} else {
|
||||||
QString usedStr = Utility::octetsToString(used);
|
QString usedStr = Utility::octetsToString(used);
|
||||||
ui->quotaInfoLabel->setText(tr("%1 in use").arg(usedStr));
|
_ui->quotaInfoLabel->setText(tr("%1 in use").arg(usedStr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -985,7 +939,7 @@ void AccountSettings::slotAccountStateChanged()
|
|||||||
{
|
{
|
||||||
int state = _accountState ? _accountState->state() : AccountState::Disconnected;
|
int state = _accountState ? _accountState->state() : AccountState::Disconnected;
|
||||||
if (_accountState) {
|
if (_accountState) {
|
||||||
ui->sslButton->updateAccountState(_accountState);
|
_ui->sslButton->updateAccountState(_accountState);
|
||||||
AccountPtr account = _accountState->account();
|
AccountPtr account = _accountState->account();
|
||||||
QUrl safeUrl(account->url());
|
QUrl safeUrl(account->url());
|
||||||
safeUrl.setPassword(QString()); // Remove the password from the URL to avoid showing it in the UI
|
safeUrl.setPassword(QString()); // Remove the password from the URL to avoid showing it in the UI
|
||||||
@@ -1030,7 +984,7 @@ void AccountSettings::slotAccountStateChanged()
|
|||||||
"<a href='%1'>Click here</a> to re-open the browser.")
|
"<a href='%1'>Click here</a> to re-open the browser.")
|
||||||
.arg(url.toString(QUrl::FullyEncoded)));
|
.arg(url.toString(QUrl::FullyEncoded)));
|
||||||
} else {
|
} else {
|
||||||
showConnectionLabel(tr("Connecting to %1...").arg(serverWithUser));
|
showConnectionLabel(tr("Connecting to %1 …").arg(serverWithUser));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showConnectionLabel(tr("No connection to %1 at %2.")
|
showConnectionLabel(tr("No connection to %1 at %2.")
|
||||||
@@ -1044,14 +998,14 @@ void AccountSettings::slotAccountStateChanged()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Allow to expand the item if the account is connected. */
|
/* Allow to expand the item if the account is connected. */
|
||||||
ui->_folderList->setItemsExpandable(state == AccountState::Connected);
|
_ui->_folderList->setItemsExpandable(state == AccountState::Connected);
|
||||||
|
|
||||||
if (state != AccountState::Connected) {
|
if (state != AccountState::Connected) {
|
||||||
/* check if there are expanded root items, if so, close them */
|
/* check if there are expanded root items, if so, close them */
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < _model->rowCount(); ++i) {
|
for (i = 0; i < _model->rowCount(); ++i) {
|
||||||
if (ui->_folderList->isExpanded(_model->index(i)))
|
if (_ui->_folderList->isExpanded(_model->index(i)))
|
||||||
ui->_folderList->setExpanded(_model->index(i), false);
|
_ui->_folderList->setExpanded(_model->index(i), false);
|
||||||
}
|
}
|
||||||
} else if (_model->isDirty()) {
|
} else if (_model->isDirty()) {
|
||||||
// If we connect and have pending changes, show the list.
|
// If we connect and have pending changes, show the list.
|
||||||
@@ -1062,21 +1016,12 @@ void AccountSettings::slotAccountStateChanged()
|
|||||||
// sync user interface buttons.
|
// sync user interface buttons.
|
||||||
refreshSelectiveSyncStatus();
|
refreshSelectiveSyncStatus();
|
||||||
|
|
||||||
/* set the correct label for the Account toolbox button */
|
|
||||||
if (_accountState) {
|
|
||||||
if (_accountState->isSignedOut()) {
|
|
||||||
_toggleSignInOutAction->setText(tr("Log in"));
|
|
||||||
} else {
|
|
||||||
_toggleSignInOutAction->setText(tr("Log out"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state == AccountState::State::Connected) {
|
if (state == AccountState::State::Connected) {
|
||||||
/* TODO: We should probably do something better here.
|
/* TODO: We should probably do something better here.
|
||||||
* Verify if the user has a private key already uploaded to the server,
|
* Verify if the user has a private key already uploaded to the server,
|
||||||
* if it has, do not offer to create one.
|
* if it has, do not offer to create one.
|
||||||
*/
|
*/
|
||||||
qCInfo(lcAccountSettings) << "Accout" << accountsState()->account()->displayName()
|
qCInfo(lcAccountSettings) << "Account" << accountsState()->account()->displayName()
|
||||||
<< "Client Side Encryption" << accountsState()->account()->capabilities().clientSideEncryptionAvaliable();
|
<< "Client Side Encryption" << accountsState()->account()->capabilities().clientSideEncryptionAvaliable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1095,21 +1040,21 @@ void AccountSettings::slotLinkActivated(const QString &link)
|
|||||||
// Make sure the folder itself is expanded
|
// Make sure the folder itself is expanded
|
||||||
Folder *f = FolderMan::instance()->folder(alias);
|
Folder *f = FolderMan::instance()->folder(alias);
|
||||||
QModelIndex folderIndx = _model->indexForPath(f, QString());
|
QModelIndex folderIndx = _model->indexForPath(f, QString());
|
||||||
if (!ui->_folderList->isExpanded(folderIndx)) {
|
if (!_ui->_folderList->isExpanded(folderIndx)) {
|
||||||
ui->_folderList->setExpanded(folderIndx, true);
|
_ui->_folderList->setExpanded(folderIndx, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
QModelIndex indx = _model->indexForPath(f, myFolder);
|
QModelIndex indx = _model->indexForPath(f, myFolder);
|
||||||
if (indx.isValid()) {
|
if (indx.isValid()) {
|
||||||
// make sure all the parents are expanded
|
// make sure all the parents are expanded
|
||||||
for (auto i = indx.parent(); i.isValid(); i = i.parent()) {
|
for (auto i = indx.parent(); i.isValid(); i = i.parent()) {
|
||||||
if (!ui->_folderList->isExpanded(i)) {
|
if (!_ui->_folderList->isExpanded(i)) {
|
||||||
ui->_folderList->setExpanded(i, true);
|
_ui->_folderList->setExpanded(i, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ui->_folderList->setSelectionMode(QAbstractItemView::SingleSelection);
|
_ui->_folderList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
ui->_folderList->setCurrentIndex(indx);
|
_ui->_folderList->setCurrentIndex(indx);
|
||||||
ui->_folderList->scrollTo(indx);
|
_ui->_folderList->scrollTo(indx);
|
||||||
} else {
|
} else {
|
||||||
qCWarning(lcAccountSettings) << "Unable to find a valid index for " << myFolder;
|
qCWarning(lcAccountSettings) << "Unable to find a valid index for " << myFolder;
|
||||||
}
|
}
|
||||||
@@ -1118,7 +1063,7 @@ void AccountSettings::slotLinkActivated(const QString &link)
|
|||||||
|
|
||||||
AccountSettings::~AccountSettings()
|
AccountSettings::~AccountSettings()
|
||||||
{
|
{
|
||||||
delete ui;
|
delete _ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountSettings::refreshSelectiveSyncStatus()
|
void AccountSettings::refreshSelectiveSyncStatus()
|
||||||
@@ -1156,8 +1101,8 @@ void AccountSettings::refreshSelectiveSyncStatus()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.isEmpty()) {
|
if (msg.isEmpty()) {
|
||||||
ui->selectiveSyncButtons->setVisible(true);
|
_ui->selectiveSyncButtons->setVisible(true);
|
||||||
ui->bigFolderUi->setVisible(false);
|
_ui->bigFolderUi->setVisible(false);
|
||||||
} else {
|
} else {
|
||||||
ConfigFile cfg;
|
ConfigFile cfg;
|
||||||
QString info = !cfg.confirmExternalStorage()
|
QString info = !cfg.confirmExternalStorage()
|
||||||
@@ -1166,44 +1111,32 @@ void AccountSettings::refreshSelectiveSyncStatus()
|
|||||||
? tr("There are folders that were not synchronized because they are external storages: ")
|
? tr("There are folders that were not synchronized because they are external storages: ")
|
||||||
: tr("There are folders that were not synchronized because they are too big or external storages: ");
|
: tr("There are folders that were not synchronized because they are too big or external storages: ");
|
||||||
|
|
||||||
ui->selectiveSyncNotification->setText(info + msg);
|
_ui->selectiveSyncNotification->setText(info + msg);
|
||||||
ui->selectiveSyncButtons->setVisible(false);
|
_ui->selectiveSyncButtons->setVisible(false);
|
||||||
ui->bigFolderUi->setVisible(true);
|
_ui->bigFolderUi->setVisible(true);
|
||||||
shouldBeVisible = true;
|
shouldBeVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->selectiveSyncApply->setEnabled(_model->isDirty() || !msg.isEmpty());
|
_ui->selectiveSyncApply->setEnabled(_model->isDirty() || !msg.isEmpty());
|
||||||
bool wasVisible = !ui->selectiveSyncStatus->isHidden();
|
bool wasVisible = !_ui->selectiveSyncStatus->isHidden();
|
||||||
if (wasVisible != shouldBeVisible) {
|
if (wasVisible != shouldBeVisible) {
|
||||||
QSize hint = ui->selectiveSyncStatus->sizeHint();
|
QSize hint = _ui->selectiveSyncStatus->sizeHint();
|
||||||
if (shouldBeVisible) {
|
if (shouldBeVisible) {
|
||||||
ui->selectiveSyncStatus->setMaximumHeight(0);
|
_ui->selectiveSyncStatus->setMaximumHeight(0);
|
||||||
ui->selectiveSyncStatus->setVisible(true);
|
_ui->selectiveSyncStatus->setVisible(true);
|
||||||
}
|
}
|
||||||
auto anim = new QPropertyAnimation(ui->selectiveSyncStatus, "maximumHeight", ui->selectiveSyncStatus);
|
auto anim = new QPropertyAnimation(_ui->selectiveSyncStatus, "maximumHeight", _ui->selectiveSyncStatus);
|
||||||
anim->setEndValue(shouldBeVisible ? hint.height() : 0);
|
anim->setEndValue(shouldBeVisible ? hint.height() : 0);
|
||||||
anim->start(QAbstractAnimation::DeleteWhenStopped);
|
anim->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
connect(anim, &QPropertyAnimation::finished, [this, shouldBeVisible]() {
|
connect(anim, &QPropertyAnimation::finished, [this, shouldBeVisible]() {
|
||||||
ui->selectiveSyncStatus->setMaximumHeight(QWIDGETSIZE_MAX);
|
_ui->selectiveSyncStatus->setMaximumHeight(QWIDGETSIZE_MAX);
|
||||||
if (!shouldBeVisible) {
|
if (!shouldBeVisible) {
|
||||||
ui->selectiveSyncStatus->hide();
|
_ui->selectiveSyncStatus->hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountSettings::slotAccountAdded(AccountState *)
|
|
||||||
{
|
|
||||||
// if the theme is limited to single account, the button must hide if
|
|
||||||
// there is already one account.
|
|
||||||
int s = AccountManager::instance()->accounts().size();
|
|
||||||
if (s > 0 && !Theme::instance()->multiAccount()) {
|
|
||||||
_addAccountAction->setVisible(false);
|
|
||||||
} else {
|
|
||||||
_addAccountAction->setVisible(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AccountSettings::slotDeleteAccount()
|
void AccountSettings::slotDeleteAccount()
|
||||||
{
|
{
|
||||||
// Deleting the account potentially deletes 'this', so
|
// Deleting the account potentially deletes 'this', so
|
||||||
@@ -1246,12 +1179,30 @@ bool AccountSettings::event(QEvent *e)
|
|||||||
// Expand the folder automatically only if there's only one, see #4283
|
// Expand the folder automatically only if there's only one, see #4283
|
||||||
// The 2 is 1 folder + 1 'add folder' button
|
// The 2 is 1 folder + 1 'add folder' button
|
||||||
if (_model->rowCount() <= 2) {
|
if (_model->rowCount() <= 2) {
|
||||||
ui->_folderList->setExpanded(_model->index(0, 0), true);
|
_ui->_folderList->setExpanded(_model->index(0, 0), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return QWidget::event(e);
|
return QWidget::event(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AccountSettings::slotStyleChanged()
|
||||||
|
{
|
||||||
|
customizeStyle();
|
||||||
|
|
||||||
|
// Notify the other widgets (Dark-/Light-Mode switching)
|
||||||
|
emit styleChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccountSettings::customizeStyle()
|
||||||
|
{
|
||||||
|
QString msg = _ui->connectLabel->text();
|
||||||
|
Theme::replaceLinkColorStringBackgroundAware(msg);
|
||||||
|
_ui->connectLabel->setText(msg);
|
||||||
|
|
||||||
|
QColor color = palette().highlight().color();
|
||||||
|
_ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name()));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|
||||||
#include "accountsettings.moc"
|
#include "accountsettings.moc"
|
||||||
|
|||||||
@@ -63,11 +63,14 @@ signals:
|
|||||||
void openFolderAlias(const QString &);
|
void openFolderAlias(const QString &);
|
||||||
void showIssuesList(AccountState *account);
|
void showIssuesList(AccountState *account);
|
||||||
void requesetMnemonic();
|
void requesetMnemonic();
|
||||||
|
void removeAccountFolders(AccountState *account);
|
||||||
|
void styleChanged();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void slotOpenOC();
|
void slotOpenOC();
|
||||||
void slotUpdateQuota(qint64, qint64);
|
void slotUpdateQuota(qint64, qint64);
|
||||||
void slotAccountStateChanged();
|
void slotAccountStateChanged();
|
||||||
|
void slotStyleChanged();
|
||||||
|
|
||||||
AccountState *accountsState() { return _accountState; }
|
AccountState *accountsState() { return _accountState; }
|
||||||
|
|
||||||
@@ -87,7 +90,6 @@ protected slots:
|
|||||||
void slotDeleteAccount();
|
void slotDeleteAccount();
|
||||||
void slotToggleSignInState();
|
void slotToggleSignInState();
|
||||||
void slotOpenAccountWizard();
|
void slotOpenAccountWizard();
|
||||||
void slotAccountAdded(AccountState *);
|
|
||||||
void refreshSelectiveSyncStatus();
|
void refreshSelectiveSyncStatus();
|
||||||
void slotMarkSubfolderEncrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
void slotMarkSubfolderEncrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||||
void slotMarkSubfolderDecrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
void slotMarkSubfolderDecrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||||
@@ -97,8 +99,6 @@ protected slots:
|
|||||||
void doExpand();
|
void doExpand();
|
||||||
void slotLinkActivated(const QString &link);
|
void slotLinkActivated(const QString &link);
|
||||||
|
|
||||||
void slotMenuBeforeShow();
|
|
||||||
|
|
||||||
// Encryption Related Stuff.
|
// Encryption Related Stuff.
|
||||||
void slotShowMnemonic(const QString &mnemonic);
|
void slotShowMnemonic(const QString &mnemonic);
|
||||||
void slotNewMnemonicGenerated();
|
void slotNewMnemonicGenerated();
|
||||||
@@ -128,11 +128,12 @@ private:
|
|||||||
bool event(QEvent *) override;
|
bool event(QEvent *) override;
|
||||||
void createAccountToolbox();
|
void createAccountToolbox();
|
||||||
void openIgnoredFilesDialog(const QString & absFolderPath);
|
void openIgnoredFilesDialog(const QString & absFolderPath);
|
||||||
|
void customizeStyle();
|
||||||
|
|
||||||
/// Returns the alias of the selected folder, empty string if none
|
/// Returns the alias of the selected folder, empty string if none
|
||||||
QString selectedFolderAlias() const;
|
QString selectedFolderAlias() const;
|
||||||
|
|
||||||
Ui::AccountSettings *ui;
|
Ui::AccountSettings *_ui;
|
||||||
|
|
||||||
FolderStatusModel *_model;
|
FolderStatusModel *_model;
|
||||||
QUrl _OCUrl;
|
QUrl _OCUrl;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>582</width>
|
<width>581</width>
|
||||||
<height>557</height>
|
<height>557</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
@@ -184,13 +184,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="2">
|
|
||||||
<widget class="QToolButton" name="_accountToolbox">
|
|
||||||
<property name="text">
|
|
||||||
<string>...</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -14,16 +14,24 @@
|
|||||||
|
|
||||||
#include "accountstate.h"
|
#include "accountstate.h"
|
||||||
#include "accountmanager.h"
|
#include "accountmanager.h"
|
||||||
|
#include "remotewipe.h"
|
||||||
#include "account.h"
|
#include "account.h"
|
||||||
#include "creds/abstractcredentials.h"
|
#include "creds/abstractcredentials.h"
|
||||||
#include "creds/httpcredentials.h"
|
#include "creds/httpcredentials.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "configfile.h"
|
#include "configfile.h"
|
||||||
|
#include "ocsnavigationappsjob.h"
|
||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <qfontmetrics.h>
|
#include <qfontmetrics.h>
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QBuffer>
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(lcAccountState, "nextcloud.gui.account.state", QtInfoMsg)
|
Q_LOGGING_CATEGORY(lcAccountState, "nextcloud.gui.account.state", QtInfoMsg)
|
||||||
@@ -34,13 +42,14 @@ AccountState::AccountState(AccountPtr account)
|
|||||||
, _state(AccountState::Disconnected)
|
, _state(AccountState::Disconnected)
|
||||||
, _connectionStatus(ConnectionValidator::Undefined)
|
, _connectionStatus(ConnectionValidator::Undefined)
|
||||||
, _waitingForNewCredentials(false)
|
, _waitingForNewCredentials(false)
|
||||||
, _notificationsEtagResponseHeader("*")
|
|
||||||
, _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay
|
, _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay
|
||||||
|
, _remoteWipe(new RemoteWipe(_account))
|
||||||
|
, _hasTalk(false)
|
||||||
{
|
{
|
||||||
qRegisterMetaType<AccountState *>("AccountState*");
|
qRegisterMetaType<AccountState *>("AccountState*");
|
||||||
|
|
||||||
connect(account.data(), &Account::invalidCredentials,
|
connect(account.data(), &Account::invalidCredentials,
|
||||||
this, &AccountState::slotInvalidCredentials);
|
this, &AccountState::slotHandleRemoteWipeCheck);
|
||||||
connect(account.data(), &Account::credentialsFetched,
|
connect(account.data(), &Account::credentialsFetched,
|
||||||
this, &AccountState::slotCredentialsFetched);
|
this, &AccountState::slotCredentialsFetched);
|
||||||
connect(account.data(), &Account::credentialsAsked,
|
connect(account.data(), &Account::credentialsAsked,
|
||||||
@@ -67,6 +76,11 @@ AccountPtr AccountState::account() const
|
|||||||
return _account;
|
return _account;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AccountState::hasTalk() const
|
||||||
|
{
|
||||||
|
return _hasTalk;
|
||||||
|
}
|
||||||
|
|
||||||
AccountState::ConnectionStatus AccountState::connectionStatus() const
|
AccountState::ConnectionStatus AccountState::connectionStatus() const
|
||||||
{
|
{
|
||||||
return _connectionStatus;
|
return _connectionStatus;
|
||||||
@@ -230,6 +244,9 @@ void AccountState::checkConnectivity()
|
|||||||
// Use a small authed propfind as a minimal ping when we're
|
// Use a small authed propfind as a minimal ping when we're
|
||||||
// already connected.
|
// already connected.
|
||||||
conValidator->checkAuthentication();
|
conValidator->checkAuthentication();
|
||||||
|
|
||||||
|
// Get the Apps available on the server.
|
||||||
|
fetchNavigationApps();
|
||||||
} else {
|
} else {
|
||||||
// Check the server and then the auth.
|
// Check the server and then the auth.
|
||||||
|
|
||||||
@@ -260,7 +277,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
|
|||||||
// Come online gradually from 503 or maintenance mode
|
// Come online gradually from 503 or maintenance mode
|
||||||
if (status == ConnectionValidator::Connected
|
if (status == ConnectionValidator::Connected
|
||||||
&& (_connectionStatus == ConnectionValidator::ServiceUnavailable
|
&& (_connectionStatus == ConnectionValidator::ServiceUnavailable
|
||||||
|| _connectionStatus == ConnectionValidator::MaintenanceMode)) {
|
|| _connectionStatus == ConnectionValidator::MaintenanceMode)) {
|
||||||
if (!_timeSinceMaintenanceOver.isValid()) {
|
if (!_timeSinceMaintenanceOver.isValid()) {
|
||||||
qCInfo(lcAccountState) << "AccountState reconnection: delaying for"
|
qCInfo(lcAccountState) << "AccountState reconnection: delaying for"
|
||||||
<< _maintenanceToConnectedDelay << "ms";
|
<< _maintenanceToConnectedDelay << "ms";
|
||||||
@@ -286,6 +303,9 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
|
|||||||
case ConnectionValidator::Connected:
|
case ConnectionValidator::Connected:
|
||||||
if (_state != Connected) {
|
if (_state != Connected) {
|
||||||
setState(Connected);
|
setState(Connected);
|
||||||
|
|
||||||
|
// Get the Apps available on the server.
|
||||||
|
fetchNavigationApps();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ConnectionValidator::Undefined:
|
case ConnectionValidator::Undefined:
|
||||||
@@ -303,7 +323,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
|
|||||||
break;
|
break;
|
||||||
case ConnectionValidator::CredentialsWrong:
|
case ConnectionValidator::CredentialsWrong:
|
||||||
case ConnectionValidator::CredentialsNotReady:
|
case ConnectionValidator::CredentialsNotReady:
|
||||||
slotInvalidCredentials();
|
handleInvalidCredentials();
|
||||||
break;
|
break;
|
||||||
case ConnectionValidator::SslError:
|
case ConnectionValidator::SslError:
|
||||||
setState(SignedOut);
|
setState(SignedOut);
|
||||||
@@ -322,7 +342,20 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountState::slotInvalidCredentials()
|
void AccountState::slotHandleRemoteWipeCheck()
|
||||||
|
{
|
||||||
|
// make sure it changes account state and icons
|
||||||
|
signOutByUi();
|
||||||
|
|
||||||
|
qCInfo(lcAccountState) << "Invalid credentials for" << _account->url().toString()
|
||||||
|
<< "checking for remote wipe request";
|
||||||
|
|
||||||
|
_waitingForNewCredentials = false;
|
||||||
|
setState(SignedOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AccountState::handleInvalidCredentials()
|
||||||
{
|
{
|
||||||
if (isSignedOut() || _waitingForNewCredentials)
|
if (isSignedOut() || _waitingForNewCredentials)
|
||||||
return;
|
return;
|
||||||
@@ -343,6 +376,7 @@ void AccountState::slotInvalidCredentials()
|
|||||||
account()->credentials()->askFromUser();
|
account()->credentials()->askFromUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AccountState::slotCredentialsFetched(AbstractCredentials *)
|
void AccountState::slotCredentialsFetched(AbstractCredentials *)
|
||||||
{
|
{
|
||||||
// Make a connection attempt, no matter whether the credentials are
|
// Make a connection attempt, no matter whether the credentials are
|
||||||
@@ -384,4 +418,110 @@ std::unique_ptr<QSettings> AccountState::settings()
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AccountState::fetchNavigationApps(){
|
||||||
|
OcsNavigationAppsJob *job = new OcsNavigationAppsJob(_account);
|
||||||
|
job->addRawHeader("If-None-Match", navigationAppsEtagResponseHeader());
|
||||||
|
connect(job, &OcsNavigationAppsJob::appsJobFinished, this, &AccountState::slotNavigationAppsFetched);
|
||||||
|
connect(job, &OcsNavigationAppsJob::etagResponseHeaderReceived, this, &AccountState::slotEtagResponseHeaderReceived);
|
||||||
|
connect(job, &OcsNavigationAppsJob::ocsError, this, &AccountState::slotOcsError);
|
||||||
|
job->getNavigationApps();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccountState::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){
|
||||||
|
if(statusCode == 200){
|
||||||
|
qCDebug(lcAccountState) << "New navigation apps ETag Response Header received " << value;
|
||||||
|
setNavigationAppsEtagResponseHeader(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccountState::slotOcsError(int statusCode, const QString &message)
|
||||||
|
{
|
||||||
|
qCDebug(lcAccountState) << "Error " << statusCode << " while fetching new navigation apps: " << message;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccountState::slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode)
|
||||||
|
{
|
||||||
|
if(_account){
|
||||||
|
if (statusCode == 304) {
|
||||||
|
qCWarning(lcAccountState) << "Status code " << statusCode << " Not Modified - No new navigation apps.";
|
||||||
|
} else {
|
||||||
|
_apps.clear();
|
||||||
|
_hasTalk = false;
|
||||||
|
|
||||||
|
if(!reply.isEmpty()){
|
||||||
|
auto element = reply.object().value("ocs").toObject().value("data");
|
||||||
|
auto navLinks = element.toArray();
|
||||||
|
|
||||||
|
if(navLinks.size() > 0){
|
||||||
|
foreach (const QJsonValue &value, navLinks) {
|
||||||
|
auto navLink = value.toObject();
|
||||||
|
|
||||||
|
AccountApp *app = new AccountApp(navLink.value("name").toString(), QUrl(navLink.value("href").toString()),
|
||||||
|
navLink.value("id").toString(), QUrl(navLink.value("icon").toString()));
|
||||||
|
|
||||||
|
_apps << app;
|
||||||
|
|
||||||
|
if(app->id() == QLatin1String("spreed"))
|
||||||
|
_hasTalk = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit hasFetchedNavigationApps();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountAppList AccountState::appList() const
|
||||||
|
{
|
||||||
|
return _apps;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountApp* AccountState::findApp(const QString &appId) const
|
||||||
|
{
|
||||||
|
if(!appId.isEmpty()) {
|
||||||
|
foreach(AccountApp *app, appList()) {
|
||||||
|
if(app->id() == appId)
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
AccountApp::AccountApp(const QString &name, const QUrl &url,
|
||||||
|
const QString &id, const QUrl &iconUrl,
|
||||||
|
QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, _name(name)
|
||||||
|
, _url(url)
|
||||||
|
, _id(id)
|
||||||
|
, _iconUrl(iconUrl)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AccountApp::name() const
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl AccountApp::url() const
|
||||||
|
{
|
||||||
|
return _url;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AccountApp::id() const
|
||||||
|
{
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl AccountApp::iconUrl() const
|
||||||
|
{
|
||||||
|
return _iconUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
@@ -29,8 +29,11 @@ namespace OCC {
|
|||||||
|
|
||||||
class AccountState;
|
class AccountState;
|
||||||
class Account;
|
class Account;
|
||||||
|
class AccountApp;
|
||||||
|
class RemoteWipe;
|
||||||
|
|
||||||
typedef QExplicitlySharedDataPointer<AccountState> AccountStatePtr;
|
typedef QExplicitlySharedDataPointer<AccountState> AccountStatePtr;
|
||||||
|
typedef QList<AccountApp*> AccountAppList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Extra info about an ownCloud server account.
|
* @brief Extra info about an ownCloud server account.
|
||||||
@@ -100,6 +103,11 @@ public:
|
|||||||
|
|
||||||
bool isSignedOut() const;
|
bool isSignedOut() const;
|
||||||
|
|
||||||
|
bool hasTalk() const;
|
||||||
|
|
||||||
|
AccountAppList appList() const;
|
||||||
|
AccountApp* findApp(const QString &appId) const;
|
||||||
|
|
||||||
/** A user-triggered sign out which disconnects, stops syncs
|
/** A user-triggered sign out which disconnects, stops syncs
|
||||||
* for the account and forgets the password. */
|
* for the account and forgets the password. */
|
||||||
void signOutByUi();
|
void signOutByUi();
|
||||||
@@ -150,6 +158,9 @@ public:
|
|||||||
*/
|
*/
|
||||||
void setNavigationAppsEtagResponseHeader(const QByteArray &value);
|
void setNavigationAppsEtagResponseHeader(const QByteArray &value);
|
||||||
|
|
||||||
|
///Asks for user credentials
|
||||||
|
void handleInvalidCredentials();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/// Triggers a ping to the server to update state and
|
/// Triggers a ping to the server to update state and
|
||||||
/// connection status and errors.
|
/// connection status and errors.
|
||||||
@@ -157,23 +168,34 @@ public slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void setState(State state);
|
void setState(State state);
|
||||||
|
void fetchNavigationApps();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void stateChanged(int state);
|
void stateChanged(int state);
|
||||||
void isConnectedChanged();
|
void isConnectedChanged();
|
||||||
|
void hasFetchedNavigationApps();
|
||||||
|
|
||||||
protected Q_SLOTS:
|
protected Q_SLOTS:
|
||||||
void slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors);
|
void slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors);
|
||||||
void slotInvalidCredentials();
|
|
||||||
|
/// When client gets a 401 or 403 checks if server requested remote wipe
|
||||||
|
/// before asking for user credentials again
|
||||||
|
void slotHandleRemoteWipeCheck();
|
||||||
|
|
||||||
void slotCredentialsFetched(AbstractCredentials *creds);
|
void slotCredentialsFetched(AbstractCredentials *creds);
|
||||||
void slotCredentialsAsked(AbstractCredentials *creds);
|
void slotCredentialsAsked(AbstractCredentials *creds);
|
||||||
|
|
||||||
|
void slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode);
|
||||||
|
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
|
||||||
|
void slotOcsError(int statusCode, const QString &message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AccountPtr _account;
|
AccountPtr _account;
|
||||||
State _state;
|
State _state;
|
||||||
ConnectionStatus _connectionStatus;
|
ConnectionStatus _connectionStatus;
|
||||||
QStringList _connectionErrors;
|
QStringList _connectionErrors;
|
||||||
bool _waitingForNewCredentials;
|
bool _waitingForNewCredentials;
|
||||||
|
bool _hasTalk;
|
||||||
QElapsedTimer _timeSinceLastETagCheck;
|
QElapsedTimer _timeSinceLastETagCheck;
|
||||||
QPointer<ConnectionValidator> _connectionValidator;
|
QPointer<ConnectionValidator> _connectionValidator;
|
||||||
QByteArray _notificationsEtagResponseHeader;
|
QByteArray _notificationsEtagResponseHeader;
|
||||||
@@ -190,7 +212,41 @@ private:
|
|||||||
* Milliseconds for which to delay reconnection after 503/maintenance.
|
* Milliseconds for which to delay reconnection after 503/maintenance.
|
||||||
*/
|
*/
|
||||||
int _maintenanceToConnectedDelay;
|
int _maintenanceToConnectedDelay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects remote wipe check with the account
|
||||||
|
* the log out triggers the check (loads app password -> create request)
|
||||||
|
*/
|
||||||
|
RemoteWipe *_remoteWipe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the App names and URLs available on the server
|
||||||
|
*/
|
||||||
|
AccountAppList _apps;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AccountApp : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
AccountApp(const QString &name, const QUrl &url,
|
||||||
|
const QString &id, const QUrl &iconUrl,
|
||||||
|
QObject* parent = 0);
|
||||||
|
|
||||||
|
QString name() const;
|
||||||
|
QUrl url() const;
|
||||||
|
QString id() const;
|
||||||
|
QUrl iconUrl() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString _name;
|
||||||
|
QUrl _url;
|
||||||
|
|
||||||
|
QString _id;
|
||||||
|
QUrl _iconUrl;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(OCC::AccountState *)
|
Q_DECLARE_METATYPE(OCC::AccountState *)
|
||||||
|
|||||||
@@ -1,313 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
|
||||||
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* 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 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
* for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "activityitemdelegate.h"
|
|
||||||
#include "folderstatusmodel.h"
|
|
||||||
#include "folderman.h"
|
|
||||||
#include "accountstate.h"
|
|
||||||
#include "activitydata.h"
|
|
||||||
#include <theme.h>
|
|
||||||
#include <account.h>
|
|
||||||
|
|
||||||
#include <QFileIconProvider>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QApplication>
|
|
||||||
|
|
||||||
namespace OCC {
|
|
||||||
|
|
||||||
int ActivityItemDelegate::_iconHeight = 0;
|
|
||||||
int ActivityItemDelegate::_margin = 0;
|
|
||||||
int ActivityItemDelegate::_primaryButtonWidth = 0;
|
|
||||||
int ActivityItemDelegate::_secondaryButtonWidth = 0;
|
|
||||||
int ActivityItemDelegate::_spaceBetweenButtons = 0;
|
|
||||||
int ActivityItemDelegate::_timeWidth = 0;
|
|
||||||
int ActivityItemDelegate::_buttonHeight = 0;
|
|
||||||
const QString ActivityItemDelegate::_remote_share("remote_share");
|
|
||||||
const QString ActivityItemDelegate::_call("call");
|
|
||||||
|
|
||||||
int ActivityItemDelegate::iconHeight()
|
|
||||||
{
|
|
||||||
if (_iconHeight == 0) {
|
|
||||||
QStyleOptionViewItem option;
|
|
||||||
QFont font = option.font;
|
|
||||||
|
|
||||||
QFontMetrics fm(font);
|
|
||||||
|
|
||||||
_iconHeight = qRound(fm.height() / 5.0 * 8.0);
|
|
||||||
}
|
|
||||||
return _iconHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ActivityItemDelegate::rowHeight()
|
|
||||||
{
|
|
||||||
if (_margin == 0) {
|
|
||||||
QStyleOptionViewItem opt;
|
|
||||||
|
|
||||||
QFont f = opt.font;
|
|
||||||
QFontMetrics fm(f);
|
|
||||||
|
|
||||||
_margin = fm.height() / 2;
|
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
|
||||||
_margin += 5;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return iconHeight() + 5 * _margin;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize ActivityItemDelegate::sizeHint(const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex & /* index */) const
|
|
||||||
{
|
|
||||||
QFont font = option.font;
|
|
||||||
|
|
||||||
return QSize(0, rowHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex &index) const
|
|
||||||
{
|
|
||||||
QStyledItemDelegate::paint(painter, option, index);
|
|
||||||
QFont font = option.font;
|
|
||||||
QFontMetrics fm(font);
|
|
||||||
int margin = fm.height() / 2.5;
|
|
||||||
painter->save();
|
|
||||||
int iconSize = 16;
|
|
||||||
int iconOffset = qRound(fm.height() / 4.0 * 7.0);
|
|
||||||
int offset = 4;
|
|
||||||
|
|
||||||
// get the data
|
|
||||||
Activity::Type activityType = qvariant_cast<Activity::Type>(index.data(ActionRole));
|
|
||||||
QIcon actionIcon = qvariant_cast<QIcon>(index.data(ActionIconRole));
|
|
||||||
QString objectType = qvariant_cast<QString>(index.data(ObjectTypeRole));
|
|
||||||
QString actionText = qvariant_cast<QString>(index.data(ActionTextRole));
|
|
||||||
QString messageText = qvariant_cast<QString>(index.data(MessageRole));
|
|
||||||
QList<QVariant> customList = index.data(ActionsLinksRole).toList();
|
|
||||||
QString timeText = qvariant_cast<QString>(index.data(PointInTimeRole));
|
|
||||||
bool accountOnline = qvariant_cast<bool>(index.data(AccountConnectedRole));
|
|
||||||
|
|
||||||
// activity/notification icons
|
|
||||||
QRect actionIconRect = option.rect;
|
|
||||||
actionIconRect.setLeft(option.rect.left() + iconOffset/3);
|
|
||||||
actionIconRect.setRight(option.rect.left() + iconOffset);
|
|
||||||
actionIconRect.setTop(option.rect.top() + qRound((option.rect.height() - 16)/3.0));
|
|
||||||
|
|
||||||
// subject text rect
|
|
||||||
QRect actionTextBox = actionIconRect;
|
|
||||||
int actionTextBoxWidth = fm.width(actionText);
|
|
||||||
actionTextBox.setTop(option.rect.top() + margin + offset/2);
|
|
||||||
actionTextBox.setHeight(fm.height());
|
|
||||||
actionTextBox.setLeft(actionIconRect.right() + margin);
|
|
||||||
actionTextBox.setRight(actionTextBox.left() + actionTextBoxWidth + margin);
|
|
||||||
|
|
||||||
// message text rect
|
|
||||||
QRect messageTextBox = actionTextBox;
|
|
||||||
int messageTextWidth = fm.width(messageText);
|
|
||||||
int messageTextTop = option.rect.top() + fm.height() + margin;
|
|
||||||
if(actionText.isEmpty()) messageTextTop = option.rect.top() + margin + offset/2;
|
|
||||||
messageTextBox.setTop(messageTextTop);
|
|
||||||
messageTextBox.setHeight(fm.height());
|
|
||||||
messageTextBox.setBottom(messageTextBox.top() + fm.height());
|
|
||||||
messageTextBox.setRight(messageTextBox.left() + messageTextWidth + margin);
|
|
||||||
if(messageText.isEmpty()){
|
|
||||||
messageTextBox.setHeight(0);
|
|
||||||
messageTextBox.setBottom(messageTextBox.top());
|
|
||||||
}
|
|
||||||
|
|
||||||
// time box rect
|
|
||||||
QRect timeBox = messageTextBox;
|
|
||||||
QString timeStr = tr("%1").arg(timeText);
|
|
||||||
int timeTextWidth = fm.width(timeStr);
|
|
||||||
int timeTop = option.rect.top() + fm.height() + fm.height() + margin + offset/2;
|
|
||||||
if(messageText.isEmpty() || actionText.isEmpty())
|
|
||||||
timeTop = option.rect.top() + fm.height() + margin;
|
|
||||||
timeBox.setTop(timeTop);
|
|
||||||
timeBox.setHeight(fm.height());
|
|
||||||
timeBox.setBottom(timeBox.top() + fm.height());
|
|
||||||
timeBox.setRight(timeBox.left() + timeTextWidth + margin);
|
|
||||||
|
|
||||||
// buttons - default values
|
|
||||||
int rightMargin = margin;
|
|
||||||
int leftMargin = margin * offset;
|
|
||||||
int top = option.rect.top() + margin;
|
|
||||||
int buttonSize = option.rect.height()/2;
|
|
||||||
int right = option.rect.right() - rightMargin;
|
|
||||||
int left = right - buttonSize;
|
|
||||||
|
|
||||||
QStyleOptionButton secondaryButton;
|
|
||||||
secondaryButton.rect = option.rect;
|
|
||||||
secondaryButton.features |= QStyleOptionButton::Flat;
|
|
||||||
secondaryButton.state |= QStyle::State_None;
|
|
||||||
secondaryButton.rect.setLeft(left);
|
|
||||||
secondaryButton.rect.setRight(right);
|
|
||||||
secondaryButton.rect.setTop(top + margin);
|
|
||||||
secondaryButton.rect.setHeight(iconSize);
|
|
||||||
|
|
||||||
QStyleOptionButton primaryButton;
|
|
||||||
primaryButton.rect = option.rect;
|
|
||||||
primaryButton.features |= QStyleOptionButton::DefaultButton;
|
|
||||||
primaryButton.state |= QStyle::State_Raised;
|
|
||||||
primaryButton.rect.setTop(top);
|
|
||||||
primaryButton.rect.setHeight(buttonSize);
|
|
||||||
|
|
||||||
right = secondaryButton.rect.left() - rightMargin;
|
|
||||||
left = secondaryButton.rect.left() - leftMargin;
|
|
||||||
|
|
||||||
primaryButton.rect.setRight(right);
|
|
||||||
|
|
||||||
if(activityType == Activity::Type::NotificationType){
|
|
||||||
|
|
||||||
// Secondary will be 'Dismiss' or '...' multiple options button
|
|
||||||
secondaryButton.icon = QIcon(QLatin1String(":/client/resources/close.svg"));
|
|
||||||
if(customList.size() > 1)
|
|
||||||
secondaryButton.icon = QIcon(QLatin1String(":/client/resources/more.svg"));
|
|
||||||
secondaryButton.iconSize = QSize(iconSize, iconSize);
|
|
||||||
|
|
||||||
// Primary button will be 'More Information' or 'Accept'
|
|
||||||
primaryButton.text = tr("More information");
|
|
||||||
if(objectType == _remote_share) primaryButton.text = tr("Accept");
|
|
||||||
if(objectType == _call) primaryButton.text = tr("Join");
|
|
||||||
|
|
||||||
primaryButton.rect.setLeft(left - margin * 2 - fm.width(primaryButton.text));
|
|
||||||
|
|
||||||
// save info to be able to filter mouse clicks
|
|
||||||
_buttonHeight = buttonSize;
|
|
||||||
_primaryButtonWidth = primaryButton.rect.size().width();
|
|
||||||
_secondaryButtonWidth = secondaryButton.rect.size().width();
|
|
||||||
_spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right();
|
|
||||||
|
|
||||||
} else if(activityType == Activity::SyncResultType){
|
|
||||||
|
|
||||||
// Secondary will be 'open file manager' with the folder icon
|
|
||||||
secondaryButton.icon = QIcon(QLatin1String(":/client/resources/folder.svg"));
|
|
||||||
secondaryButton.iconSize = QSize(iconSize, iconSize);
|
|
||||||
|
|
||||||
// Primary button will be 'open browser'
|
|
||||||
primaryButton.text = tr("Open Browser");
|
|
||||||
primaryButton.rect.setLeft(left - margin * 2 - fm.width(primaryButton.text));
|
|
||||||
|
|
||||||
// save info to be able to filter mouse clicks
|
|
||||||
_buttonHeight = buttonSize;
|
|
||||||
_primaryButtonWidth = primaryButton.rect.size().width();
|
|
||||||
_secondaryButtonWidth = secondaryButton.rect.size().width();
|
|
||||||
_spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right();
|
|
||||||
|
|
||||||
} else if(activityType == Activity::SyncFileItemType){
|
|
||||||
|
|
||||||
// Secondary will be 'open file manager' with the folder icon
|
|
||||||
secondaryButton.icon = QIcon(QLatin1String(":/client/resources/folder.svg"));
|
|
||||||
secondaryButton.iconSize = QSize(iconSize, iconSize);
|
|
||||||
|
|
||||||
// No primary button on this case
|
|
||||||
// Whatever error we have at this case it is local, there is no point on opening the browser
|
|
||||||
_primaryButtonWidth = 0;
|
|
||||||
_secondaryButtonWidth = secondaryButton.rect.size().width();
|
|
||||||
_spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
_spaceBetweenButtons = leftMargin;
|
|
||||||
_primaryButtonWidth = 0;
|
|
||||||
_secondaryButtonWidth = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the icon
|
|
||||||
QPixmap pm = actionIcon.pixmap(iconSize, iconSize, QIcon::Normal);
|
|
||||||
painter->drawPixmap(QPoint(actionIconRect.left(), actionIconRect.top()), pm);
|
|
||||||
|
|
||||||
// change pen color if use is not online
|
|
||||||
QPalette p = option.palette;
|
|
||||||
if(!accountOnline)
|
|
||||||
p.setCurrentColorGroup(QPalette::Disabled);
|
|
||||||
|
|
||||||
// change pen color if the line is selected
|
|
||||||
if (option.state & QStyle::State_Selected)
|
|
||||||
painter->setPen(p.color(QPalette::HighlightedText));
|
|
||||||
else
|
|
||||||
painter->setPen(p.color(QPalette::Text));
|
|
||||||
|
|
||||||
// calculate space for text - use the max possible before using the elipses
|
|
||||||
int spaceLeftForText = option.rect.width() - (actionIconRect.width() + margin + rightMargin + leftMargin) -
|
|
||||||
(_primaryButtonWidth + _secondaryButtonWidth + _spaceBetweenButtons);
|
|
||||||
|
|
||||||
// draw the subject
|
|
||||||
const QString elidedAction = fm.elidedText(actionText, Qt::ElideRight, spaceLeftForText);
|
|
||||||
painter->drawText(actionTextBox, elidedAction);
|
|
||||||
|
|
||||||
// draw the buttons
|
|
||||||
if(activityType == Activity::Type::NotificationType || activityType == Activity::Type::SyncResultType)
|
|
||||||
QApplication::style()->drawControl(QStyle::CE_PushButton, &primaryButton, painter);
|
|
||||||
|
|
||||||
// Since they are errors on local syncing, there is nothing to do in the server
|
|
||||||
if(activityType != Activity::Type::ActivityType)
|
|
||||||
QApplication::style()->drawControl(QStyle::CE_PushButton, &secondaryButton, painter);
|
|
||||||
|
|
||||||
// draw the message
|
|
||||||
// change pen color for the message
|
|
||||||
if(!messageText.isEmpty()){
|
|
||||||
const QString elidedMessage = fm.elidedText(messageText, Qt::ElideRight, spaceLeftForText);
|
|
||||||
painter->drawText(messageTextBox, elidedMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// change pen color for the time
|
|
||||||
if (option.state & QStyle::State_Selected)
|
|
||||||
painter->setPen(p.color(QPalette::Disabled, QPalette::HighlightedText));
|
|
||||||
else
|
|
||||||
painter->setPen(p.color(QPalette::Disabled, QPalette::Text));
|
|
||||||
|
|
||||||
// draw the time
|
|
||||||
const QString elidedTime = fm.elidedText(timeStr, Qt::ElideRight, spaceLeftForText);
|
|
||||||
painter->drawText(timeBox, elidedTime);
|
|
||||||
|
|
||||||
painter->restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ActivityItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
|
||||||
const QStyleOptionViewItem &option, const QModelIndex &index)
|
|
||||||
{
|
|
||||||
Activity::Type activityType = qvariant_cast<Activity::Type>(index.data(ActionRole));
|
|
||||||
if(activityType != Activity::Type::ActivityType){
|
|
||||||
if (event->type() == QEvent::MouseButtonRelease){
|
|
||||||
QMouseEvent *mouseEvent = (QMouseEvent*)event;
|
|
||||||
if(mouseEvent){
|
|
||||||
int mouseEventX = mouseEvent->x();
|
|
||||||
int mouseEventY = mouseEvent->y();
|
|
||||||
int buttonsWidth = _primaryButtonWidth + _spaceBetweenButtons + _secondaryButtonWidth;
|
|
||||||
int x = option.rect.left() + option.rect.width() - buttonsWidth - _timeWidth;
|
|
||||||
int y = option.rect.top();
|
|
||||||
|
|
||||||
// clickable area for ...
|
|
||||||
if (mouseEventX > x && mouseEventX < x + buttonsWidth){
|
|
||||||
if(mouseEventY > y && mouseEventY < y + _buttonHeight){
|
|
||||||
|
|
||||||
// ...primary button ('more information' or 'accept' on notifications or 'open browser' on errors)
|
|
||||||
if (mouseEventX > x && mouseEventX < x + _primaryButtonWidth){
|
|
||||||
emit primaryButtonClickedOnItemView(index);
|
|
||||||
|
|
||||||
// ...secondary button ('dismiss' on notifications or 'open file manager' on errors)
|
|
||||||
} else {
|
|
||||||
x += _primaryButtonWidth + _spaceBetweenButtons;
|
|
||||||
if (mouseEventX > x && mouseEventX < x + _secondaryButtonWidth)
|
|
||||||
emit secondaryButtonClickedOnItemView(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return QStyledItemDelegate::editorEvent(event, model, option, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace OCC
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) by Klaas Freitag <freitag@kde.org>
|
|
||||||
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
|
|
||||||
*
|
|
||||||
* 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 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
* for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <QStyledItemDelegate>
|
|
||||||
#include <QMouseEvent>
|
|
||||||
|
|
||||||
class QMouseEvent;
|
|
||||||
|
|
||||||
namespace OCC {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The ActivityItemDelegate class
|
|
||||||
* @ingroup gui
|
|
||||||
*/
|
|
||||||
class ActivityItemDelegate : public QStyledItemDelegate
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
enum datarole { ActionIconRole = Qt::UserRole + 1,
|
|
||||||
UserIconRole,
|
|
||||||
AccountRole,
|
|
||||||
ObjectTypeRole,
|
|
||||||
ActionsLinksRole,
|
|
||||||
ActionTextRole,
|
|
||||||
ActionRole,
|
|
||||||
MessageRole,
|
|
||||||
PathRole,
|
|
||||||
LinkRole,
|
|
||||||
PointInTimeRole,
|
|
||||||
AccountConnectedRole,
|
|
||||||
SyncFileStatusRole };
|
|
||||||
|
|
||||||
void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override;
|
|
||||||
QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override;
|
|
||||||
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
|
|
||||||
const QModelIndex &index) override;
|
|
||||||
|
|
||||||
static int rowHeight();
|
|
||||||
static int iconHeight();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void primaryButtonClickedOnItemView(const QModelIndex &index);
|
|
||||||
void secondaryButtonClickedOnItemView(const QModelIndex &index);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static int _margin;
|
|
||||||
static int _iconHeight;
|
|
||||||
static int _primaryButtonWidth;
|
|
||||||
static int _secondaryButtonWidth;
|
|
||||||
static int _spaceBetweenButtons;
|
|
||||||
static int _timeWidth;
|
|
||||||
static int _buttonHeight;
|
|
||||||
static const QString _remote_share;
|
|
||||||
static const QString _call;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace OCC
|
|
||||||
@@ -1,341 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
|
||||||
*
|
|
||||||
* 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 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
* for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <QtCore>
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QWidget>
|
|
||||||
#include <QIcon>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
|
|
||||||
#include "account.h"
|
|
||||||
#include "accountstate.h"
|
|
||||||
#include "accountmanager.h"
|
|
||||||
#include "folderman.h"
|
|
||||||
#include "accessmanager.h"
|
|
||||||
#include "activityitemdelegate.h"
|
|
||||||
|
|
||||||
#include "activitydata.h"
|
|
||||||
#include "activitylistmodel.h"
|
|
||||||
|
|
||||||
#include "theme.h"
|
|
||||||
|
|
||||||
#include "servernotificationhandler.h"
|
|
||||||
|
|
||||||
namespace OCC {
|
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg)
|
|
||||||
|
|
||||||
ActivityListModel::ActivityListModel(AccountState *accountState, QWidget *parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
, _accountState(accountState)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
Activity a;
|
|
||||||
|
|
||||||
// filter the get action here
|
|
||||||
// send only the text of the get action
|
|
||||||
// if there is more than one send the icon? the ...
|
|
||||||
|
|
||||||
if (!index.isValid())
|
|
||||||
return QVariant();
|
|
||||||
|
|
||||||
a = _finalList.at(index.row());
|
|
||||||
AccountStatePtr ast = AccountManager::instance()->account(a._accName);
|
|
||||||
if (!ast && _accountState != ast.data())
|
|
||||||
return QVariant();
|
|
||||||
QStringList list;
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case ActivityItemDelegate::PathRole:
|
|
||||||
if(!a._file.isEmpty()){
|
|
||||||
auto folder = FolderMan::instance()->folder(a._folder);
|
|
||||||
QString relPath(a._file);
|
|
||||||
if(folder) relPath.prepend(folder->remotePath());
|
|
||||||
list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
|
|
||||||
if (list.count() > 0) {
|
|
||||||
return QVariant(list.at(0));
|
|
||||||
}
|
|
||||||
// File does not exist anymore? Let's try to open its path
|
|
||||||
list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(relPath).path(), ast->account());
|
|
||||||
if (list.count() > 0) {
|
|
||||||
return QVariant(list.at(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QVariant();
|
|
||||||
break;
|
|
||||||
case ActivityItemDelegate::ActionsLinksRole:{
|
|
||||||
QList<QVariant> customList;
|
|
||||||
foreach (ActivityLink customItem, a._links) {
|
|
||||||
QVariant customVariant;
|
|
||||||
customVariant.setValue(customItem);
|
|
||||||
customList << customVariant;
|
|
||||||
}
|
|
||||||
return customList;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActivityItemDelegate::ActionIconRole:
|
|
||||||
if(a._type == Activity::NotificationType){
|
|
||||||
QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id);
|
|
||||||
if(!cachedIcon.isNull())
|
|
||||||
return cachedIcon;
|
|
||||||
else return QIcon(QLatin1String(":/client/resources/bell.svg"));
|
|
||||||
} else if(a._type == Activity::SyncResultType){
|
|
||||||
return QIcon(QLatin1String(":/client/resources/state-error.svg"));
|
|
||||||
} else if(a._type == Activity::SyncFileItemType){
|
|
||||||
if(a._status == SyncFileItem::NormalError
|
|
||||||
|| a._status == SyncFileItem::FatalError
|
|
||||||
|| a._status == SyncFileItem::DetailError
|
|
||||||
|| a._status == SyncFileItem::BlacklistedError) {
|
|
||||||
return QIcon(QLatin1String(":/client/resources/state-error.svg"));
|
|
||||||
} else if(a._status == SyncFileItem::SoftError
|
|
||||||
|| a._status == SyncFileItem::Conflict
|
|
||||||
|| a._status == SyncFileItem::Restoration
|
|
||||||
|| a._status == SyncFileItem::FileLocked){
|
|
||||||
return QIcon(QLatin1String(":/client/resources/state-warning.svg"));
|
|
||||||
} else if(a._status == SyncFileItem::FileIgnored){
|
|
||||||
return QIcon(QLatin1String(":/client/resources/state-info.svg"));
|
|
||||||
}
|
|
||||||
return QIcon(QLatin1String(":/client/resources/state-sync.svg"));
|
|
||||||
}
|
|
||||||
return QIcon(QLatin1String(":/client/resources/activity.png"));
|
|
||||||
break;
|
|
||||||
case ActivityItemDelegate::ObjectTypeRole:
|
|
||||||
return a._objectType;
|
|
||||||
break;
|
|
||||||
case ActivityItemDelegate::ActionRole:{
|
|
||||||
QVariant type;
|
|
||||||
type.setValue(a._type);
|
|
||||||
return type;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ActivityItemDelegate::ActionTextRole:
|
|
||||||
return a._subject;
|
|
||||||
break;
|
|
||||||
case ActivityItemDelegate::MessageRole:
|
|
||||||
return a._message;
|
|
||||||
break;
|
|
||||||
case ActivityItemDelegate::LinkRole:
|
|
||||||
return a._link;
|
|
||||||
break;
|
|
||||||
case ActivityItemDelegate::AccountRole:
|
|
||||||
return a._accName;
|
|
||||||
break;
|
|
||||||
case ActivityItemDelegate::PointInTimeRole:
|
|
||||||
return Utility::timeAgoInWords(a._dateTime);
|
|
||||||
break;
|
|
||||||
case ActivityItemDelegate::AccountConnectedRole:
|
|
||||||
return (ast && ast->isConnected());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
int ActivityListModel::rowCount(const QModelIndex &) const
|
|
||||||
{
|
|
||||||
return _finalList.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ActivityListModel::canFetchMore(const QModelIndex &) const
|
|
||||||
{
|
|
||||||
// We need to be connected to be able to fetch more
|
|
||||||
if (_accountState && _accountState->isConnected()) {
|
|
||||||
// If the fetching is reported to be done or we are currently fetching we can't fetch more
|
|
||||||
if (!_doneFetching && !_currentlyFetching) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityListModel::startFetchJob()
|
|
||||||
{
|
|
||||||
if (!_accountState->isConnected()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonApiJob *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/cloud/activity"), this);
|
|
||||||
QObject::connect(job, &JsonApiJob::jsonReceived,
|
|
||||||
this, &ActivityListModel::slotActivitiesReceived);
|
|
||||||
|
|
||||||
QUrlQuery params;
|
|
||||||
params.addQueryItem(QLatin1String("start"), QString::number(_currentItem));
|
|
||||||
params.addQueryItem(QLatin1String("count"), QString::number(100));
|
|
||||||
job->addQueryParams(params);
|
|
||||||
|
|
||||||
_currentlyFetching = true;
|
|
||||||
qCInfo(lcActivity) << "Start fetching activities for " << _accountState->account()->displayName();
|
|
||||||
job->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int statusCode)
|
|
||||||
{
|
|
||||||
auto activities = json.object().value("ocs").toObject().value("data").toArray();
|
|
||||||
|
|
||||||
ActivityList list;
|
|
||||||
auto ast = _accountState;
|
|
||||||
if (!ast) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activities.size() == 0) {
|
|
||||||
_doneFetching = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentlyFetching = false;
|
|
||||||
_currentItem += activities.size();
|
|
||||||
|
|
||||||
foreach (auto activ, activities) {
|
|
||||||
auto json = activ.toObject();
|
|
||||||
|
|
||||||
Activity a;
|
|
||||||
a._type = Activity::ActivityType;
|
|
||||||
a._accName = ast->account()->displayName();
|
|
||||||
a._id = json.value("id").toInt();
|
|
||||||
a._subject = json.value("subject").toString();
|
|
||||||
a._message = json.value("message").toString();
|
|
||||||
a._file = json.value("file").toString();
|
|
||||||
a._link = QUrl(json.value("link").toString());
|
|
||||||
a._dateTime = QDateTime::fromString(json.value("date").toString(), Qt::ISODate);
|
|
||||||
list.append(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
_activityLists.append(list);
|
|
||||||
|
|
||||||
emit activityJobStatusCode(statusCode);
|
|
||||||
|
|
||||||
combineActivityLists();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityListModel::addErrorToActivityList(Activity activity) {
|
|
||||||
qCInfo(lcActivity) << "Error successfully added to the notification list: " << activity._subject;
|
|
||||||
_notificationErrorsLists.prepend(activity);
|
|
||||||
combineActivityLists();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityListModel::addNotificationToActivityList(Activity activity) {
|
|
||||||
qCInfo(lcActivity) << "Notification successfully added to the notification list: " << activity._subject;
|
|
||||||
_notificationLists.prepend(activity);
|
|
||||||
combineActivityLists();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityListModel::clearNotifications() {
|
|
||||||
qCInfo(lcActivity) << "Clear the notifications";
|
|
||||||
_notificationLists.clear();
|
|
||||||
combineActivityLists();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityListModel::removeActivityFromActivityList(int row) {
|
|
||||||
Activity activity = _finalList.at(row);
|
|
||||||
removeActivityFromActivityList(activity);
|
|
||||||
combineActivityLists();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityListModel::addSyncFileItemToActivityList(Activity activity) {
|
|
||||||
qCInfo(lcActivity) << "Successfully added to the activity list: " << activity._subject;
|
|
||||||
_syncFileItemLists.prepend(activity);
|
|
||||||
combineActivityLists();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityListModel::removeActivityFromActivityList(Activity activity) {
|
|
||||||
qCInfo(lcActivity) << "Activity/Notification/Error successfully dismissed: " << activity._subject;
|
|
||||||
qCInfo(lcActivity) << "Trying to remove Activity/Notification/Error from view... ";
|
|
||||||
|
|
||||||
int index = -1;
|
|
||||||
if(activity._type == Activity::ActivityType){
|
|
||||||
index = _activityLists.indexOf(activity);
|
|
||||||
if(index != -1) _activityLists.removeAt(index);
|
|
||||||
} else if(activity._type == Activity::NotificationType){
|
|
||||||
index = _notificationLists.indexOf(activity);
|
|
||||||
if(index != -1) _notificationLists.removeAt(index);
|
|
||||||
} else {
|
|
||||||
index = _notificationErrorsLists.indexOf(activity);
|
|
||||||
if(index != -1) _notificationErrorsLists.removeAt(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(index != -1){
|
|
||||||
qCInfo(lcActivity) << "Activity/Notification/Error successfully removed from the list.";
|
|
||||||
qCInfo(lcActivity) << "Updating Activity/Notification/Error view.";
|
|
||||||
combineActivityLists();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ActivityListModel::combineActivityLists()
|
|
||||||
{
|
|
||||||
ActivityList resultList;
|
|
||||||
|
|
||||||
std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end());
|
|
||||||
resultList.append(_notificationErrorsLists);
|
|
||||||
|
|
||||||
std::sort(_notificationLists.begin(), _notificationLists.end());
|
|
||||||
resultList.append(_notificationLists);
|
|
||||||
|
|
||||||
std::sort(_syncFileItemLists.begin(), _syncFileItemLists.end());
|
|
||||||
resultList.append(_syncFileItemLists);
|
|
||||||
|
|
||||||
std::sort(_activityLists.begin(), _activityLists.end());
|
|
||||||
resultList.append(_activityLists);
|
|
||||||
|
|
||||||
beginResetModel();
|
|
||||||
_finalList.clear();
|
|
||||||
endResetModel();
|
|
||||||
|
|
||||||
beginInsertRows(QModelIndex(), 0, resultList.count());
|
|
||||||
_finalList = resultList;
|
|
||||||
endInsertRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ActivityListModel::canFetchActivities() const {
|
|
||||||
return _accountState->isConnected() && _accountState->account()->capabilities().hasActivities();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityListModel::fetchMore(const QModelIndex &)
|
|
||||||
{
|
|
||||||
if (canFetchActivities()) {
|
|
||||||
startFetchJob();
|
|
||||||
} else {
|
|
||||||
_doneFetching = true;
|
|
||||||
combineActivityLists();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityListModel::slotRefreshActivity()
|
|
||||||
{
|
|
||||||
_activityLists.clear();
|
|
||||||
_doneFetching = false;
|
|
||||||
_currentItem = 0;
|
|
||||||
|
|
||||||
if (canFetchActivities()) {
|
|
||||||
startFetchJob();
|
|
||||||
} else {
|
|
||||||
_doneFetching = true;
|
|
||||||
combineActivityLists();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityListModel::slotRemoveAccount()
|
|
||||||
{
|
|
||||||
_finalList.clear();
|
|
||||||
_activityLists.clear();
|
|
||||||
_currentlyFetching = false;
|
|
||||||
_doneFetching = false;
|
|
||||||
_currentItem = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,626 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
|
||||||
*
|
|
||||||
* 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 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
* for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <QtGui>
|
|
||||||
#include <QtWidgets>
|
|
||||||
|
|
||||||
#include "activitylistmodel.h"
|
|
||||||
#include "activitywidget.h"
|
|
||||||
#include "syncresult.h"
|
|
||||||
#include "logger.h"
|
|
||||||
#include "theme.h"
|
|
||||||
#include "folderman.h"
|
|
||||||
#include "syncfileitem.h"
|
|
||||||
#include "folder.h"
|
|
||||||
#include "openfilemanager.h"
|
|
||||||
#include "owncloudpropagator.h"
|
|
||||||
#include "account.h"
|
|
||||||
#include "accountstate.h"
|
|
||||||
#include "accountmanager.h"
|
|
||||||
#include "activityitemdelegate.h"
|
|
||||||
#include "QProgressIndicator.h"
|
|
||||||
#include "notificationconfirmjob.h"
|
|
||||||
#include "servernotificationhandler.h"
|
|
||||||
#include "theme.h"
|
|
||||||
#include "ocsjob.h"
|
|
||||||
#include "configfile.h"
|
|
||||||
#include "guiutility.h"
|
|
||||||
#include "socketapi.h"
|
|
||||||
#include "ui_activitywidget.h"
|
|
||||||
#include "syncengine.h"
|
|
||||||
|
|
||||||
#include <climits>
|
|
||||||
|
|
||||||
// time span in milliseconds which has to be between two
|
|
||||||
// refreshes of the notifications
|
|
||||||
#define NOTIFICATION_REQUEST_FREE_PERIOD 15000
|
|
||||||
|
|
||||||
namespace OCC {
|
|
||||||
|
|
||||||
ActivityWidget::ActivityWidget(AccountState *accountState, QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
, _ui(new Ui::ActivityWidget)
|
|
||||||
, _notificationRequestsRunning(0)
|
|
||||||
, _accountState(accountState)
|
|
||||||
, _accept(tr("Accept"))
|
|
||||||
, _remote_share("remote_share")
|
|
||||||
{
|
|
||||||
_ui->setupUi(this);
|
|
||||||
|
|
||||||
// Adjust copyToClipboard() when making changes here!
|
|
||||||
#if defined(Q_OS_MAC)
|
|
||||||
_ui->_activityList->setMinimumWidth(400);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
_model = new ActivityListModel(accountState, this);
|
|
||||||
ActivityItemDelegate *delegate = new ActivityItemDelegate;
|
|
||||||
delegate->setParent(this);
|
|
||||||
_ui->_activityList->setItemDelegate(delegate);
|
|
||||||
_ui->_activityList->setAlternatingRowColors(true);
|
|
||||||
_ui->_activityList->setModel(_model);
|
|
||||||
|
|
||||||
showLabels();
|
|
||||||
|
|
||||||
connect(_model, &ActivityListModel::activityJobStatusCode,
|
|
||||||
this, &ActivityWidget::slotAccountActivityStatus);
|
|
||||||
|
|
||||||
connect(_model, &QAbstractItemModel::rowsInserted, this, &ActivityWidget::rowsInserted);
|
|
||||||
|
|
||||||
connect(delegate, &ActivityItemDelegate::primaryButtonClickedOnItemView, this, &ActivityWidget::slotPrimaryButtonClickedOnListView);
|
|
||||||
connect(delegate, &ActivityItemDelegate::secondaryButtonClickedOnItemView, this, &ActivityWidget::slotSecondaryButtonClickedOnListView);
|
|
||||||
connect(_ui->_activityList, &QListView::activated, this, &ActivityWidget::slotOpenFile);
|
|
||||||
|
|
||||||
connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo,
|
|
||||||
this, &ActivityWidget::slotProgressInfo);
|
|
||||||
connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted,
|
|
||||||
this, &ActivityWidget::slotItemCompleted);
|
|
||||||
connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError,
|
|
||||||
this, &ActivityWidget::addError);
|
|
||||||
|
|
||||||
_removeTimer.setInterval(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivityWidget::~ActivityWidget()
|
|
||||||
{
|
|
||||||
delete _ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::slotProgressInfo(const QString &folder, const ProgressInfo &progress)
|
|
||||||
{
|
|
||||||
if (progress.status() == ProgressInfo::Reconcile) {
|
|
||||||
// Wipe all non-persistent entries - as well as the persistent ones
|
|
||||||
// in cases where a local discovery was done.
|
|
||||||
auto f = FolderMan::instance()->folder(folder);
|
|
||||||
if (!f)
|
|
||||||
return;
|
|
||||||
const auto &engine = f->syncEngine();
|
|
||||||
const auto style = engine.lastLocalDiscoveryStyle();
|
|
||||||
foreach (Activity activity, _model->errorsList()) {
|
|
||||||
if (activity._folder != folder){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (style == LocalDiscoveryStyle::FilesystemOnly){
|
|
||||||
_model->removeActivityFromActivityList(activity);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(activity._status == SyncFileItem::Conflict && !QFileInfo(f->path() + activity._file).exists()){
|
|
||||||
_model->removeActivityFromActivityList(activity);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(activity._status == SyncFileItem::FileLocked && !QFileInfo(f->path() + activity._file).exists()){
|
|
||||||
_model->removeActivityFromActivityList(activity);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if(activity._status == SyncFileItem::FileIgnored && !QFileInfo(f->path() + activity._file).exists()){
|
|
||||||
_model->removeActivityFromActivityList(activity);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!QFileInfo(f->path() + activity._file).exists()){
|
|
||||||
_model->removeActivityFromActivityList(activity);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto path = QFileInfo(activity._file).dir().path().toUtf8();
|
|
||||||
if (path == ".")
|
|
||||||
path.clear();
|
|
||||||
|
|
||||||
if(engine.shouldDiscoverLocally(path))
|
|
||||||
_model->removeActivityFromActivityList(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress.status() == ProgressInfo::Done) {
|
|
||||||
// We keep track very well of pending conflicts.
|
|
||||||
// Inform other components about them.
|
|
||||||
QStringList conflicts;
|
|
||||||
foreach (Activity activity, _model->errorsList()) {
|
|
||||||
if (activity._folder == folder
|
|
||||||
&& activity._status == SyncFileItem::Conflict) {
|
|
||||||
conflicts.append(activity._file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit ProgressDispatcher::instance()->folderConflicts(folder, conflicts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item){
|
|
||||||
auto folderInstance = FolderMan::instance()->folder(folder);
|
|
||||||
|
|
||||||
if (!folderInstance)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// check if we are adding it to the right account and if it is useful information (protocol errors)
|
|
||||||
if(folderInstance->accountState() == _accountState){
|
|
||||||
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in " << item->_errorString;
|
|
||||||
|
|
||||||
Activity activity;
|
|
||||||
activity._type = Activity::SyncFileItemType; //client activity
|
|
||||||
activity._status = item->_status;
|
|
||||||
activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
|
|
||||||
activity._message = item->_originalFile;
|
|
||||||
activity._link = folderInstance->accountState()->account()->url();
|
|
||||||
activity._accName = folderInstance->accountState()->account()->displayName();
|
|
||||||
activity._file = item->_file;
|
|
||||||
activity._folder = folder;
|
|
||||||
|
|
||||||
if(item->_status == SyncFileItem::NoStatus || item->_status == SyncFileItem::Success){
|
|
||||||
qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully.";
|
|
||||||
activity._message.prepend(" ");
|
|
||||||
activity._message.prepend(tr("Synced"));
|
|
||||||
_model->addSyncFileItemToActivityList(activity);
|
|
||||||
} else {
|
|
||||||
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in error " << item->_errorString;
|
|
||||||
activity._subject = item->_errorString;
|
|
||||||
|
|
||||||
// add 'protocol error' to activity list
|
|
||||||
_model->addErrorToActivityList(activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::addError(const QString &folderAlias, const QString &message,
|
|
||||||
ErrorCategory category)
|
|
||||||
{
|
|
||||||
auto folderInstance = FolderMan::instance()->folder(folderAlias);
|
|
||||||
if (!folderInstance)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(folderInstance->accountState() == _accountState){
|
|
||||||
qCWarning(lcActivity) << "Item " << folderInstance->shortGuiLocalPath() << " retrieved resulted in " << message;
|
|
||||||
|
|
||||||
Activity activity;
|
|
||||||
activity._type = Activity::SyncResultType;
|
|
||||||
activity._status = SyncResult::Error;
|
|
||||||
activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
|
|
||||||
activity._subject = message;
|
|
||||||
activity._message = folderInstance->shortGuiLocalPath();
|
|
||||||
activity._link = folderInstance->shortGuiLocalPath();
|
|
||||||
activity._accName = folderInstance->accountState()->account()->displayName();
|
|
||||||
activity._folder = folderAlias;
|
|
||||||
|
|
||||||
|
|
||||||
if (category == ErrorCategory::InsufficientRemoteStorage) {
|
|
||||||
ActivityLink link;
|
|
||||||
link._label = tr("Retry all uploads");
|
|
||||||
link._link = folderInstance->path();
|
|
||||||
link._verb = "";
|
|
||||||
link._isPrimary = true;
|
|
||||||
activity._links.append(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add 'other errors' to activity list
|
|
||||||
_model->addErrorToActivityList(activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ActivityWidget::slotPrimaryButtonClickedOnListView(const QModelIndex &index){
|
|
||||||
QUrl link = qvariant_cast<QString>(index.data(ActivityItemDelegate::LinkRole));
|
|
||||||
QString objectType = index.data(ActivityItemDelegate::ObjectTypeRole).toString();
|
|
||||||
if(!link.isEmpty()){
|
|
||||||
qCWarning(lcActivity) << "Opening" << link.toString() << "in browser for Notification/Activity" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
|
|
||||||
Utility::openBrowser(link, this);
|
|
||||||
} else if(objectType == _remote_share){
|
|
||||||
QVariant customItem = index.data(ActivityItemDelegate::ActionsLinksRole).toList().first();
|
|
||||||
ActivityLink actionLink = qvariant_cast<ActivityLink>(customItem);
|
|
||||||
if(actionLink._label == _accept){
|
|
||||||
qCWarning(lcActivity) << objectType << "action" << actionLink._label << "for" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
|
|
||||||
const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString();
|
|
||||||
slotSendNotificationRequest(accountName, actionLink._link, actionLink._verb, index.row());
|
|
||||||
} else {
|
|
||||||
qCWarning(lcActivity) << "Failed: " << objectType << "action" << actionLink._label << "for" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::slotSecondaryButtonClickedOnListView(const QModelIndex &index){
|
|
||||||
QList<QVariant> customList = index.data(ActivityItemDelegate::ActionsLinksRole).toList();
|
|
||||||
QString objectType = index.data(ActivityItemDelegate::ObjectTypeRole).toString();
|
|
||||||
|
|
||||||
QList<ActivityLink> actionLinks;
|
|
||||||
foreach(QVariant customItem, customList){
|
|
||||||
actionLinks << qvariant_cast<ActivityLink>(customItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(objectType == _remote_share && actionLinks.first()._label == _accept)
|
|
||||||
actionLinks.removeFirst();
|
|
||||||
|
|
||||||
if(qvariant_cast<Activity::Type>(index.data(ActivityItemDelegate::ActionRole)) == Activity::Type::NotificationType){
|
|
||||||
const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString();
|
|
||||||
if(actionLinks.size() == 1){
|
|
||||||
if(actionLinks.at(0)._verb == "DELETE"){
|
|
||||||
qCWarning(lcActivity) << "Dismissing Notification/Activity" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
|
|
||||||
slotSendNotificationRequest(index.data(ActivityItemDelegate::AccountRole).toString(), actionLinks.at(0)._link, actionLinks.at(0)._verb, index.row());
|
|
||||||
}
|
|
||||||
} else if(actionLinks.size() > 1){
|
|
||||||
QMenu menu;
|
|
||||||
qCWarning(lcActivity) << "Displaying menu for Notification/Activity" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
|
|
||||||
foreach (ActivityLink actionLink, actionLinks) {
|
|
||||||
QAction *menuAction = new QAction(actionLink._label, &menu);
|
|
||||||
connect(menuAction, &QAction::triggered, this, [this, index, accountName, actionLink] {
|
|
||||||
this->slotSendNotificationRequest(accountName, actionLink._link, actionLink._verb, index.row());
|
|
||||||
});
|
|
||||||
menu.addAction(menuAction);
|
|
||||||
}
|
|
||||||
menu.exec(QCursor::pos());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Activity::Type activityType = qvariant_cast<Activity::Type>(index.data(ActivityItemDelegate::ActionRole));
|
|
||||||
if(activityType == Activity::Type::SyncFileItemType || activityType == Activity::Type::SyncResultType)
|
|
||||||
slotOpenFile(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::slotNotificationRequestFinished(int statusCode)
|
|
||||||
{
|
|
||||||
int row = sender()->property("activityRow").toInt();
|
|
||||||
|
|
||||||
// the ocs API returns stat code 100 or 200 inside the xml if it succeeded.
|
|
||||||
if (statusCode != OCS_SUCCESS_STATUS_CODE && statusCode != OCS_SUCCESS_STATUS_CODE_V2) {
|
|
||||||
qCWarning(lcActivity) << "Notification Request to Server failed, leave notification visible.";
|
|
||||||
} else {
|
|
||||||
// to do use the model to rebuild the list or remove the item
|
|
||||||
qCWarning(lcActivity) << "Notification Request to Server successed, rebuilding list.";
|
|
||||||
_model->removeActivityFromActivityList(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::slotRefreshActivities()
|
|
||||||
{
|
|
||||||
_model->slotRefreshActivity();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::slotRefreshNotifications()
|
|
||||||
{
|
|
||||||
// start a server notification handler if no notification requests
|
|
||||||
// are running
|
|
||||||
if (_notificationRequestsRunning == 0) {
|
|
||||||
ServerNotificationHandler *snh = new ServerNotificationHandler(_accountState);
|
|
||||||
connect(snh, &ServerNotificationHandler::newNotificationList,
|
|
||||||
this, &ActivityWidget::slotBuildNotificationDisplay);
|
|
||||||
|
|
||||||
snh->slotFetchNotifications();
|
|
||||||
} else {
|
|
||||||
qCWarning(lcActivity) << "Notification request counter not zero.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::slotRemoveAccount()
|
|
||||||
{
|
|
||||||
_model->slotRemoveAccount();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::showLabels()
|
|
||||||
{
|
|
||||||
_ui->_bottomLabel->hide(); // hide whatever was there before
|
|
||||||
QString t("");
|
|
||||||
QSetIterator<QString> i(_accountsWithoutActivities);
|
|
||||||
while (i.hasNext()) {
|
|
||||||
t.append(tr("<br/>Account %1 does not have activities enabled.").arg(i.next()));
|
|
||||||
}
|
|
||||||
if(!t.isEmpty()){
|
|
||||||
_ui->_bottomLabel->setTextFormat(Qt::RichText);
|
|
||||||
_ui->_bottomLabel->setText(t);
|
|
||||||
_ui->_bottomLabel->show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::slotAccountActivityStatus(int statusCode)
|
|
||||||
{
|
|
||||||
if (!(_accountState && _accountState->account())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (statusCode == 999) {
|
|
||||||
_accountsWithoutActivities.insert(_accountState->account()->displayName());
|
|
||||||
} else {
|
|
||||||
_accountsWithoutActivities.remove(_accountState->account()->displayName());
|
|
||||||
}
|
|
||||||
|
|
||||||
checkActivityWidgetVisibility();
|
|
||||||
showLabels();
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Reused from protocol widget. Move over to utilities.
|
|
||||||
QString ActivityWidget::timeString(QDateTime dt, QLocale::FormatType format) const
|
|
||||||
{
|
|
||||||
const QLocale loc = QLocale::system();
|
|
||||||
QString dtFormat = loc.dateTimeFormat(format);
|
|
||||||
static const QRegExp re("(HH|H|hh|h):mm(?!:s)");
|
|
||||||
dtFormat.replace(re, "\\1:mm:ss");
|
|
||||||
return loc.toString(dt, dtFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::storeActivityList(QTextStream &ts)
|
|
||||||
{
|
|
||||||
ActivityList activities = _model->activityList();
|
|
||||||
|
|
||||||
foreach (Activity activity, activities) {
|
|
||||||
ts << right
|
|
||||||
// account name
|
|
||||||
<< qSetFieldWidth(activity._accName.length())
|
|
||||||
<< activity._accName
|
|
||||||
// separator
|
|
||||||
<< qSetFieldWidth(2) << " - "
|
|
||||||
|
|
||||||
// date and time
|
|
||||||
<< qSetFieldWidth(activity._dateTime.toString().length())
|
|
||||||
<< activity._dateTime.toString()
|
|
||||||
// separator
|
|
||||||
<< qSetFieldWidth(2) << " - "
|
|
||||||
|
|
||||||
// fileq
|
|
||||||
<< qSetFieldWidth(activity._file.length())
|
|
||||||
<< activity._file
|
|
||||||
// separator
|
|
||||||
<< qSetFieldWidth(2) << " - "
|
|
||||||
|
|
||||||
// subject
|
|
||||||
<< qSetFieldWidth(activity._subject.length())
|
|
||||||
<< activity._subject
|
|
||||||
// separator
|
|
||||||
<< qSetFieldWidth(2) << " - "
|
|
||||||
|
|
||||||
// message
|
|
||||||
<< qSetFieldWidth(activity._message.length())
|
|
||||||
<< activity._message
|
|
||||||
<< endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::checkActivityWidgetVisibility()
|
|
||||||
{
|
|
||||||
int accountCount = AccountManager::instance()->accounts().count();
|
|
||||||
bool hasAccountsWithActivity =
|
|
||||||
_accountsWithoutActivities.count() != accountCount;
|
|
||||||
|
|
||||||
_ui->_activityList->setVisible(hasAccountsWithActivity);
|
|
||||||
|
|
||||||
emit hideActivityTab(!hasAccountsWithActivity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::slotOpenFile(QModelIndex indx)
|
|
||||||
{
|
|
||||||
qCDebug(lcActivity) << indx.isValid() << indx.data(ActivityItemDelegate::PathRole).toString() << QFile::exists(indx.data(ActivityItemDelegate::PathRole).toString());
|
|
||||||
if (indx.isValid()) {
|
|
||||||
QString fullPath = indx.data(ActivityItemDelegate::PathRole).toString();
|
|
||||||
if(!fullPath.isEmpty()){
|
|
||||||
if (QFile::exists(fullPath)) {
|
|
||||||
showInFileManager(fullPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GUI: Display the notifications.
|
|
||||||
// All notifications in list are coming from the same account
|
|
||||||
// but in the _widgetForNotifId hash widgets for all accounts are
|
|
||||||
// collected.
|
|
||||||
void ActivityWidget::slotBuildNotificationDisplay(const ActivityList &list)
|
|
||||||
{
|
|
||||||
// Whether a new notification was added to the list
|
|
||||||
bool newNotificationShown = false;
|
|
||||||
|
|
||||||
_model->clearNotifications();
|
|
||||||
|
|
||||||
foreach (auto activity, list) {
|
|
||||||
if (_blacklistedNotifications.contains(activity)) {
|
|
||||||
qCInfo(lcActivity) << "Activity in blacklist, skip";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle gui logs. In order to NOT annoy the user with every fetching of the
|
|
||||||
// notifications the notification id is stored in a Set. Only if an id
|
|
||||||
// is not in the set, it qualifies for guiLog.
|
|
||||||
// Important: The _guiLoggedNotifications set must be wiped regularly which
|
|
||||||
// will repeat the gui log.
|
|
||||||
|
|
||||||
// after one hour, clear the gui log notification store
|
|
||||||
if (_guiLogTimer.elapsed() > 60 * 60 * 1000) {
|
|
||||||
_guiLoggedNotifications.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_guiLoggedNotifications.contains(activity._id)) {
|
|
||||||
newNotificationShown = true;
|
|
||||||
_guiLoggedNotifications.insert(activity._id);
|
|
||||||
|
|
||||||
// Assemble a tray notification for the NEW notification
|
|
||||||
ConfigFile cfg;
|
|
||||||
if(cfg.optionalServerNotifications()){
|
|
||||||
if(AccountManager::instance()->accounts().count() == 1){
|
|
||||||
emit guiLog(activity._subject, "");
|
|
||||||
} else {
|
|
||||||
emit guiLog(activity._subject, activity._accName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_model->addNotificationToActivityList(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// restart the gui log timer now that we show a new notification
|
|
||||||
if(newNotificationShown) {
|
|
||||||
_guiLogTimer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row)
|
|
||||||
{
|
|
||||||
qCInfo(lcActivity) << "Server Notification Request " << verb << link << "on account" << accountName;
|
|
||||||
|
|
||||||
const QStringList validVerbs = QStringList() << "GET"
|
|
||||||
<< "PUT"
|
|
||||||
<< "POST"
|
|
||||||
<< "DELETE";
|
|
||||||
|
|
||||||
if (validVerbs.contains(verb)) {
|
|
||||||
AccountStatePtr acc = AccountManager::instance()->account(accountName);
|
|
||||||
if (acc) {
|
|
||||||
NotificationConfirmJob *job = new NotificationConfirmJob(acc->account());
|
|
||||||
QUrl l(link);
|
|
||||||
job->setLinkAndVerb(l, verb);
|
|
||||||
job->setProperty("activityRow", QVariant::fromValue(row));
|
|
||||||
connect(job, &AbstractNetworkJob::networkError,
|
|
||||||
this, &ActivityWidget::slotNotifyNetworkError);
|
|
||||||
connect(job, &NotificationConfirmJob::jobFinished,
|
|
||||||
this, &ActivityWidget::slotNotifyServerFinished);
|
|
||||||
job->start();
|
|
||||||
|
|
||||||
// count the number of running notification requests. If this member var
|
|
||||||
// is larger than zero, no new fetching of notifications is started
|
|
||||||
_notificationRequestsRunning++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCWarning(lcActivity) << "Notification Links: Invalid verb:" << verb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::endNotificationRequest(int replyCode)
|
|
||||||
{
|
|
||||||
_notificationRequestsRunning--;
|
|
||||||
slotNotificationRequestFinished(replyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::slotNotifyNetworkError(QNetworkReply *reply)
|
|
||||||
{
|
|
||||||
NotificationConfirmJob *job = qobject_cast<NotificationConfirmJob *>(sender());
|
|
||||||
if (!job) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
|
|
||||||
endNotificationRequest(resultCode);
|
|
||||||
qCWarning(lcActivity) << "Server notify job failed with code " << resultCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityWidget::slotNotifyServerFinished(const QString &reply, int replyCode)
|
|
||||||
{
|
|
||||||
NotificationConfirmJob *job = qobject_cast<NotificationConfirmJob *>(sender());
|
|
||||||
if (!job) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
endNotificationRequest(replyCode);
|
|
||||||
qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================================================================== */
|
|
||||||
|
|
||||||
ActivitySettings::ActivitySettings(AccountState *accountState, QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
, _accountState(accountState)
|
|
||||||
{
|
|
||||||
_vbox = new QVBoxLayout(this);
|
|
||||||
setLayout(_vbox);
|
|
||||||
|
|
||||||
_activityWidget = new ActivityWidget(_accountState, this);
|
|
||||||
|
|
||||||
_vbox->insertWidget(1, _activityWidget);
|
|
||||||
connect(_activityWidget, &ActivityWidget::guiLog, this, &ActivitySettings::guiLog);
|
|
||||||
connect(&_notificationCheckTimer, &QTimer::timeout,
|
|
||||||
this, &ActivitySettings::slotRegularNotificationCheck);
|
|
||||||
|
|
||||||
// Add a progress indicator to spin if the acitivity list is updated.
|
|
||||||
_progressIndicator = new QProgressIndicator(this);
|
|
||||||
|
|
||||||
// connect a model signal to stop the animation
|
|
||||||
connect(_activityWidget, &ActivityWidget::rowsInserted, _progressIndicator, &QProgressIndicator::stopAnimation);
|
|
||||||
connect(_activityWidget, &ActivityWidget::rowsInserted, this, &ActivitySettings::slotDisplayActivities);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivitySettings::slotDisplayActivities(){
|
|
||||||
_vbox->removeWidget(_progressIndicator);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivitySettings::setNotificationRefreshInterval(std::chrono::milliseconds interval)
|
|
||||||
{
|
|
||||||
qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval.count() / 1000 << " sec interval";
|
|
||||||
_notificationCheckTimer.start(interval.count());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivitySettings::slotRemoveAccount()
|
|
||||||
{
|
|
||||||
_activityWidget->slotRemoveAccount();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivitySettings::slotRefresh()
|
|
||||||
{
|
|
||||||
// QElapsedTimer isn't actually constructed as invalid.
|
|
||||||
if (!_timeSinceLastCheck.contains(_accountState)) {
|
|
||||||
_timeSinceLastCheck[_accountState].invalidate();
|
|
||||||
}
|
|
||||||
QElapsedTimer &timer = _timeSinceLastCheck[_accountState];
|
|
||||||
|
|
||||||
// Fetch Activities only if visible and if last check is longer than 15 secs ago
|
|
||||||
if (timer.isValid() && timer.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD) {
|
|
||||||
qCDebug(lcActivity) << "Do not check as last check is only secs ago: " << timer.elapsed() / 1000;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_accountState && _accountState->isConnected()) {
|
|
||||||
if (isVisible() || !timer.isValid()) {
|
|
||||||
_vbox->insertWidget(0, _progressIndicator);
|
|
||||||
_vbox->setAlignment(_progressIndicator, Qt::AlignHCenter);
|
|
||||||
_progressIndicator->startAnimation();
|
|
||||||
_activityWidget->slotRefreshActivities();
|
|
||||||
}
|
|
||||||
_activityWidget->slotRefreshNotifications();
|
|
||||||
timer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivitySettings::slotRegularNotificationCheck()
|
|
||||||
{
|
|
||||||
slotRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ActivitySettings::event(QEvent *e)
|
|
||||||
{
|
|
||||||
if (e->type() == QEvent::Show) {
|
|
||||||
slotRefresh();
|
|
||||||
}
|
|
||||||
return QWidget::event(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivitySettings::~ActivitySettings()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
|
||||||
*
|
|
||||||
* 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 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
* for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ACTIVITYWIDGET_H
|
|
||||||
#define ACTIVITYWIDGET_H
|
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QLocale>
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include "progressdispatcher.h"
|
|
||||||
#include "owncloudgui.h"
|
|
||||||
#include "account.h"
|
|
||||||
#include "activitydata.h"
|
|
||||||
#include "accountmanager.h"
|
|
||||||
|
|
||||||
#include "ui_activitywidget.h"
|
|
||||||
|
|
||||||
class QPushButton;
|
|
||||||
class QProgressIndicator;
|
|
||||||
|
|
||||||
namespace OCC {
|
|
||||||
|
|
||||||
class Account;
|
|
||||||
class AccountStatusPtr;
|
|
||||||
class JsonApiJob;
|
|
||||||
class ActivityListModel;
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
class ActivityWidget;
|
|
||||||
}
|
|
||||||
class Application;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The ActivityWidget class
|
|
||||||
* @ingroup gui
|
|
||||||
*
|
|
||||||
* The list widget to display the activities, contained in the
|
|
||||||
* subsequent ActivitySettings widget.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ActivityWidget : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ActivityWidget(AccountState *accountState, QWidget *parent = nullptr);
|
|
||||||
~ActivityWidget();
|
|
||||||
QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); }
|
|
||||||
void storeActivityList(QTextStream &ts);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adjusts the activity tab's and some widgets' visibility
|
|
||||||
*
|
|
||||||
* Based on whether activities are enabled and whether notifications are
|
|
||||||
* available.
|
|
||||||
*/
|
|
||||||
void checkActivityWidgetVisibility();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void slotOpenFile(QModelIndex indx);
|
|
||||||
void slotRefreshActivities();
|
|
||||||
void slotRefreshNotifications();
|
|
||||||
void slotRemoveAccount();
|
|
||||||
void slotAccountActivityStatus(int statusCode);
|
|
||||||
void addError(const QString &folderAlias, const QString &message, ErrorCategory category);
|
|
||||||
void slotProgressInfo(const QString &folder, const ProgressInfo &progress);
|
|
||||||
void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void guiLog(const QString &, const QString &);
|
|
||||||
void rowsInserted();
|
|
||||||
void hideActivityTab(bool);
|
|
||||||
void sendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void slotBuildNotificationDisplay(const ActivityList &list);
|
|
||||||
void slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row);
|
|
||||||
void slotNotifyNetworkError(QNetworkReply *);
|
|
||||||
void slotNotifyServerFinished(const QString &reply, int replyCode);
|
|
||||||
void endNotificationRequest(int replyCode);
|
|
||||||
void slotNotificationRequestFinished(int statusCode);
|
|
||||||
void slotPrimaryButtonClickedOnListView(const QModelIndex &index);
|
|
||||||
void slotSecondaryButtonClickedOnListView(const QModelIndex &index);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void showLabels();
|
|
||||||
QString timeString(QDateTime dt, QLocale::FormatType format) const;
|
|
||||||
Ui::ActivityWidget *_ui;
|
|
||||||
QSet<QString> _accountsWithoutActivities;
|
|
||||||
QElapsedTimer _guiLogTimer;
|
|
||||||
QSet<int> _guiLoggedNotifications;
|
|
||||||
ActivityList _blacklistedNotifications;
|
|
||||||
|
|
||||||
QTimer _removeTimer;
|
|
||||||
|
|
||||||
// number of currently running notification requests. If non zero,
|
|
||||||
// no query for notifications is started.
|
|
||||||
int _notificationRequestsRunning;
|
|
||||||
|
|
||||||
ActivityListModel *_model;
|
|
||||||
AccountState *_accountState;
|
|
||||||
const QString _accept;
|
|
||||||
const QString _remote_share;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The ActivitySettings class
|
|
||||||
* @ingroup gui
|
|
||||||
*
|
|
||||||
* Implements a tab for the settings dialog, displaying the three activity
|
|
||||||
* lists.
|
|
||||||
*/
|
|
||||||
class ActivitySettings : public QWidget
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ActivitySettings(AccountState *accountState, QWidget *parent = nullptr);
|
|
||||||
|
|
||||||
~ActivitySettings();
|
|
||||||
QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); }
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void slotRefresh();
|
|
||||||
void slotRemoveAccount();
|
|
||||||
void setNotificationRefreshInterval(std::chrono::milliseconds interval);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void slotRegularNotificationCheck();
|
|
||||||
void slotDisplayActivities();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void guiLog(const QString &, const QString &);
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool event(QEvent *e) override;
|
|
||||||
|
|
||||||
ActivityWidget *_activityWidget;
|
|
||||||
QProgressIndicator *_progressIndicator;
|
|
||||||
QVBoxLayout *_vbox;
|
|
||||||
QTimer _notificationCheckTimer;
|
|
||||||
QHash<AccountState *, QElapsedTimer> _timeSinceLastCheck;
|
|
||||||
|
|
||||||
AccountState *_accountState;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#endif // ActivityWIDGET_H
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>OCC::ActivityWidget</class>
|
|
||||||
<widget class="QWidget" name="OCC::ActivityWidget">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>652</width>
|
|
||||||
<height>556</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string notr="true">Form</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<property name="sizeConstraint">
|
|
||||||
<enum>QLayout::SetDefaultConstraint</enum>
|
|
||||||
</property>
|
|
||||||
<property name="leftMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="rightMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="bottomMargin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QListView" name="_activityList">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="frameShape">
|
|
||||||
<enum>QFrame::StyledPanel</enum>
|
|
||||||
</property>
|
|
||||||
<property name="frameShadow">
|
|
||||||
<enum>QFrame::Sunken</enum>
|
|
||||||
</property>
|
|
||||||
<property name="lineWidth">
|
|
||||||
<number>1</number>
|
|
||||||
</property>
|
|
||||||
<property name="sizeAdjustPolicy">
|
|
||||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
|
||||||
</property>
|
|
||||||
<property name="editTriggers">
|
|
||||||
<set>QAbstractItemView::NoEditTriggers</set>
|
|
||||||
</property>
|
|
||||||
<property name="showDropIndicator" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="defaultDropAction">
|
|
||||||
<enum>Qt::IgnoreAction</enum>
|
|
||||||
</property>
|
|
||||||
<property name="resizeMode">
|
|
||||||
<enum>QListView::Adjust</enum>
|
|
||||||
</property>
|
|
||||||
<property name="viewMode">
|
|
||||||
<enum>QListView::ListMode</enum>
|
|
||||||
</property>
|
|
||||||
<property name="modelColumn">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="_bottomLabel">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string notr="true">TextLabel</string>
|
|
||||||
</property>
|
|
||||||
<property name="textFormat">
|
|
||||||
<enum>Qt::RichText</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<tabstops>
|
|
||||||
<tabstop>_activityList</tabstop>
|
|
||||||
</tabstops>
|
|
||||||
<resources/>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
||||||
@@ -52,6 +52,7 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
|
||||||
class QSocket;
|
class QSocket;
|
||||||
|
|
||||||
@@ -122,6 +123,15 @@ Application::Application(int &argc, char **argv)
|
|||||||
// TODO: Can't set this without breaking current config paths
|
// TODO: Can't set this without breaking current config paths
|
||||||
// setOrganizationName(QLatin1String(APPLICATION_VENDOR));
|
// setOrganizationName(QLatin1String(APPLICATION_VENDOR));
|
||||||
setOrganizationDomain(QLatin1String(APPLICATION_REV_DOMAIN));
|
setOrganizationDomain(QLatin1String(APPLICATION_REV_DOMAIN));
|
||||||
|
|
||||||
|
// setDesktopFilename to provide wayland compatibility (in general: conformance with naming standards)
|
||||||
|
// but only on Qt >= 5.7, where setDesktopFilename was introduced
|
||||||
|
#if (QT_VERSION >= 0x050700)
|
||||||
|
QString desktopFileName = QString(QLatin1String(LINUX_APPLICATION_ID)
|
||||||
|
+ QLatin1String(".desktop"));
|
||||||
|
setDesktopFileName(desktopFileName);
|
||||||
|
#endif
|
||||||
|
|
||||||
setApplicationName(_theme->appName());
|
setApplicationName(_theme->appName());
|
||||||
setWindowIcon(_theme->applicationIcon());
|
setWindowIcon(_theme->applicationIcon());
|
||||||
setAttribute(Qt::AA_UseHighDpiPixmaps, true);
|
setAttribute(Qt::AA_UseHighDpiPixmaps, true);
|
||||||
@@ -253,6 +263,11 @@ Application::Application(int &argc, char **argv)
|
|||||||
|
|
||||||
// Cleanup at Quit.
|
// Cleanup at Quit.
|
||||||
connect(this, &QCoreApplication::aboutToQuit, this, &Application::slotCleanup);
|
connect(this, &QCoreApplication::aboutToQuit, this, &Application::slotCleanup);
|
||||||
|
|
||||||
|
// Allow other classes to hook into isShowingSettingsDialog() signals (re-auth widgets, for example)
|
||||||
|
connect(_gui.data(), &ownCloudGui::isShowingSettingsDialog, this, &Application::slotGuiIsShowingSettings);
|
||||||
|
|
||||||
|
_gui->createTray();
|
||||||
}
|
}
|
||||||
|
|
||||||
Application::~Application()
|
Application::~Application()
|
||||||
@@ -264,6 +279,8 @@ Application::~Application()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove the account from the account manager so it can be deleted.
|
// Remove the account from the account manager so it can be deleted.
|
||||||
|
disconnect(AccountManager::instance(), &AccountManager::accountRemoved,
|
||||||
|
this, &Application::slotAccountStateRemoved);
|
||||||
AccountManager::instance()->shutdown();
|
AccountManager::instance()->shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +392,7 @@ void Application::slotownCloudWizardDone(int res)
|
|||||||
Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), true);
|
Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_gui->slotShowSettings();
|
Systray::instance()->showWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,5 +660,9 @@ void Application::showSettingsDialog()
|
|||||||
_gui->slotShowSettings();
|
_gui->slotShowSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::slotGuiIsShowingSettings()
|
||||||
|
{
|
||||||
|
emit isShowingSettingsDialog();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ protected:
|
|||||||
signals:
|
signals:
|
||||||
void folderRemoved();
|
void folderRemoved();
|
||||||
void folderStateChanged(Folder *);
|
void folderStateChanged(Folder *);
|
||||||
|
void isShowingSettingsDialog();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void slotParseMessage(const QString &, QObject *);
|
void slotParseMessage(const QString &, QObject *);
|
||||||
@@ -91,6 +92,7 @@ protected slots:
|
|||||||
void slotAccountStateAdded(AccountState *accountState);
|
void slotAccountStateAdded(AccountState *accountState);
|
||||||
void slotAccountStateRemoved(AccountState *accountState);
|
void slotAccountStateRemoved(AccountState *accountState);
|
||||||
void slotSystemOnlineConfigurationChanged(QNetworkConfiguration);
|
void slotSystemOnlineConfigurationChanged(QNetworkConfiguration);
|
||||||
|
void slotGuiIsShowingSettings();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setHelp();
|
void setHelp();
|
||||||
|
|||||||
@@ -249,6 +249,11 @@ void ConnectionValidator::slotCapabilitiesRecieved(const QJsonDocument &json)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for the directEditing capability
|
||||||
|
QUrl directEditingURL = QUrl(caps["files"].toObject()["directEditing"].toObject()["url"].toString());
|
||||||
|
QString directEditingETag = caps["files"].toObject()["directEditing"].toObject()["etag"].toString();
|
||||||
|
_account->fetchDirectEditors(directEditingURL, directEditingETag);
|
||||||
|
|
||||||
fetchUser();
|
fetchUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QClipboard>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
#include "account.h"
|
#include "account.h"
|
||||||
#include "creds/flow2auth.h"
|
#include "flow2auth.h"
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
@@ -28,6 +30,17 @@ namespace OCC {
|
|||||||
|
|
||||||
Q_LOGGING_CATEGORY(lcFlow2auth, "nextcloud.sync.credentials.flow2auth", QtInfoMsg)
|
Q_LOGGING_CATEGORY(lcFlow2auth, "nextcloud.sync.credentials.flow2auth", QtInfoMsg)
|
||||||
|
|
||||||
|
|
||||||
|
Flow2Auth::Flow2Auth(Account *account, QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, _account(account)
|
||||||
|
, _isBusy(false)
|
||||||
|
, _hasToken(false)
|
||||||
|
{
|
||||||
|
_pollTimer.setInterval(1000);
|
||||||
|
QObject::connect(&_pollTimer, &QTimer::timeout, this, &Flow2Auth::slotPollTimerTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
Flow2Auth::~Flow2Auth()
|
Flow2Auth::~Flow2Auth()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -47,22 +60,46 @@ QUrl Flow2Auth::authorisationLink() const
|
|||||||
|
|
||||||
void Flow2Auth::openBrowser()
|
void Flow2Auth::openBrowser()
|
||||||
{
|
{
|
||||||
_pollTimer.stop();
|
fetchNewToken(TokenAction::actionOpenBrowser);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Flow2Auth::copyLinkToClipboard()
|
||||||
|
{
|
||||||
|
fetchNewToken(TokenAction::actionCopyLinkToClipboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Flow2Auth::fetchNewToken(const TokenAction action)
|
||||||
|
{
|
||||||
|
if(_isBusy)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isBusy = true;
|
||||||
|
_hasToken = false;
|
||||||
|
|
||||||
|
emit statusChanged(PollStatus::statusFetchToken, 0);
|
||||||
|
|
||||||
// Step 1: Initiate a login, do an anonymous POST request
|
// Step 1: Initiate a login, do an anonymous POST request
|
||||||
QUrl url = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/login/v2"));
|
QUrl url = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/login/v2"));
|
||||||
|
|
||||||
auto job = _account->sendRequest("POST", url);
|
// add 'Content-Length: 0' header (see https://github.com/nextcloud/desktop/issues/1473)
|
||||||
|
QNetworkRequest req;
|
||||||
|
req.setHeader(QNetworkRequest::ContentLengthHeader, "0");
|
||||||
|
|
||||||
|
auto job = _account->sendRequest("POST", url, req);
|
||||||
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
|
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
|
||||||
|
|
||||||
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
|
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this, action](QNetworkReply *reply) {
|
||||||
auto jsonData = reply->readAll();
|
auto jsonData = reply->readAll();
|
||||||
QJsonParseError jsonParseError;
|
QJsonParseError jsonParseError;
|
||||||
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
||||||
|
QString pollToken, pollEndpoint, loginUrl;
|
||||||
|
|
||||||
QString pollToken = json.value("poll").toObject().value("token").toString();
|
if (reply->error() == QNetworkReply::NoError && jsonParseError.error == QJsonParseError::NoError
|
||||||
QString pollEndpoint = json.value("poll").toObject().value("endpoint").toString();
|
&& !json.isEmpty()) {
|
||||||
QUrl loginUrl = json["login"].toString();
|
pollToken = json.value("poll").toObject().value("token").toString();
|
||||||
|
pollEndpoint = json.value("poll").toObject().value("endpoint").toString();
|
||||||
|
loginUrl = json["login"].toString();
|
||||||
|
}
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
|
if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
|
||||||
|| json.isEmpty() || pollToken.isEmpty() || pollEndpoint.isEmpty() || loginUrl.isEmpty()) {
|
|| json.isEmpty() || pollToken.isEmpty() || pollEndpoint.isEmpty() || loginUrl.isEmpty()) {
|
||||||
@@ -81,7 +118,9 @@ void Flow2Auth::openBrowser()
|
|||||||
errorReason = tr("The reply from the server did not contain all expected fields");
|
errorReason = tr("The reply from the server did not contain all expected fields");
|
||||||
}
|
}
|
||||||
qCWarning(lcFlow2auth) << "Error when getting the loginUrl" << json << errorReason;
|
qCWarning(lcFlow2auth) << "Error when getting the loginUrl" << json << errorReason;
|
||||||
emit result(Error);
|
emit result(Error, errorReason);
|
||||||
|
_pollTimer.stop();
|
||||||
|
_isBusy = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,23 +134,50 @@ void Flow2Auth::openBrowser()
|
|||||||
ConfigFile cfg;
|
ConfigFile cfg;
|
||||||
std::chrono::milliseconds polltime = cfg.remotePollInterval();
|
std::chrono::milliseconds polltime = cfg.remotePollInterval();
|
||||||
qCInfo(lcFlow2auth) << "setting remote poll timer interval to" << polltime.count() << "msec";
|
qCInfo(lcFlow2auth) << "setting remote poll timer interval to" << polltime.count() << "msec";
|
||||||
_pollTimer.setInterval(polltime.count());
|
_secondsInterval = (polltime.count() / 1000);
|
||||||
QObject::connect(&_pollTimer, &QTimer::timeout, this, &Flow2Auth::slotPollTimerTimeout);
|
_secondsLeft = _secondsInterval;
|
||||||
_pollTimer.start();
|
emit statusChanged(PollStatus::statusPollCountdown, _secondsLeft);
|
||||||
|
|
||||||
|
if(!_pollTimer.isActive()) {
|
||||||
// Try to open Browser
|
_pollTimer.start();
|
||||||
if (!QDesktopServices::openUrl(authorisationLink())) {
|
|
||||||
// We cannot open the browser, then we claim we don't support Flow2Auth.
|
|
||||||
// Our UI callee should ask the user to copy and open the link.
|
|
||||||
emit result(NotSupported, QString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
switch(action)
|
||||||
|
{
|
||||||
|
case actionOpenBrowser:
|
||||||
|
// Try to open Browser
|
||||||
|
if (!QDesktopServices::openUrl(authorisationLink())) {
|
||||||
|
// We cannot open the browser, then we claim we don't support Flow2Auth.
|
||||||
|
// Our UI callee will ask the user to copy and open the link.
|
||||||
|
emit result(NotSupported);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case actionCopyLinkToClipboard:
|
||||||
|
QApplication::clipboard()->setText(authorisationLink().toString(QUrl::FullyEncoded));
|
||||||
|
emit statusChanged(PollStatus::statusCopyLinkToClipboard, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isBusy = false;
|
||||||
|
_hasToken = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flow2Auth::slotPollTimerTimeout()
|
void Flow2Auth::slotPollTimerTimeout()
|
||||||
{
|
{
|
||||||
_pollTimer.stop();
|
if(_isBusy || !_hasToken)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isBusy = true;
|
||||||
|
|
||||||
|
_secondsLeft--;
|
||||||
|
if(_secondsLeft > 0) {
|
||||||
|
emit statusChanged(PollStatus::statusPollCountdown, _secondsLeft);
|
||||||
|
_isBusy = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit statusChanged(PollStatus::statusPollNow, 0);
|
||||||
|
|
||||||
// Step 2: Poll
|
// Step 2: Poll
|
||||||
QNetworkRequest req;
|
QNetworkRequest req;
|
||||||
@@ -128,10 +194,15 @@ void Flow2Auth::slotPollTimerTimeout()
|
|||||||
auto jsonData = reply->readAll();
|
auto jsonData = reply->readAll();
|
||||||
QJsonParseError jsonParseError;
|
QJsonParseError jsonParseError;
|
||||||
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
||||||
|
QUrl serverUrl;
|
||||||
|
QString loginName, appPassword;
|
||||||
|
|
||||||
QUrl serverUrl = json["server"].toString();
|
if (reply->error() == QNetworkReply::NoError && jsonParseError.error == QJsonParseError::NoError
|
||||||
QString loginName = json["loginName"].toString();
|
&& !json.isEmpty()) {
|
||||||
QString appPassword = json["appPassword"].toString();
|
serverUrl = json["server"].toString();
|
||||||
|
loginName = json["loginName"].toString();
|
||||||
|
appPassword = json["appPassword"].toString();
|
||||||
|
}
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
|
if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
|
||||||
|| json.isEmpty() || serverUrl.isEmpty() || loginName.isEmpty() || appPassword.isEmpty()) {
|
|| json.isEmpty() || serverUrl.isEmpty() || loginName.isEmpty() || appPassword.isEmpty()) {
|
||||||
@@ -151,26 +222,50 @@ void Flow2Auth::slotPollTimerTimeout()
|
|||||||
}
|
}
|
||||||
qCDebug(lcFlow2auth) << "Error when polling for the appPassword" << json << errorReason;
|
qCDebug(lcFlow2auth) << "Error when polling for the appPassword" << json << errorReason;
|
||||||
|
|
||||||
|
// We get a 404 until authentication is done, so don't show this error in the GUI.
|
||||||
|
if(reply->error() != QNetworkReply::ContentNotFoundError)
|
||||||
|
emit result(Error, errorReason);
|
||||||
|
|
||||||
// Forget sensitive data
|
// Forget sensitive data
|
||||||
appPassword.clear();
|
appPassword.clear();
|
||||||
loginName.clear();
|
loginName.clear();
|
||||||
|
|
||||||
// Failed: poll again
|
// Failed: poll again
|
||||||
_pollTimer.start();
|
_secondsLeft = _secondsInterval;
|
||||||
|
_isBusy = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_pollTimer.stop();
|
||||||
|
|
||||||
// Success
|
// Success
|
||||||
qCInfo(lcFlow2auth) << "Success getting the appPassword for user: " << loginName << ", server: " << serverUrl.toString();
|
qCInfo(lcFlow2auth) << "Success getting the appPassword for user: " << loginName << ", server: " << serverUrl.toString();
|
||||||
|
|
||||||
_account->setUrl(serverUrl);
|
_account->setUrl(serverUrl);
|
||||||
|
|
||||||
emit result(LoggedIn, loginName, appPassword);
|
emit result(LoggedIn, QString(), loginName, appPassword);
|
||||||
|
|
||||||
// Forget sensitive data
|
// Forget sensitive data
|
||||||
appPassword.clear();
|
appPassword.clear();
|
||||||
loginName.clear();
|
loginName.clear();
|
||||||
|
|
||||||
|
_loginUrl.clear();
|
||||||
|
_pollToken.clear();
|
||||||
|
_pollEndpoint.clear();
|
||||||
|
|
||||||
|
_isBusy = false;
|
||||||
|
_hasToken = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Flow2Auth::slotPollNow()
|
||||||
|
{
|
||||||
|
// poll now if we're not already doing so
|
||||||
|
if(_isBusy || !_hasToken)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_secondsLeft = 1;
|
||||||
|
slotPollTimerTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
@@ -25,17 +25,23 @@ namespace OCC {
|
|||||||
* Job that does the authorization, grants and fetches the access token via Login Flow v2
|
* Job that does the authorization, grants and fetches the access token via Login Flow v2
|
||||||
*
|
*
|
||||||
* See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2
|
* See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
class Flow2Auth : public QObject
|
class Flow2Auth : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Flow2Auth(Account *account, QObject *parent)
|
enum TokenAction {
|
||||||
: QObject(parent)
|
actionOpenBrowser = 1,
|
||||||
, _account(account)
|
actionCopyLinkToClipboard
|
||||||
{
|
};
|
||||||
}
|
enum PollStatus {
|
||||||
|
statusPollCountdown = 1,
|
||||||
|
statusPollNow,
|
||||||
|
statusFetchToken,
|
||||||
|
statusCopyLinkToClipboard
|
||||||
|
};
|
||||||
|
|
||||||
|
Flow2Auth(Account *account, QObject *parent);
|
||||||
~Flow2Auth();
|
~Flow2Auth();
|
||||||
|
|
||||||
enum Result { NotSupported,
|
enum Result { NotSupported,
|
||||||
@@ -44,6 +50,7 @@ public:
|
|||||||
Q_ENUM(Result);
|
Q_ENUM(Result);
|
||||||
void start();
|
void start();
|
||||||
void openBrowser();
|
void openBrowser();
|
||||||
|
void copyLinkToClipboard();
|
||||||
QUrl authorisationLink() const;
|
QUrl authorisationLink() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -51,18 +58,29 @@ signals:
|
|||||||
* The state has changed.
|
* The state has changed.
|
||||||
* when logged in, appPassword has the value of the app password.
|
* when logged in, appPassword has the value of the app password.
|
||||||
*/
|
*/
|
||||||
void result(Flow2Auth::Result result, const QString &user = QString(), const QString &appPassword = QString());
|
void result(Flow2Auth::Result result, const QString &errorString = QString(),
|
||||||
|
const QString &user = QString(), const QString &appPassword = QString());
|
||||||
|
|
||||||
|
void statusChanged(const PollStatus status, int secondsLeft);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void slotPollNow();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void slotPollTimerTimeout();
|
void slotPollTimerTimeout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void fetchNewToken(const TokenAction action);
|
||||||
|
|
||||||
Account *_account;
|
Account *_account;
|
||||||
QUrl _loginUrl;
|
QUrl _loginUrl;
|
||||||
QString _pollToken;
|
QString _pollToken;
|
||||||
QString _pollEndpoint;
|
QString _pollEndpoint;
|
||||||
QTimer _pollTimer;
|
QTimer _pollTimer;
|
||||||
|
int _secondsLeft;
|
||||||
|
int _secondsInterval;
|
||||||
|
bool _isBusy;
|
||||||
|
bool _hasToken;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
221
src/gui/creds/keychainchunk.cpp
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
|
||||||
|
*
|
||||||
|
* 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 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "account.h"
|
||||||
|
#include "keychainchunk.h"
|
||||||
|
#include "theme.h"
|
||||||
|
#include "networkjobs.h"
|
||||||
|
#include "configfile.h"
|
||||||
|
#include "creds/abstractcredentials.h"
|
||||||
|
|
||||||
|
using namespace QKeychain;
|
||||||
|
|
||||||
|
namespace OCC {
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(lcKeychainChunk, "nextcloud.sync.credentials.keychainchunk", QtInfoMsg)
|
||||||
|
|
||||||
|
namespace KeychainChunk {
|
||||||
|
|
||||||
|
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
|
||||||
|
static void addSettingsToJob(Account *account, QKeychain::Job *job)
|
||||||
|
{
|
||||||
|
Q_UNUSED(account)
|
||||||
|
auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName());
|
||||||
|
settings->setParent(job); // make the job parent to make setting deleted properly
|
||||||
|
job->setSettings(settings.release());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Job
|
||||||
|
*/
|
||||||
|
Job::Job(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
_serviceName = Theme::instance()->appName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WriteJob
|
||||||
|
*/
|
||||||
|
WriteJob::WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent)
|
||||||
|
: Job(parent)
|
||||||
|
{
|
||||||
|
_account = account;
|
||||||
|
_key = key;
|
||||||
|
|
||||||
|
// Windows workaround: Split the private key into chunks of 2048 bytes,
|
||||||
|
// to allow 4k (4096 bit) keys to be saved (obey Windows's limits)
|
||||||
|
_chunkBuffer = data;
|
||||||
|
_chunkCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteJob::start()
|
||||||
|
{
|
||||||
|
slotWriteJobDone(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteJob::slotWriteJobDone(QKeychain::Job *incomingJob)
|
||||||
|
{
|
||||||
|
QKeychain::WritePasswordJob *writeJob = static_cast<QKeychain::WritePasswordJob *>(incomingJob);
|
||||||
|
|
||||||
|
// errors?
|
||||||
|
if (writeJob) {
|
||||||
|
_error = writeJob->error();
|
||||||
|
_errorString = writeJob->errorString();
|
||||||
|
|
||||||
|
if (writeJob->error() != NoError) {
|
||||||
|
qCWarning(lcKeychainChunk) << "Error while writing" << writeJob->key() << "chunk" << writeJob->errorString();
|
||||||
|
_chunkBuffer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write a chunk if there is any in the buffer
|
||||||
|
if (!_chunkBuffer.isEmpty()) {
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
// Windows workaround: Split the data into chunks of 2048 bytes,
|
||||||
|
// to allow 4k (4096 bit) keys to be saved (obey Windows's limits)
|
||||||
|
auto chunk = _chunkBuffer.left(KeychainChunk::ChunkSize);
|
||||||
|
|
||||||
|
_chunkBuffer = _chunkBuffer.right(_chunkBuffer.size() - chunk.size());
|
||||||
|
#else
|
||||||
|
// write full data in one chunk on non-Windows, as usual
|
||||||
|
auto chunk = _chunkBuffer;
|
||||||
|
|
||||||
|
_chunkBuffer.clear();
|
||||||
|
#endif
|
||||||
|
auto index = (_chunkCount++);
|
||||||
|
|
||||||
|
// keep the limit
|
||||||
|
if (_chunkCount > KeychainChunk::MaxChunks) {
|
||||||
|
qCWarning(lcKeychainChunk) << "Maximum chunk count exceeded while writing" << writeJob->key() << "chunk" << QString::number(index) << "cutting off after" << QString::number(KeychainChunk::MaxChunks) << "chunks";
|
||||||
|
|
||||||
|
writeJob->deleteLater();
|
||||||
|
|
||||||
|
_chunkBuffer.clear();
|
||||||
|
|
||||||
|
emit finished(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString kck = AbstractCredentials::keychainKey(
|
||||||
|
_account->url().toString(),
|
||||||
|
_key + (index > 0 ? (QString(".") + QString::number(index)) : QString()),
|
||||||
|
_account->id());
|
||||||
|
|
||||||
|
QKeychain::WritePasswordJob *job = new QKeychain::WritePasswordJob(_serviceName);
|
||||||
|
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
|
||||||
|
addSettingsToJob(_account, job);
|
||||||
|
#endif
|
||||||
|
job->setInsecureFallback(_insecureFallback);
|
||||||
|
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::WriteJob::slotWriteJobDone);
|
||||||
|
// only add the key's (sub)"index" after the first element, to stay compatible with older versions and non-Windows
|
||||||
|
job->setKey(kck);
|
||||||
|
job->setBinaryData(chunk);
|
||||||
|
job->start();
|
||||||
|
|
||||||
|
chunk.clear();
|
||||||
|
} else {
|
||||||
|
emit finished(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeJob->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ReadJob
|
||||||
|
*/
|
||||||
|
ReadJob::ReadJob(Account *account, const QString &key, const bool &keychainMigration, QObject *parent)
|
||||||
|
: Job(parent)
|
||||||
|
{
|
||||||
|
_account = account;
|
||||||
|
_key = key;
|
||||||
|
|
||||||
|
_keychainMigration = keychainMigration;
|
||||||
|
|
||||||
|
_chunkCount = 0;
|
||||||
|
_chunkBuffer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReadJob::start()
|
||||||
|
{
|
||||||
|
_chunkCount = 0;
|
||||||
|
_chunkBuffer.clear();
|
||||||
|
|
||||||
|
const QString kck = AbstractCredentials::keychainKey(
|
||||||
|
_account->url().toString(),
|
||||||
|
_key,
|
||||||
|
_keychainMigration ? QString() : _account->id());
|
||||||
|
|
||||||
|
QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(_serviceName);
|
||||||
|
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
|
||||||
|
addSettingsToJob(_account, job);
|
||||||
|
#endif
|
||||||
|
job->setInsecureFallback(_insecureFallback);
|
||||||
|
job->setKey(kck);
|
||||||
|
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone);
|
||||||
|
job->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReadJob::slotReadJobDone(QKeychain::Job *incomingJob)
|
||||||
|
{
|
||||||
|
// Errors or next chunk?
|
||||||
|
QKeychain::ReadPasswordJob *readJob = static_cast<QKeychain::ReadPasswordJob *>(incomingJob);
|
||||||
|
|
||||||
|
if (readJob) {
|
||||||
|
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
|
||||||
|
_chunkBuffer.append(readJob->binaryData());
|
||||||
|
_chunkCount++;
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
// try to fetch next chunk
|
||||||
|
if (_chunkCount < KeychainChunk::MaxChunks) {
|
||||||
|
const QString kck = AbstractCredentials::keychainKey(
|
||||||
|
_account->url().toString(),
|
||||||
|
_key + QString(".") + QString::number(_chunkCount),
|
||||||
|
_keychainMigration ? QString() : _account->id());
|
||||||
|
|
||||||
|
QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(_serviceName);
|
||||||
|
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
|
||||||
|
addSettingsToJob(_account, job);
|
||||||
|
#endif
|
||||||
|
job->setInsecureFallback(_insecureFallback);
|
||||||
|
job->setKey(kck);
|
||||||
|
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone);
|
||||||
|
job->start();
|
||||||
|
|
||||||
|
readJob->deleteLater();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
qCWarning(lcKeychainChunk) << "Maximum chunk count for" << readJob->key() << "reached, ignoring after" << KeychainChunk::MaxChunks;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
if (readJob->error() != QKeychain::Error::EntryNotFound ||
|
||||||
|
((readJob->error() == QKeychain::Error::EntryNotFound) && _chunkCount == 0)) {
|
||||||
|
_error = readJob->error();
|
||||||
|
_errorString = readJob->errorString();
|
||||||
|
qCWarning(lcKeychainChunk) << "Unable to read" << readJob->key() << "chunk" << QString::number(_chunkCount) << readJob->errorString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readJob->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit finished(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace KeychainChunk
|
||||||
|
|
||||||
|
} // namespace OCC
|
||||||
120
src/gui/creds/keychainchunk.h
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
|
||||||
|
*
|
||||||
|
* 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 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#ifndef KEYCHAINCHUNK_H
|
||||||
|
#define KEYCHAINCHUNK_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <keychain.h>
|
||||||
|
#include "accountfwd.h"
|
||||||
|
|
||||||
|
// We don't support insecure fallback
|
||||||
|
// #define KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK
|
||||||
|
|
||||||
|
namespace OCC {
|
||||||
|
|
||||||
|
namespace KeychainChunk {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Workaround for Windows:
|
||||||
|
*
|
||||||
|
* Split the keychain entry's data into chunks of 2048 bytes,
|
||||||
|
* to allow 4k (4096 bit) keys / large certs to be saved (see limits in webflowcredentials.h)
|
||||||
|
*/
|
||||||
|
static constexpr int ChunkSize = 2048;
|
||||||
|
static constexpr int MaxChunks = 10;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief: Abstract base class for KeychainChunk jobs.
|
||||||
|
*/
|
||||||
|
class Job : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
Job(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
const QKeychain::Error error() const {
|
||||||
|
return _error;
|
||||||
|
}
|
||||||
|
const QString errorString() const {
|
||||||
|
return _errorString;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray binaryData() const {
|
||||||
|
return _chunkBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool insecureFallback() const {
|
||||||
|
return _insecureFallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we use it but don't support insecure fallback, give us nice compilation errors ;p
|
||||||
|
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
|
||||||
|
void setInsecureFallback(const bool &insecureFallback)
|
||||||
|
{
|
||||||
|
_insecureFallback = insecureFallback;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QString _serviceName;
|
||||||
|
Account *_account;
|
||||||
|
QString _key;
|
||||||
|
bool _insecureFallback = false;
|
||||||
|
bool _keychainMigration = false;
|
||||||
|
|
||||||
|
QKeychain::Error _error = QKeychain::NoError;
|
||||||
|
QString _errorString;
|
||||||
|
|
||||||
|
int _chunkCount = 0;
|
||||||
|
QByteArray _chunkBuffer;
|
||||||
|
}; // class Job
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief: Simple wrapper class for QKeychain::WritePasswordJob, splits too large keychain entry's data into chunks on Windows
|
||||||
|
*/
|
||||||
|
class WriteJob : public KeychainChunk::Job {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent = nullptr);
|
||||||
|
void start();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void finished(KeychainChunk::WriteJob *incomingJob);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void slotWriteJobDone(QKeychain::Job *incomingJob);
|
||||||
|
}; // class WriteJob
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief: Simple wrapper class for QKeychain::ReadPasswordJob, splits too large keychain entry's data into chunks on Windows
|
||||||
|
*/
|
||||||
|
class ReadJob : public KeychainChunk::Job {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ReadJob(Account *account, const QString &key, const bool &keychainMigration, QObject *parent = nullptr);
|
||||||
|
void start();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void finished(KeychainChunk::ReadJob *incomingJob);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void slotReadJobDone(QKeychain::Job *incomingJob);
|
||||||
|
}; // class ReadJob
|
||||||
|
|
||||||
|
} // namespace KeychainChunk
|
||||||
|
|
||||||
|
} // namespace OCC
|
||||||
|
|
||||||
|
#endif // KEYCHAINCHUNK_H
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#include "wizard/webview.h"
|
#include "wizard/webview.h"
|
||||||
#include "webflowcredentialsdialog.h"
|
#include "webflowcredentialsdialog.h"
|
||||||
|
#include "keychainchunk.h"
|
||||||
|
|
||||||
using namespace QKeychain;
|
using namespace QKeychain;
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ private:
|
|||||||
QPointer<const WebFlowCredentials> _cred;
|
QPointer<const WebFlowCredentials> _cred;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
|
||||||
static void addSettingsToJob(Account *account, QKeychain::Job *job)
|
static void addSettingsToJob(Account *account, QKeychain::Job *job)
|
||||||
{
|
{
|
||||||
Q_UNUSED(account)
|
Q_UNUSED(account)
|
||||||
@@ -82,6 +84,7 @@ static void addSettingsToJob(Account *account, QKeychain::Job *job)
|
|||||||
settings->setParent(job); // make the job parent to make setting deleted properly
|
settings->setParent(job); // make the job parent to make setting deleted properly
|
||||||
job->setSettings(settings.release());
|
job->setSettings(settings.release());
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
WebFlowCredentials::WebFlowCredentials()
|
WebFlowCredentials::WebFlowCredentials()
|
||||||
: _ready(false)
|
: _ready(false)
|
||||||
@@ -147,25 +150,32 @@ void WebFlowCredentials::fetchFromKeychain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentials::askFromUser() {
|
void WebFlowCredentials::askFromUser() {
|
||||||
// LoginFlowV2 > WebViewFlow > OAuth > Shib > Basic
|
// Determine if the old flow has to be used (GS for now)
|
||||||
bool useFlow2 = (_account->serverVersionInt() >= Account::makeServerVersion(16, 0, 0));
|
// Do a DetermineAuthTypeJob to make sure that the server is still using Flow2
|
||||||
|
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
|
||||||
|
connect(job, &DetermineAuthTypeJob::authType, [this](DetermineAuthTypeJob::AuthType type) {
|
||||||
|
// LoginFlowV2 > WebViewFlow > OAuth > Shib > Basic
|
||||||
|
bool useFlow2 = (type != DetermineAuthTypeJob::WebViewFlow);
|
||||||
|
|
||||||
_askDialog = new WebFlowCredentialsDialog(_account, useFlow2);
|
_askDialog = new WebFlowCredentialsDialog(_account, useFlow2);
|
||||||
|
|
||||||
if (!useFlow2) {
|
if (!useFlow2) {
|
||||||
QUrl url = _account->url();
|
QUrl url = _account->url();
|
||||||
QString path = url.path() + "/index.php/login/flow";
|
QString path = url.path() + "/index.php/login/flow";
|
||||||
url.setPath(path);
|
url.setPath(path);
|
||||||
_askDialog->setUrl(url);
|
_askDialog->setUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString msg = tr("You have been logged out of %1 as user %2. Please login again")
|
QString msg = tr("You have been logged out of %1 as user %2. Please login again")
|
||||||
.arg(_account->displayName(), _user);
|
.arg(_account->displayName(), _user);
|
||||||
_askDialog->setInfo(msg);
|
_askDialog->setInfo(msg);
|
||||||
|
|
||||||
_askDialog->show();
|
_askDialog->show();
|
||||||
|
|
||||||
connect(_askDialog, &WebFlowCredentialsDialog::urlCatched, this, &WebFlowCredentials::slotAskFromUserCredentialsProvided);
|
connect(_askDialog, &WebFlowCredentialsDialog::urlCatched, this, &WebFlowCredentials::slotAskFromUserCredentialsProvided);
|
||||||
|
connect(_askDialog, &WebFlowCredentialsDialog::onClose, this, &WebFlowCredentials::slotAskFromUserCancelled);
|
||||||
|
});
|
||||||
|
job->start();
|
||||||
|
|
||||||
qCDebug(lcWebFlowCredentials()) << "User needs to reauth!";
|
qCDebug(lcWebFlowCredentials()) << "User needs to reauth!";
|
||||||
}
|
}
|
||||||
@@ -199,10 +209,18 @@ void WebFlowCredentials::slotAskFromUserCredentialsProvided(const QString &user,
|
|||||||
emit asked();
|
emit asked();
|
||||||
|
|
||||||
_askDialog->close();
|
_askDialog->close();
|
||||||
delete _askDialog;
|
_askDialog->deleteLater();
|
||||||
_askDialog = nullptr;
|
_askDialog = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebFlowCredentials::slotAskFromUserCancelled() {
|
||||||
|
qCDebug(lcWebFlowCredentials()) << "User cancelled reauth!";
|
||||||
|
|
||||||
|
emit asked();
|
||||||
|
|
||||||
|
_askDialog->deleteLater();
|
||||||
|
_askDialog = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool WebFlowCredentials::stillValid(QNetworkReply *reply) {
|
bool WebFlowCredentials::stillValid(QNetworkReply *reply) {
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
@@ -223,33 +241,32 @@ void WebFlowCredentials::persist() {
|
|||||||
|
|
||||||
// write cert if there is one
|
// write cert if there is one
|
||||||
if (!_clientSslCertificate.isNull()) {
|
if (!_clientSslCertificate.isNull()) {
|
||||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
auto *job = new KeychainChunk::WriteJob(_account,
|
||||||
addSettingsToJob(_account, job);
|
_user + clientCertificatePEMC,
|
||||||
job->setInsecureFallback(false);
|
_clientSslCertificate.toPem());
|
||||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientCertPEMJobDone);
|
connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientCertPEMJobDone);
|
||||||
job->setKey(keychainKey(_account->url().toString(), _user + clientCertificatePEMC, _account->id()));
|
|
||||||
job->setBinaryData(_clientSslCertificate.toPem());
|
|
||||||
job->start();
|
job->start();
|
||||||
} else {
|
} else {
|
||||||
// no cert, just write credentials
|
// no cert, just write credentials
|
||||||
slotWriteClientCertPEMJobDone();
|
slotWriteClientCertPEMJobDone(nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentials::slotWriteClientCertPEMJobDone()
|
void WebFlowCredentials::slotWriteClientCertPEMJobDone(KeychainChunk::WriteJob *writeJob)
|
||||||
{
|
{
|
||||||
|
if(writeJob)
|
||||||
|
writeJob->deleteLater();
|
||||||
|
|
||||||
// write ssl key if there is one
|
// write ssl key if there is one
|
||||||
if (!_clientSslKey.isNull()) {
|
if (!_clientSslKey.isNull()) {
|
||||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
auto *job = new KeychainChunk::WriteJob(_account,
|
||||||
addSettingsToJob(_account, job);
|
_user + clientKeyPEMC,
|
||||||
job->setInsecureFallback(false);
|
_clientSslKey.toPem());
|
||||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientKeyPEMJobDone);
|
connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientKeyPEMJobDone);
|
||||||
job->setKey(keychainKey(_account->url().toString(), _user + clientKeyPEMC, _account->id()));
|
|
||||||
job->setBinaryData(_clientSslKey.toPem());
|
|
||||||
job->start();
|
job->start();
|
||||||
} else {
|
} else {
|
||||||
// no key, just write credentials
|
// no key, just write credentials
|
||||||
slotWriteClientKeyPEMJobDone();
|
slotWriteClientKeyPEMJobDone(nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +281,7 @@ void WebFlowCredentials::writeSingleClientCaCertPEM()
|
|||||||
|
|
||||||
// keep the limit
|
// keep the limit
|
||||||
if (index > (_clientSslCaCertificatesMaxCount - 1)) {
|
if (index > (_clientSslCaCertificatesMaxCount - 1)) {
|
||||||
qCWarning(lcWebFlowCredentials) << "Maximum client CA cert count exceeded while writing slot" << QString::number(index) << "), cutting off after " << QString::number(_clientSslCaCertificatesMaxCount) << "certs";
|
qCWarning(lcWebFlowCredentials) << "Maximum client CA cert count exceeded while writing slot" << QString::number(index) << "cutting off after" << QString::number(_clientSslCaCertificatesMaxCount) << "certs";
|
||||||
|
|
||||||
_clientSslCaCertificatesWriteQueue.clear();
|
_clientSslCaCertificatesWriteQueue.clear();
|
||||||
|
|
||||||
@@ -272,20 +289,21 @@ void WebFlowCredentials::writeSingleClientCaCertPEM()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
auto *job = new KeychainChunk::WriteJob(_account,
|
||||||
addSettingsToJob(_account, job);
|
_user + clientCaCertificatePEMC + QString::number(index),
|
||||||
job->setInsecureFallback(false);
|
cert.toPem());
|
||||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientCaCertsPEMJobDone);
|
connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientCaCertsPEMJobDone);
|
||||||
job->setKey(keychainKey(_account->url().toString(), _user + clientCaCertificatePEMC + QString::number(index), _account->id()));
|
|
||||||
job->setBinaryData(cert.toPem());
|
|
||||||
job->start();
|
job->start();
|
||||||
} else {
|
} else {
|
||||||
slotWriteClientCaCertsPEMJobDone(nullptr);
|
slotWriteClientCaCertsPEMJobDone(nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentials::slotWriteClientKeyPEMJobDone()
|
void WebFlowCredentials::slotWriteClientKeyPEMJobDone(KeychainChunk::WriteJob *writeJob)
|
||||||
{
|
{
|
||||||
|
if(writeJob)
|
||||||
|
writeJob->deleteLater();
|
||||||
|
|
||||||
_clientSslCaCertificatesWriteQueue.clear();
|
_clientSslCaCertificatesWriteQueue.clear();
|
||||||
|
|
||||||
// write ca certs if there are any
|
// write ca certs if there are any
|
||||||
@@ -300,16 +318,16 @@ void WebFlowCredentials::slotWriteClientKeyPEMJobDone()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomingJob)
|
void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(KeychainChunk::WriteJob *writeJob)
|
||||||
{
|
{
|
||||||
// errors / next ca cert?
|
// errors / next ca cert?
|
||||||
if (incomingJob && !_clientSslCaCertificates.isEmpty()) {
|
if (writeJob && !_clientSslCaCertificates.isEmpty()) {
|
||||||
WritePasswordJob *writeJob = static_cast<WritePasswordJob *>(incomingJob);
|
|
||||||
|
|
||||||
if (writeJob->error() != NoError) {
|
if (writeJob->error() != NoError) {
|
||||||
qCWarning(lcWebFlowCredentials) << "Error while writing client CA cert" << writeJob->errorString();
|
qCWarning(lcWebFlowCredentials) << "Error while writing client CA cert" << writeJob->errorString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeJob->deleteLater();
|
||||||
|
|
||||||
if (!_clientSslCaCertificatesWriteQueue.isEmpty()) {
|
if (!_clientSslCaCertificatesWriteQueue.isEmpty()) {
|
||||||
// next ca cert
|
// next ca cert
|
||||||
writeSingleClientCaCertPEM();
|
writeSingleClientCaCertPEM();
|
||||||
@@ -319,7 +337,9 @@ void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomi
|
|||||||
|
|
||||||
// done storing ca certs, time for the password
|
// done storing ca certs, time for the password
|
||||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||||
|
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
|
||||||
addSettingsToJob(_account, job);
|
addSettingsToJob(_account, job);
|
||||||
|
#endif
|
||||||
job->setInsecureFallback(false);
|
job->setInsecureFallback(false);
|
||||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteJobDone);
|
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteJobDone);
|
||||||
job->setKey(keychainKey(_account->url().toString(), _user, _account->id()));
|
job->setKey(keychainKey(_account->url().toString(), _user, _account->id()));
|
||||||
@@ -358,6 +378,8 @@ void WebFlowCredentials::forgetSensitiveData() {
|
|||||||
|
|
||||||
fetchUser();
|
fetchUser();
|
||||||
|
|
||||||
|
_account->deleteAppPassword();
|
||||||
|
|
||||||
const QString kck = keychainKey(_account->url().toString(), _user, _account->id());
|
const QString kck = keychainKey(_account->url().toString(), _user, _account->id());
|
||||||
if (kck.isEmpty()) {
|
if (kck.isEmpty()) {
|
||||||
qCDebug(lcWebFlowCredentials()) << "InvalidateToken: User is empty, bailing out!";
|
qCDebug(lcWebFlowCredentials()) << "InvalidateToken: User is empty, bailing out!";
|
||||||
@@ -367,18 +389,15 @@ void WebFlowCredentials::forgetSensitiveData() {
|
|||||||
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
|
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
|
||||||
job->setInsecureFallback(false);
|
job->setInsecureFallback(false);
|
||||||
job->setKey(kck);
|
job->setKey(kck);
|
||||||
|
connect(job, &Job::finished, this, [](QKeychain::Job *job) {
|
||||||
|
DeletePasswordJob *djob = qobject_cast<DeletePasswordJob *>(job);
|
||||||
|
djob->deleteLater();
|
||||||
|
});
|
||||||
job->start();
|
job->start();
|
||||||
|
|
||||||
invalidateToken();
|
invalidateToken();
|
||||||
|
|
||||||
/* IMPORTANT
|
deleteKeychainEntries();
|
||||||
/* TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
|
|
||||||
*
|
|
||||||
* Disabled as long as selecting another cert is not supported by the UI.
|
|
||||||
*
|
|
||||||
* Being able to specify a new certificate is important anyway: expiry etc.
|
|
||||||
*/
|
|
||||||
//deleteKeychainEntries();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentials::setAccount(Account *account) {
|
void WebFlowCredentials::setAccount(Account *account) {
|
||||||
@@ -416,34 +435,31 @@ void WebFlowCredentials::slotFinished(QNetworkReply *reply) {
|
|||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
_credentialsValid = true;
|
_credentialsValid = true;
|
||||||
|
|
||||||
|
/// Used later for remote wipe
|
||||||
|
_account->writeAppPasswordOnce(_password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentials::fetchFromKeychainHelper() {
|
void WebFlowCredentials::fetchFromKeychainHelper() {
|
||||||
// Read client cert from keychain
|
// Read client cert from keychain
|
||||||
const QString kck = keychainKey(
|
auto *job = new KeychainChunk::ReadJob(_account,
|
||||||
_account->url().toString(),
|
_user + clientCertificatePEMC,
|
||||||
_user + clientCertificatePEMC,
|
_keychainMigration);
|
||||||
_keychainMigration ? QString() : _account->id());
|
connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientCertPEMJobDone);
|
||||||
|
|
||||||
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
|
|
||||||
addSettingsToJob(_account, job);
|
|
||||||
job->setInsecureFallback(false);
|
|
||||||
job->setKey(kck);
|
|
||||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientCertPEMJobDone);
|
|
||||||
job->start();
|
job->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJob)
|
void WebFlowCredentials::slotReadClientCertPEMJobDone(KeychainChunk::ReadJob *readJob)
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
||||||
Q_ASSERT(!incomingJob->insecureFallback()); // If insecureFallback is set, the next test would be pointless
|
Q_ASSERT(!readJob->insecureFallback()); // If insecureFallback is set, the next test would be pointless
|
||||||
if (_retryOnKeyChainError && (incomingJob->error() == QKeychain::NoBackendAvailable
|
if (_retryOnKeyChainError && (readJob->error() == QKeychain::NoBackendAvailable
|
||||||
|| incomingJob->error() == QKeychain::OtherError)) {
|
|| readJob->error() == QKeychain::OtherError)) {
|
||||||
// Could be that the backend was not yet available. Wait some extra seconds.
|
// Could be that the backend was not yet available. Wait some extra seconds.
|
||||||
// (Issues #4274 and #6522)
|
// (Issues #4274 and #6522)
|
||||||
// (For kwallet, the error is OtherError instead of NoBackendAvailable, maybe a bug in QtKeychain)
|
// (For kwallet, the error is OtherError instead of NoBackendAvailable, maybe a bug in QtKeychain)
|
||||||
qCInfo(lcWebFlowCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << incomingJob->errorString();
|
qCInfo(lcWebFlowCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << readJob->errorString();
|
||||||
QTimer::singleShot(10000, this, &WebFlowCredentials::fetchFromKeychainHelper);
|
QTimer::singleShot(10000, this, &WebFlowCredentials::fetchFromKeychainHelper);
|
||||||
_retryOnKeyChainError = false;
|
_retryOnKeyChainError = false;
|
||||||
return;
|
return;
|
||||||
@@ -452,7 +468,6 @@ void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJo
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Store PEM in memory
|
// Store PEM in memory
|
||||||
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob);
|
|
||||||
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
|
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
|
||||||
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
|
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
|
||||||
if (sslCertificateList.length() >= 1) {
|
if (sslCertificateList.length() >= 1) {
|
||||||
@@ -460,25 +475,19 @@ void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load key too
|
readJob->deleteLater();
|
||||||
const QString kck = keychainKey(
|
|
||||||
_account->url().toString(),
|
|
||||||
_user + clientKeyPEMC,
|
|
||||||
_keychainMigration ? QString() : _account->id());
|
|
||||||
|
|
||||||
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
|
// Load key too
|
||||||
addSettingsToJob(_account, job);
|
auto *job = new KeychainChunk::ReadJob(_account,
|
||||||
job->setInsecureFallback(false);
|
_user + clientKeyPEMC,
|
||||||
job->setKey(kck);
|
_keychainMigration);
|
||||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone);
|
connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone);
|
||||||
job->start();
|
job->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentials::slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob)
|
void WebFlowCredentials::slotReadClientKeyPEMJobDone(KeychainChunk::ReadJob *readJob)
|
||||||
{
|
{
|
||||||
// Store key in memory
|
// Store key in memory
|
||||||
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob);
|
|
||||||
|
|
||||||
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
|
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
|
||||||
QByteArray clientKeyPEM = readJob->binaryData();
|
QByteArray clientKeyPEM = readJob->binaryData();
|
||||||
// FIXME Unfortunately Qt has a bug and we can't just use QSsl::Opaque to let it
|
// FIXME Unfortunately Qt has a bug and we can't just use QSsl::Opaque to let it
|
||||||
@@ -493,8 +502,13 @@ void WebFlowCredentials::slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob
|
|||||||
if (_clientSslKey.isNull()) {
|
if (_clientSslKey.isNull()) {
|
||||||
qCWarning(lcWebFlowCredentials) << "Could not load SSL key into Qt!";
|
qCWarning(lcWebFlowCredentials) << "Could not load SSL key into Qt!";
|
||||||
}
|
}
|
||||||
|
clientKeyPEM.clear();
|
||||||
|
} else {
|
||||||
|
qCWarning(lcWebFlowCredentials) << "Unable to read client key" << readJob->errorString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readJob->deleteLater();
|
||||||
|
|
||||||
// Start fetching client CA certs
|
// Start fetching client CA certs
|
||||||
_clientSslCaCertificates.clear();
|
_clientSslCaCertificates.clear();
|
||||||
|
|
||||||
@@ -505,28 +519,20 @@ void WebFlowCredentials::readSingleClientCaCertPEM()
|
|||||||
{
|
{
|
||||||
// try to fetch a client ca cert
|
// try to fetch a client ca cert
|
||||||
if (_clientSslCaCertificates.count() < _clientSslCaCertificatesMaxCount) {
|
if (_clientSslCaCertificates.count() < _clientSslCaCertificatesMaxCount) {
|
||||||
const QString kck = keychainKey(
|
auto *job = new KeychainChunk::ReadJob(_account,
|
||||||
_account->url().toString(),
|
_user + clientCaCertificatePEMC + QString::number(_clientSslCaCertificates.count()),
|
||||||
_user + clientCaCertificatePEMC + QString::number(_clientSslCaCertificates.count()),
|
_keychainMigration);
|
||||||
_keychainMigration ? QString() : _account->id());
|
connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientCaCertsPEMJobDone);
|
||||||
|
|
||||||
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
|
|
||||||
addSettingsToJob(_account, job);
|
|
||||||
job->setInsecureFallback(false);
|
|
||||||
job->setKey(kck);
|
|
||||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientCaCertsPEMJobDone);
|
|
||||||
job->start();
|
job->start();
|
||||||
} else {
|
} else {
|
||||||
qCWarning(lcWebFlowCredentials) << "Maximum client CA cert count exceeded while reading, ignoring after " << _clientSslCaCertificatesMaxCount;
|
qCWarning(lcWebFlowCredentials) << "Maximum client CA cert count exceeded while reading, ignoring after" << _clientSslCaCertificatesMaxCount;
|
||||||
|
|
||||||
slotReadClientCaCertsPEMJobDone(nullptr);
|
slotReadClientCaCertsPEMJobDone(nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomingJob) {
|
void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(KeychainChunk::ReadJob *readJob) {
|
||||||
// Store key in memory
|
// Store cert in memory
|
||||||
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob);
|
|
||||||
|
|
||||||
if (readJob) {
|
if (readJob) {
|
||||||
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
|
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
|
||||||
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
|
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
|
||||||
@@ -534,15 +540,19 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin
|
|||||||
_clientSslCaCertificates.append(sslCertificateList.at(0));
|
_clientSslCaCertificates.append(sslCertificateList.at(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readJob->deleteLater();
|
||||||
|
|
||||||
// try next cert
|
// try next cert
|
||||||
readSingleClientCaCertPEM();
|
readSingleClientCaCertPEM();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if (readJob->error() != QKeychain::Error::EntryNotFound ||
|
if (readJob->error() != QKeychain::Error::EntryNotFound ||
|
||||||
(readJob->error() == QKeychain::Error::EntryNotFound) && _clientSslCaCertificates.count() == 0) {
|
((readJob->error() == QKeychain::Error::EntryNotFound) && _clientSslCaCertificates.count() == 0)) {
|
||||||
qCWarning(lcWebFlowCredentials) << "Unable to read client CA cert slot " << QString::number(_clientSslCaCertificates.count()) << readJob->errorString();
|
qCWarning(lcWebFlowCredentials) << "Unable to read client CA cert slot" << QString::number(_clientSslCaCertificates.count()) << readJob->errorString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readJob->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now fetch the actual server password
|
// Now fetch the actual server password
|
||||||
@@ -552,7 +562,9 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin
|
|||||||
_keychainMigration ? QString() : _account->id());
|
_keychainMigration ? QString() : _account->id());
|
||||||
|
|
||||||
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
|
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
|
||||||
|
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
|
||||||
addSettingsToJob(_account, job);
|
addSettingsToJob(_account, job);
|
||||||
|
#endif
|
||||||
job->setInsecureFallback(false);
|
job->setInsecureFallback(false);
|
||||||
job->setKey(kck);
|
job->setKey(kck);
|
||||||
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadPasswordJobDone);
|
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadPasswordJobDone);
|
||||||
@@ -560,7 +572,7 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
|
void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
|
||||||
QKeychain::ReadPasswordJob *job = static_cast<ReadPasswordJob *>(incomingJob);
|
QKeychain::ReadPasswordJob *job = qobject_cast<ReadPasswordJob *>(incomingJob);
|
||||||
QKeychain::Error error = job->error();
|
QKeychain::Error error = job->error();
|
||||||
|
|
||||||
// If we could not find the entry try the old entries
|
// If we could not find the entry try the old entries
|
||||||
@@ -583,6 +595,8 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
|
|||||||
}
|
}
|
||||||
emit fetched();
|
emit fetched();
|
||||||
|
|
||||||
|
job->deleteLater();
|
||||||
|
|
||||||
// If keychain data was read from legacy location, wipe these entries and store new ones
|
// If keychain data was read from legacy location, wipe these entries and store new ones
|
||||||
if (_keychainMigration && _ready) {
|
if (_keychainMigration && _ready) {
|
||||||
_keychainMigration = false;
|
_keychainMigration = false;
|
||||||
@@ -593,23 +607,60 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) {
|
void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) {
|
||||||
auto startDeleteJob = [this, oldKeychainEntries](QString user) {
|
auto startDeleteJob = [this, oldKeychainEntries](QString key) {
|
||||||
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
|
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
|
||||||
|
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
|
||||||
addSettingsToJob(_account, job);
|
addSettingsToJob(_account, job);
|
||||||
job->setInsecureFallback(true);
|
#endif
|
||||||
|
job->setInsecureFallback(false);
|
||||||
job->setKey(keychainKey(_account->url().toString(),
|
job->setKey(keychainKey(_account->url().toString(),
|
||||||
user,
|
key,
|
||||||
oldKeychainEntries ? QString() : _account->id()));
|
oldKeychainEntries ? QString() : _account->id()));
|
||||||
|
|
||||||
|
connect(job, &Job::finished, this, [](QKeychain::Job *job) {
|
||||||
|
DeletePasswordJob *djob = qobject_cast<DeletePasswordJob *>(job);
|
||||||
|
djob->deleteLater();
|
||||||
|
});
|
||||||
job->start();
|
job->start();
|
||||||
};
|
};
|
||||||
|
|
||||||
startDeleteJob(_user);
|
startDeleteJob(_user);
|
||||||
startDeleteJob(_user + clientKeyPEMC);
|
|
||||||
startDeleteJob(_user + clientCertificatePEMC);
|
|
||||||
|
|
||||||
for (auto i = 0; i < _clientSslCaCertificates.count(); i++) {
|
/* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
|
||||||
startDeleteJob(_user + clientCaCertificatePEMC + QString::number(i));
|
* TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
|
||||||
|
*
|
||||||
|
* Disabled as long as selecting another cert is not supported by the UI.
|
||||||
|
*
|
||||||
|
* Being able to specify a new certificate is important anyway: expiry etc.
|
||||||
|
*
|
||||||
|
* We introduce this dirty hack here, to allow deleting them upon Remote Wipe.
|
||||||
|
*/
|
||||||
|
if(_account->isRemoteWipeRequested_HACK()) {
|
||||||
|
// <-- FIXME MS@2019-12-07
|
||||||
|
startDeleteJob(_user + clientKeyPEMC);
|
||||||
|
startDeleteJob(_user + clientCertificatePEMC);
|
||||||
|
|
||||||
|
for (auto i = 0; i < _clientSslCaCertificates.count(); i++) {
|
||||||
|
startDeleteJob(_user + clientCaCertificatePEMC + QString::number(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
// Also delete key / cert sub-chunks (Windows workaround)
|
||||||
|
// The first chunk (0) has no suffix, to stay compatible with older versions and non-Windows
|
||||||
|
for (auto chunk = 1; chunk < KeychainChunk::MaxChunks; chunk++) {
|
||||||
|
const QString strChunkSuffix = QString(".") + QString::number(chunk);
|
||||||
|
|
||||||
|
startDeleteJob(_user + clientKeyPEMC + strChunkSuffix);
|
||||||
|
startDeleteJob(_user + clientCertificatePEMC + strChunkSuffix);
|
||||||
|
|
||||||
|
for (auto i = 0; i < _clientSslCaCertificates.count(); i++) {
|
||||||
|
startDeleteJob(_user + clientCaCertificatePEMC + QString::number(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// FIXME MS@2019-12-07 -->
|
||||||
}
|
}
|
||||||
|
// <-- FIXME MS@2019-12-07
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace OCC
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ namespace QKeychain {
|
|||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
|
namespace KeychainChunk {
|
||||||
|
class ReadJob;
|
||||||
|
class WriteJob;
|
||||||
|
}
|
||||||
|
|
||||||
class WebFlowCredentialsDialog;
|
class WebFlowCredentialsDialog;
|
||||||
|
|
||||||
class WebFlowCredentials : public AbstractCredentials
|
class WebFlowCredentials : public AbstractCredentials
|
||||||
@@ -61,15 +66,16 @@ private slots:
|
|||||||
void slotFinished(QNetworkReply *reply);
|
void slotFinished(QNetworkReply *reply);
|
||||||
|
|
||||||
void slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host);
|
void slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host);
|
||||||
|
void slotAskFromUserCancelled();
|
||||||
|
|
||||||
void slotReadClientCertPEMJobDone(QKeychain::Job *incomingJob);
|
void slotReadClientCertPEMJobDone(KeychainChunk::ReadJob *readJob);
|
||||||
void slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob);
|
void slotReadClientKeyPEMJobDone(KeychainChunk::ReadJob *readJob);
|
||||||
void slotReadClientCaCertsPEMJobDone(QKeychain::Job *incommingJob);
|
void slotReadClientCaCertsPEMJobDone(KeychainChunk::ReadJob *readJob);
|
||||||
void slotReadPasswordJobDone(QKeychain::Job *incomingJob);
|
void slotReadPasswordJobDone(QKeychain::Job *incomingJob);
|
||||||
|
|
||||||
void slotWriteClientCertPEMJobDone();
|
void slotWriteClientCertPEMJobDone(KeychainChunk::WriteJob *writeJob);
|
||||||
void slotWriteClientKeyPEMJobDone();
|
void slotWriteClientKeyPEMJobDone(KeychainChunk::WriteJob *writeJob);
|
||||||
void slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomingJob);
|
void slotWriteClientCaCertsPEMJobDone(KeychainChunk::WriteJob *writeJob);
|
||||||
void slotWriteJobDone(QKeychain::Job *);
|
void slotWriteJobDone(QKeychain::Job *);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -83,7 +89,7 @@ private:
|
|||||||
void writeSingleClientCaCertPEM();
|
void writeSingleClientCaCertPEM();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Since we're limited by Windows limits we just create our own
|
* Since we're limited by Windows limits, we just create our own
|
||||||
* limit to avoid evil things happening by endless recursion
|
* limit to avoid evil things happening by endless recursion
|
||||||
*
|
*
|
||||||
* Better than storing the count and relying on maybe-hacked values
|
* Better than storing the count and relying on maybe-hacked values
|
||||||
@@ -121,6 +127,6 @@ protected:
|
|||||||
WebFlowCredentialsDialog *_askDialog;
|
WebFlowCredentialsDialog *_askDialog;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace OCC
|
||||||
|
|
||||||
#endif // WEBFLOWCREDENTIALS_H
|
#endif // WEBFLOWCREDENTIALS_H
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
|
#include "application.h"
|
||||||
|
#include "owncloudgui.h"
|
||||||
|
#include "headerbanner.h"
|
||||||
#include "wizard/owncloudwizardcommon.h"
|
#include "wizard/owncloudwizardcommon.h"
|
||||||
#include "wizard/webview.h"
|
#include "wizard/webview.h"
|
||||||
#include "wizard/flow2authwidget.h"
|
#include "wizard/flow2authwidget.h"
|
||||||
@@ -19,31 +22,59 @@ WebFlowCredentialsDialog::WebFlowCredentialsDialog(Account *account, bool useFlo
|
|||||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||||
|
|
||||||
_layout = new QVBoxLayout(this);
|
_layout = new QVBoxLayout(this);
|
||||||
|
int spacing = _layout->spacing();
|
||||||
|
int margin = _layout->margin();
|
||||||
|
_layout->setSpacing(0);
|
||||||
|
_layout->setMargin(0);
|
||||||
|
|
||||||
|
if(_useFlow2) {
|
||||||
|
_headerBanner = new HeaderBanner(this);
|
||||||
|
_layout->addWidget(_headerBanner);
|
||||||
|
Theme *theme = Theme::instance();
|
||||||
|
_headerBanner->setup(tr("Log in"), theme->wizardHeaderLogo(), theme->wizardHeaderBanner(),
|
||||||
|
Qt::AutoText, QString::fromLatin1("color:#fff;"));
|
||||||
|
}
|
||||||
|
|
||||||
|
_containerLayout = new QVBoxLayout(this);
|
||||||
|
_containerLayout->setSpacing(spacing);
|
||||||
|
_containerLayout->setMargin(margin);
|
||||||
|
|
||||||
//QString msg = tr("You have been logged out of %1 as user %2, please login again")
|
|
||||||
// .arg(_account->displayName(), _user);
|
|
||||||
_infoLabel = new QLabel();
|
_infoLabel = new QLabel();
|
||||||
_layout->addWidget(_infoLabel);
|
_containerLayout->addWidget(_infoLabel);
|
||||||
|
|
||||||
if (_useFlow2) {
|
if (_useFlow2) {
|
||||||
_flow2AuthWidget = new Flow2AuthWidget(account);
|
_flow2AuthWidget = new Flow2AuthWidget();
|
||||||
_layout->addWidget(_flow2AuthWidget);
|
_containerLayout->addWidget(_flow2AuthWidget);
|
||||||
|
|
||||||
connect(_flow2AuthWidget, &Flow2AuthWidget::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
|
connect(_flow2AuthWidget, &Flow2AuthWidget::authResult, this, &WebFlowCredentialsDialog::slotFlow2AuthResult);
|
||||||
|
|
||||||
|
// Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching)
|
||||||
|
connect(this, &WebFlowCredentialsDialog::styleChanged, _flow2AuthWidget, &Flow2AuthWidget::slotStyleChanged);
|
||||||
|
|
||||||
|
// allow Flow2 page to poll on window activation
|
||||||
|
connect(this, &WebFlowCredentialsDialog::onActivate, _flow2AuthWidget, &Flow2AuthWidget::slotPollNow);
|
||||||
|
|
||||||
|
_flow2AuthWidget->startAuth(account);
|
||||||
} else {
|
} else {
|
||||||
_webView = new WebView();
|
_webView = new WebView();
|
||||||
_layout->addWidget(_webView);
|
_containerLayout->addWidget(_webView);
|
||||||
|
|
||||||
connect(_webView, &WebView::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
|
connect(_webView, &WebView::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto app = static_cast<Application *>(qApp);
|
||||||
|
connect(app, &Application::isShowingSettingsDialog, this, &WebFlowCredentialsDialog::slotShowSettingsDialog);
|
||||||
|
|
||||||
_errorLabel = new QLabel();
|
_errorLabel = new QLabel();
|
||||||
_errorLabel->hide();
|
_errorLabel->hide();
|
||||||
_layout->addWidget(_errorLabel);
|
_containerLayout->addWidget(_errorLabel);
|
||||||
|
|
||||||
WizardCommon::initErrorLabel(_errorLabel);
|
WizardCommon::initErrorLabel(_errorLabel);
|
||||||
|
|
||||||
|
_layout->addLayout(_containerLayout);
|
||||||
setLayout(_layout);
|
setLayout(_layout);
|
||||||
|
|
||||||
|
customizeStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentialsDialog::closeEvent(QCloseEvent* e) {
|
void WebFlowCredentialsDialog::closeEvent(QCloseEvent* e) {
|
||||||
@@ -52,11 +83,17 @@ void WebFlowCredentialsDialog::closeEvent(QCloseEvent* e) {
|
|||||||
if (_webView) {
|
if (_webView) {
|
||||||
// Force calling WebView::~WebView() earlier so that _profile and _page are
|
// Force calling WebView::~WebView() earlier so that _profile and _page are
|
||||||
// deleted in the correct order.
|
// deleted in the correct order.
|
||||||
delete _webView;
|
_webView->deleteLater();
|
||||||
|
_webView = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_flow2AuthWidget)
|
if (_flow2AuthWidget) {
|
||||||
delete _flow2AuthWidget;
|
_flow2AuthWidget->resetAuth();
|
||||||
|
_flow2AuthWidget->deleteLater();
|
||||||
|
_flow2AuthWidget = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentialsDialog::setUrl(const QUrl &url) {
|
void WebFlowCredentialsDialog::setUrl(const QUrl &url) {
|
||||||
@@ -69,6 +106,9 @@ void WebFlowCredentialsDialog::setInfo(const QString &msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebFlowCredentialsDialog::setError(const QString &error) {
|
void WebFlowCredentialsDialog::setError(const QString &error) {
|
||||||
|
// bring window to top
|
||||||
|
slotShowSettingsDialog();
|
||||||
|
|
||||||
if (_useFlow2 && _flow2AuthWidget) {
|
if (_useFlow2 && _flow2AuthWidget) {
|
||||||
_flow2AuthWidget->setError(error);
|
_flow2AuthWidget->setError(error);
|
||||||
return;
|
return;
|
||||||
@@ -82,4 +122,49 @@ void WebFlowCredentialsDialog::setError(const QString &error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebFlowCredentialsDialog::changeEvent(QEvent *e)
|
||||||
|
{
|
||||||
|
switch (e->type()) {
|
||||||
|
case QEvent::StyleChange:
|
||||||
|
case QEvent::PaletteChange:
|
||||||
|
case QEvent::ThemeChange:
|
||||||
|
customizeStyle();
|
||||||
|
|
||||||
|
// Notify the other widgets (Dark-/Light-Mode switching)
|
||||||
|
emit styleChanged();
|
||||||
|
break;
|
||||||
|
case QEvent::ActivationChange:
|
||||||
|
if(isActiveWindow())
|
||||||
|
emit onActivate();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDialog::changeEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebFlowCredentialsDialog::customizeStyle()
|
||||||
|
{
|
||||||
|
// HINT: Customize dialog's own style here, if necessary in the future (Dark-/Light-Mode switching)
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebFlowCredentialsDialog::slotShowSettingsDialog()
|
||||||
|
{
|
||||||
|
// bring window to top but slightly delay, to avoid being hidden behind the SettingsDialog
|
||||||
|
QTimer::singleShot(100, this, [this] {
|
||||||
|
ownCloudGui::raiseDialog(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebFlowCredentialsDialog::slotFlow2AuthResult(Flow2Auth::Result r, const QString &errorString, const QString &user, const QString &appPassword)
|
||||||
|
{
|
||||||
|
if(r == Flow2Auth::LoggedIn) {
|
||||||
|
emit urlCatched(user, appPassword, QString());
|
||||||
|
} else {
|
||||||
|
// bring window to top
|
||||||
|
slotShowSettingsDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace OCC
|
||||||
|
|||||||