Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2b19bd628 | ||
|
|
767f868a36 | ||
|
|
ab6ecaa462 | ||
|
|
fb1df54941 | ||
|
|
89afc03218 | ||
|
|
e97e6b68c6 | ||
|
|
b0c0e4b26e | ||
|
|
90c5ef3311 | ||
|
|
35c29a4842 | ||
|
|
15326da736 | ||
|
|
24ce490524 | ||
|
|
85cf6b8332 | ||
|
|
f9cb0c2dbb | ||
|
|
33cbdd06a6 | ||
|
|
c7828d0671 | ||
|
|
dad76def2f | ||
|
|
318a49c526 | ||
|
|
c8c0839a57 | ||
|
|
5a6661eab1 | ||
|
|
065ff11983 | ||
|
|
6c9d1b0e84 | ||
|
|
4d1494a17a | ||
|
|
51eb5b1a42 | ||
|
|
485c9dd768 | ||
|
|
564c636391 | ||
|
|
5a8c69e557 | ||
|
|
e7a61fb06b | ||
|
|
46a3666198 | ||
|
|
4ef617b1f2 | ||
|
|
2f5e0dd6c8 | ||
|
|
8310cb3c05 | ||
|
|
100e7e04c3 | ||
|
|
b18bb02176 | ||
|
|
ddc900a93c | ||
|
|
3a2f9fe6f5 | ||
|
|
2ec42e5e1f | ||
|
|
5090006e2d | ||
|
|
156f3387eb | ||
|
|
67d535362f | ||
|
|
2d851d4cb9 | ||
|
|
4c45ad3e67 | ||
|
|
e96524f702 | ||
|
|
8fdb98f14d | ||
|
|
0b4f08fbef | ||
|
|
af9d71d16e | ||
|
|
66775873c7 | ||
|
|
9d4945c733 | ||
|
|
0c0a501aad | ||
|
|
62daeaa5ea | ||
|
|
6b63e9020c | ||
|
|
69e607eddb | ||
|
|
85a093dd4a | ||
|
|
aff2061bd1 | ||
|
|
b85f545fa3 | ||
|
|
248205cddd | ||
|
|
ce63951ae6 | ||
|
|
990cbc58b6 | ||
|
|
748681d765 | ||
|
|
96768adaae | ||
|
|
e9f23e2dd9 | ||
|
|
1206071dd6 | ||
|
|
a89a325d6c | ||
|
|
815bc7927d | ||
|
|
796b66478b | ||
|
|
eeb87dd144 | ||
|
|
b0fa43fb14 | ||
|
|
41a85f7987 | ||
|
|
fb007211f0 | ||
|
|
00c582e157 | ||
|
|
c43ac9eb84 | ||
|
|
467536ac97 | ||
|
|
9175fc9827 | ||
|
|
92f6a07d85 | ||
|
|
fc53eaf9e5 | ||
|
|
1109ce8da1 | ||
|
|
24b0cafb7f | ||
|
|
1c0d7166c9 | ||
|
|
b1129e898e | ||
|
|
2b5159e86a | ||
|
|
616159b144 | ||
|
|
553ed3a64b | ||
|
|
85ec3e2313 | ||
|
|
a898c6fc9c | ||
|
|
973d55c12b | ||
|
|
e148577aed | ||
|
|
1e1f71d700 | ||
|
|
12a7dfe05a | ||
|
|
fa02cfd830 | ||
|
|
f442b21969 | ||
|
|
e9ea4e1d3e | ||
|
|
7958b43da6 | ||
|
|
bc2049ccbc | ||
|
|
7a45acb497 | ||
|
|
556f03ed7f | ||
|
|
f0a3edd798 | ||
|
|
5b553f1a8d | ||
|
|
a5e2bea04c | ||
|
|
c485baaadf | ||
|
|
2ab43e2b69 | ||
|
|
f8c79c33bc | ||
|
|
453aa7b769 | ||
|
|
dbae377351 | ||
|
|
da6c99c949 | ||
|
|
71c0580bae | ||
|
|
f005c82bf5 | ||
|
|
24395ec7bf | ||
|
|
d8ed9f9307 | ||
|
|
d04e55bc9c | ||
|
|
698507d363 | ||
|
|
f5bb73a3e7 | ||
|
|
a0633c39ec | ||
|
|
3d2005833b | ||
|
|
8c84ab9b31 | ||
|
|
b1ad0be3b4 | ||
|
|
0d72b3b671 | ||
|
|
b141dcdb47 | ||
|
|
1343dad443 | ||
|
|
db7a95514d | ||
|
|
da9bbba79b | ||
|
|
c348ee2a49 | ||
|
|
9cd73af53c | ||
|
|
5532c2c482 | ||
|
|
6e48b2dcff | ||
|
|
743a7b8d2f | ||
|
|
ca82644ece | ||
|
|
5e1ac84d37 | ||
|
|
b8cb74d2ff | ||
|
|
3d4ac4d130 | ||
|
|
34c6ce261e | ||
|
|
7ed44c27b9 | ||
|
|
86ac7223db | ||
|
|
7feb6b0d2c | ||
|
|
a232ed76ce | ||
|
|
2c832d2518 | ||
|
|
a6d66c15a0 | ||
|
|
527236b17b | ||
|
|
a1a08ae73b | ||
|
|
298f3e0ac2 | ||
|
|
9612635602 | ||
|
|
58968f1e29 | ||
|
|
b54e141a13 | ||
|
|
41709e2ee5 | ||
|
|
81f2b5fd15 | ||
|
|
1e53e7f33b | ||
|
|
183edb517c | ||
|
|
9e074d7a5a | ||
|
|
0664c310fa | ||
|
|
39a8c59f83 | ||
|
|
ed0a751d15 | ||
|
|
6ed21a94d3 | ||
|
|
01abaf6f81 | ||
|
|
6aefb07782 | ||
|
|
4ed3ddeac1 | ||
|
|
f1e8f5d193 | ||
|
|
2489e19bab | ||
|
|
2958e32636 | ||
|
|
21bde9743d | ||
|
|
8f144f20a6 | ||
|
|
61454b7405 | ||
|
|
95cb364316 | ||
|
|
a6524c1ee7 | ||
|
|
ebf984a595 | ||
|
|
4e7b96b942 | ||
|
|
55a885f326 | ||
|
|
ac79c0c15c | ||
|
|
ab74809973 | ||
|
|
fabaf676b8 | ||
|
|
2ec5960b2b | ||
|
|
92ed77ab5a | ||
|
|
447272a1f1 | ||
|
|
0e00cf4ebb | ||
|
|
8a414929b0 | ||
|
|
ba39057a23 | ||
|
|
e34d298b8e | ||
|
|
5709bc99e3 | ||
|
|
5da565f68c | ||
|
|
0281f006e6 | ||
|
|
7f9afe9ad3 | ||
|
|
0d0381cc42 | ||
|
|
de0cb12354 | ||
|
|
d60a07f58c | ||
|
|
321f843e8e | ||
|
|
245188760a | ||
|
|
00902c5740 | ||
|
|
077cdd6278 | ||
|
|
4a22eabebd | ||
|
|
0d514560a4 | ||
|
|
fed0eee243 | ||
|
|
52816f0f44 | ||
|
|
3b3a0199bd | ||
|
|
533806893f | ||
|
|
bfb9ed1d83 | ||
|
|
bb47f0aa21 | ||
|
|
a2e6fccac6 | ||
|
|
4773eb1d06 | ||
|
|
d144336aa2 | ||
|
|
4a0f5cd9a6 | ||
|
|
23d0cae7b1 | ||
|
|
3a4ab634cf | ||
|
|
6572c99042 | ||
|
|
133d6db83c | ||
|
|
db47f14923 | ||
|
|
6f5b0b04c2 | ||
|
|
1616766b4b | ||
|
|
122df895d1 | ||
|
|
d819f7773e | ||
|
|
b7eb185463 | ||
|
|
a790c7f5f8 | ||
|
|
03e400a4b2 | ||
|
|
81cf289a85 | ||
|
|
4bff7f503d | ||
|
|
2dad7f5c62 | ||
|
|
3fcbeb2020 | ||
|
|
2a40bcce85 | ||
|
|
745f3d56fb | ||
|
|
ed004a2ae0 | ||
|
|
b722e7301d | ||
|
|
93cd6897d3 | ||
|
|
18db36a70a | ||
|
|
4ea2953045 | ||
|
|
0ebf93e383 | ||
|
|
f4f01b23f9 | ||
|
|
d7aaa95e57 | ||
|
|
f651813f0b | ||
|
|
16daf32edb | ||
|
|
f9b306abc7 | ||
|
|
dc910d51c5 | ||
|
|
c64921a249 | ||
|
|
07b16554cd | ||
|
|
520c9717ca | ||
|
|
767f3aac96 | ||
|
|
977308edcc | ||
|
|
4a1a95a7b9 | ||
|
|
b82c7da159 | ||
|
|
2ee3e26dd6 | ||
|
|
aea13e5c14 | ||
|
|
5c6b920fa5 | ||
|
|
244bde3017 | ||
|
|
3563206ef1 | ||
|
|
896559df39 | ||
|
|
9c7041dcc1 | ||
|
|
b2e0b86584 | ||
|
|
299362793c | ||
|
|
7013c79d09 | ||
|
|
f21a259ddc | ||
|
|
73845d8842 | ||
|
|
4da3fcbd4a | ||
|
|
8729d069dc | ||
|
|
16a5c2efe9 | ||
|
|
15616b0297 | ||
|
|
120306a1df | ||
|
|
7246126f84 | ||
|
|
61819f04a2 | ||
|
|
8a4ed528b8 | ||
|
|
f6c4d0a0fd | ||
|
|
456fbe1f24 | ||
|
|
06222e33ff | ||
|
|
c3e6af0032 | ||
|
|
79419b3f71 | ||
|
|
07f658a96f | ||
|
|
72a64afbec | ||
|
|
b3a4272188 | ||
|
|
407c9d316c | ||
|
|
7e51eca1a6 | ||
|
|
21a3ae0768 | ||
|
|
dca0103899 | ||
|
|
4efe189eec | ||
|
|
52f462ff2c | ||
|
|
3778465e3b | ||
|
|
c00ea331dc | ||
|
|
9f0828a56a | ||
|
|
374bb40894 | ||
|
|
f225c395ad | ||
|
|
0c6aff5af7 | ||
|
|
53ed41bb55 | ||
|
|
178d4e166c | ||
|
|
481ec10ecd | ||
|
|
f8d342c535 | ||
|
|
d275333bc7 | ||
|
|
62c7033588 | ||
|
|
3bdf79c133 | ||
|
|
aa4817e1e6 | ||
|
|
6d6fd1e849 | ||
|
|
efc65a7843 | ||
|
|
6ea6d20a60 | ||
|
|
89dd5dada2 | ||
|
|
3dc92b9a67 | ||
|
|
415b537c36 | ||
|
|
a03150703d | ||
|
|
1f3c9326fe | ||
|
|
0fc2aadb7b | ||
|
|
41e827a2ee | ||
|
|
625bbf5c95 | ||
|
|
52df5d8ae1 | ||
|
|
7c4abdbb8c | ||
|
|
3a07939b01 | ||
|
|
2072bbb998 | ||
|
|
4d8d1f1457 | ||
|
|
b27ca28bb3 | ||
|
|
24e400ad70 | ||
|
|
ab3a8e2bcd | ||
|
|
df867395ae | ||
|
|
b333d16b6f | ||
|
|
7cd737a08a | ||
|
|
96c4ea3a45 | ||
|
|
92f08eaa79 | ||
|
|
7137732a2a | ||
|
|
1bf928daa5 | ||
|
|
0537dec817 | ||
|
|
65e1577bfd | ||
|
|
96b43de5f0 | ||
|
|
d4fdc63a40 | ||
|
|
307b741dc7 | ||
|
|
82828cba6f | ||
|
|
520f2cf470 | ||
|
|
afd134c7a1 | ||
|
|
1f1295e92b | ||
|
|
e1c4c472dd | ||
|
|
866ea0977f | ||
|
|
7feadbfb80 | ||
|
|
53b3e6e706 | ||
|
|
86b66121f8 | ||
|
|
ec7c33d820 | ||
|
|
8b5a7de4ef | ||
|
|
1ffb1b60df | ||
|
|
831ac5150a | ||
|
|
3f17e594ec | ||
|
|
6a3606f92c | ||
|
|
a79b578e5f | ||
|
|
766fbd2fb6 | ||
|
|
da73ebf9ab | ||
|
|
4d6e15e03b | ||
|
|
77d8c739f2 | ||
|
|
5e6199d106 | ||
|
|
bcf04d3fae | ||
|
|
f4b904b47b | ||
|
|
b56603ca83 | ||
|
|
a124dbc7ea | ||
|
|
dea506aa42 | ||
|
|
1210b9d21a | ||
|
|
93822ae556 | ||
|
|
ddb60e498d | ||
|
|
df772ed629 | ||
|
|
d0117b5d6e | ||
|
|
dc72150c1a | ||
|
|
76a3c2af9b | ||
|
|
3e391079f3 | ||
|
|
c219bb58be | ||
|
|
d668449e63 | ||
|
|
15e6c52049 | ||
|
|
3c04d1e914 | ||
|
|
2b3ba949d3 | ||
|
|
8feb5cd180 | ||
|
|
bb100994ec | ||
|
|
d3adb45fae | ||
|
|
cb9ae34498 | ||
|
|
b2b45e0f2f | ||
|
|
cd92818e56 | ||
|
|
3caa36d3a5 | ||
|
|
fa52eb413d | ||
|
|
b7c4bf8f1e | ||
|
|
ad639f66be | ||
|
|
e02b2e6964 | ||
|
|
ac22c3014c | ||
|
|
94248dfd59 | ||
|
|
51736bc535 | ||
|
|
b2f8f83518 | ||
|
|
05d76811b6 | ||
|
|
a0aa02bb8a | ||
|
|
73b27c7dac | ||
|
|
8a38d54192 | ||
|
|
913c6925bf | ||
|
|
89fa4d71a3 | ||
|
|
7e946d4313 | ||
|
|
09387888de | ||
|
|
04512cee0d | ||
|
|
2c369a1fe4 | ||
|
|
92fb54a3e4 | ||
|
|
6eb84043f0 | ||
|
|
33e1065e4d | ||
|
|
5f03a5e76b | ||
|
|
8abf60d5d7 | ||
|
|
aa6c2e7ce4 | ||
|
|
d6737cc547 | ||
|
|
aa5566c010 | ||
|
|
c95059b4c6 | ||
|
|
370683ff70 | ||
|
|
68e122cf72 | ||
|
|
3a43f603fb | ||
|
|
1a588d0a72 | ||
|
|
f796cc6c6d | ||
|
|
ba1cc69c01 | ||
|
|
e93812cdcc | ||
|
|
862d5fe693 | ||
|
|
95bcbcd9a5 | ||
|
|
5fb7bcca22 | ||
|
|
956d644bab | ||
|
|
e05dc905e5 | ||
|
|
c262be5f58 | ||
|
|
de47b5aaf5 | ||
|
|
7409171af3 | ||
|
|
f858d39534 | ||
|
|
703bf4fda6 | ||
|
|
0938f812c2 | ||
|
|
3ed9d6180a | ||
|
|
f81196eb98 | ||
|
|
d30d79a110 | ||
|
|
d91d0941f6 | ||
|
|
d07ff2ff8e | ||
|
|
38bd4eb36e | ||
|
|
1ad2a45c86 | ||
|
|
261645a780 | ||
|
|
522fd57e4e | ||
|
|
1ae94266d2 | ||
|
|
2f899fe95c | ||
|
|
2185f18d70 | ||
|
|
d1fe82b086 | ||
|
|
b97df8d5f6 | ||
|
|
5db8fd8f0b | ||
|
|
7541375e43 | ||
|
|
47297d8988 | ||
|
|
086b99ede2 | ||
|
|
bc08acd36c | ||
|
|
8097445e6e | ||
|
|
56e6cba575 | ||
|
|
a0e2f62d7e | ||
|
|
8a259941a7 | ||
|
|
45885a59b3 | ||
|
|
9a043e39a0 | ||
|
|
984dc91f14 | ||
|
|
64c596de8a | ||
|
|
c51e3e2e05 | ||
|
|
f6e2cc3890 | ||
|
|
887354642c | ||
|
|
94696cc44e | ||
|
|
4a9565add1 | ||
|
|
bac139c93f | ||
|
|
820d5af672 | ||
|
|
7288d0b28f | ||
|
|
10d721b970 | ||
|
|
748994be2e | ||
|
|
2ccc150b38 | ||
|
|
6cb9fc5092 | ||
|
|
854d832c81 | ||
|
|
cf15052b76 | ||
|
|
f6d5ea6495 | ||
|
|
b155031cb1 | ||
|
|
8b67c188ba | ||
|
|
b1a1ff1a72 | ||
|
|
0e7450fd52 | ||
|
|
bf3b80825a | ||
|
|
f95123fc43 | ||
|
|
a2765a5233 | ||
|
|
80c8da8cac | ||
|
|
eb1049b160 | ||
|
|
81bdd3fefb | ||
|
|
cc89d5919e | ||
|
|
2d2f24b3e3 | ||
|
|
5fb6ee5a7b | ||
|
|
34848254be | ||
|
|
e4d8b4578d | ||
|
|
d256bfbc4c | ||
|
|
b37a7480a3 | ||
|
|
9aea231fd8 | ||
|
|
0d516874e2 | ||
|
|
75d94db5bb | ||
|
|
5a6af6dd2c | ||
|
|
4e76451bf8 | ||
|
|
88c4bc28f6 | ||
|
|
13bd358bce | ||
|
|
bb665828bc | ||
|
|
4f27e8e48d | ||
|
|
e12edef524 | ||
|
|
d15a02571f | ||
|
|
4a0ec1f793 | ||
|
|
e0db25e2b3 | ||
|
|
bf76836a21 | ||
|
|
b0f684769a | ||
|
|
15e950ed44 | ||
|
|
58646d8b0c | ||
|
|
7c017f6249 | ||
|
|
9828b1ea04 | ||
|
|
5855742497 | ||
|
|
61b9c03aad | ||
|
|
d7cae8e32e | ||
|
|
7a57d7b0c4 | ||
|
|
11d50d087a | ||
|
|
ccfcc2f399 | ||
|
|
7794373b3c | ||
|
|
96f7c6073d | ||
|
|
42b0a2a38b | ||
|
|
f95ee94f62 | ||
|
|
b2da6ff300 | ||
|
|
50b51b1246 | ||
|
|
3cbecca696 | ||
|
|
c27e8a9773 | ||
|
|
907de1ecd2 | ||
|
|
aa9d84a86b | ||
|
|
957ed31ded | ||
|
|
0317a4fbe2 | ||
|
|
332d8bb373 | ||
|
|
3c03ee80da | ||
|
|
101ee0e300 | ||
|
|
dcc7ff372d | ||
|
|
5b7cf0b902 | ||
|
|
3bf29d8352 | ||
|
|
6ba6356777 | ||
|
|
1119d89ba7 | ||
|
|
612a78cee0 | ||
|
|
0f4dd039cd | ||
|
|
c719bcfd0c | ||
|
|
f297ac8a4f | ||
|
|
a625d7ab41 | ||
|
|
561c504f7b | ||
|
|
7a63b533fe | ||
|
|
35d492e63b | ||
|
|
502695bb37 | ||
|
|
ae4e6f9701 | ||
|
|
cd29d45b27 | ||
|
|
f4a52215d5 | ||
|
|
5736e7e35a | ||
|
|
0728a4ead1 | ||
|
|
75c739ce4f | ||
|
|
19c035f4f3 | ||
|
|
195ac70726 | ||
|
|
5b08cba028 | ||
|
|
9d35ef2e57 | ||
|
|
20c038ede8 | ||
|
|
2447926f03 | ||
|
|
a20f0771f1 | ||
|
|
302a65bedb | ||
|
|
1aa867ecfc | ||
|
|
5c5c763d24 | ||
|
|
6ad23dd128 | ||
|
|
6534b8c60c | ||
|
|
9956bedd78 | ||
|
|
1103a5b284 | ||
|
|
f13d7e634a | ||
|
|
4b649f5996 | ||
|
|
36071b3a79 | ||
|
|
4db7d11f1f | ||
|
|
2edd059846 | ||
|
|
da4a43d5a0 | ||
|
|
3c611239b9 | ||
|
|
d12455bc31 | ||
|
|
3ec34e84bc | ||
|
|
257a4d8e13 | ||
|
|
41fa304f70 | ||
|
|
c3230d5430 | ||
|
|
c74c41deaa | ||
|
|
770b649936 | ||
|
|
7eb1539bdf | ||
|
|
596a315c5f | ||
|
|
aac5b3e83c | ||
|
|
7f19df6f6b | ||
|
|
8073ad00c3 | ||
|
|
67b49cd33e | ||
|
|
15667b7ad8 | ||
|
|
df57a0230f | ||
|
|
ce1273a32c | ||
|
|
bb398cdb2a | ||
|
|
54813ccdec | ||
|
|
9c1f0acf8b | ||
|
|
4294c7d2f9 | ||
|
|
43d63c3e01 | ||
|
|
9ba3c1b34a | ||
|
|
0f257cbc0c | ||
|
|
fd690d5758 | ||
|
|
63007032e4 | ||
|
|
5f62fc7e88 | ||
|
|
021342bfe9 | ||
|
|
270a6bdcee | ||
|
|
aa029ce71b | ||
|
|
8afb4b5c1f | ||
|
|
bf146e6676 | ||
|
|
5794ea1650 | ||
|
|
e18f71111a | ||
|
|
17e37fb8fb | ||
|
|
523aedaf3e | ||
|
|
21e5cb36c5 | ||
|
|
27e0cbf1ea | ||
|
|
a32a3674b1 | ||
|
|
7046601ef8 | ||
|
|
98323b0063 | ||
|
|
a66a356947 | ||
|
|
53e945b956 | ||
|
|
934f413807 | ||
|
|
f3daed1144 | ||
|
|
73774d99c2 | ||
|
|
69e5ea08e0 | ||
|
|
c81724c36f | ||
|
|
86359af3be | ||
|
|
56f35c1a73 | ||
|
|
6861aa2c51 | ||
|
|
7a08bd8fb3 | ||
|
|
861e126ab4 | ||
|
|
c13a6e1d5f | ||
|
|
40760d5c19 | ||
|
|
b7e1e1502f | ||
|
|
81f24d4cb0 | ||
|
|
bc857efc6c | ||
|
|
fb6476939e | ||
|
|
b59e2c2593 | ||
|
|
1af3e764ec | ||
|
|
85321db4d7 | ||
|
|
a510fa21b7 | ||
|
|
791ecc56b0 | ||
|
|
3eade57cba | ||
|
|
fcbc6af479 | ||
|
|
6f13df03a6 | ||
|
|
7183127189 | ||
|
|
2fa94cfe7b | ||
|
|
fd7c0010d8 | ||
|
|
ff12e4ad1e | ||
|
|
5e8bd53fb2 | ||
|
|
fa733293cb | ||
|
|
f2f2be36e9 | ||
|
|
d8461fb856 | ||
|
|
f8509bc85a | ||
|
|
c31ebe6794 | ||
|
|
ed3d7a7720 | ||
|
|
30a8194638 | ||
|
|
8a4b58969b | ||
|
|
8210ea2c76 | ||
|
|
41e2b26116 | ||
|
|
185f4aeb59 | ||
|
|
0ed7b8162a | ||
|
|
f4809905b9 | ||
|
|
084dfd0ede | ||
|
|
96edcc1dc9 | ||
|
|
36b940e9ae | ||
|
|
9d9867a632 | ||
|
|
86ba8e33d0 | ||
|
|
6bf8864f5a | ||
|
|
a67aebf526 | ||
|
|
8c60e77b6f | ||
|
|
00b1b8e4a5 | ||
|
|
c5b766345c | ||
|
|
4eca88b0bc | ||
|
|
2b3ae0fb94 | ||
|
|
82eb76c1ea | ||
|
|
2ba8108292 | ||
|
|
8735700514 | ||
|
|
e6f4ff2f20 | ||
|
|
78f67d221c | ||
|
|
5098daf152 | ||
|
|
75706111ee | ||
|
|
0d0bd82dc7 | ||
|
|
af5077d1c2 | ||
|
|
c85ae1a335 | ||
|
|
3dec56141f | ||
|
|
c5a958df12 | ||
|
|
bd7e38d1af | ||
|
|
896b2d982a | ||
|
|
17d24547aa | ||
|
|
8b630dc7be | ||
|
|
7f67f46b30 | ||
|
|
220b2608b1 | ||
|
|
d02169c585 | ||
|
|
a1e4e4f714 | ||
|
|
ef20f0c44e | ||
|
|
e9bee38b36 | ||
|
|
df804c198d | ||
|
|
694973a4e3 | ||
|
|
b24f79645d | ||
|
|
8483ca1c87 | ||
|
|
91427be6f7 | ||
|
|
907cbdea09 | ||
|
|
54277d77c2 | ||
|
|
0f6122c09b | ||
|
|
f5a2a7a959 | ||
|
|
d23f36d364 | ||
|
|
f988ba9206 | ||
|
|
4e6b7cfdda | ||
|
|
5715450aff | ||
|
|
bba4efaa50 | ||
|
|
87997b6b4b | ||
|
|
cb2f64f0d5 | ||
|
|
035a2fc1e6 | ||
|
|
a6507db992 | ||
|
|
a5fe46e50e | ||
|
|
676976e7d3 | ||
|
|
696cc37055 | ||
|
|
d09a802487 | ||
|
|
8805ba78ef | ||
|
|
bd3aff19e7 | ||
|
|
5d3e79e506 | ||
|
|
a894f42e05 | ||
|
|
7167b71a90 | ||
|
|
4881c052aa | ||
|
|
b0b4e5b721 | ||
|
|
8d7eaf665c | ||
|
|
cb8a288773 | ||
|
|
41a3f0502e | ||
|
|
348acd6d84 | ||
|
|
6faa5d6dc6 | ||
|
|
be1c6e89b9 | ||
|
|
e1987a36bb | ||
|
|
54d2e6fc27 | ||
|
|
3046a44d12 | ||
|
|
2946c050b9 | ||
|
|
5f6e51c15a | ||
|
|
481e2c52af | ||
|
|
1e73844ac0 | ||
|
|
457f90f17f | ||
|
|
1fab89e154 | ||
|
|
c53ede6913 | ||
|
|
177ac62057 | ||
|
|
9b85cf2373 | ||
|
|
b977ac0e14 | ||
|
|
7ddd6825ba | ||
|
|
ca6964702f | ||
|
|
f2790cb574 | ||
|
|
5ecef4d6f5 | ||
|
|
ddae2be2a1 | ||
|
|
70f3929f44 | ||
|
|
dad0926ede | ||
|
|
77a289d2d0 | ||
|
|
9fbe1aefd2 | ||
|
|
2dcacdfbb6 | ||
|
|
add04d3e88 | ||
|
|
921e4410b8 | ||
|
|
349cf58ccf | ||
|
|
34f9c55458 | ||
|
|
9642c11eea | ||
|
|
453967fad8 | ||
|
|
500725ffee | ||
|
|
3f90011c10 | ||
|
|
e1784aa2fb | ||
|
|
6518a0f971 | ||
|
|
69211dfe66 | ||
|
|
e289fd4f89 | ||
|
|
cec48d1c44 | ||
|
|
a21e27cce5 | ||
|
|
3de5265fbc | ||
|
|
8c5ac1337a | ||
|
|
27ff00eb48 | ||
|
|
e0e0ed08ae | ||
|
|
f920200be9 | ||
|
|
3eeb3ec887 | ||
|
|
40d5d832fb | ||
|
|
f213ec7efe | ||
|
|
1af3f1b290 | ||
|
|
df4f1d709d | ||
|
|
bb156e1bd0 | ||
|
|
524cf07d56 | ||
|
|
282ef17df3 | ||
|
|
071d0800b7 | ||
|
|
267c96785f | ||
|
|
9d4f0395b9 | ||
|
|
aa0d6ea155 | ||
|
|
9692ebe887 | ||
|
|
a3f2fb5eae | ||
|
|
42e0c50540 | ||
|
|
2c9f54464a | ||
|
|
547582e3ed | ||
|
|
2c3ebf9ce1 | ||
|
|
daa9bb1df0 | ||
|
|
5689863046 | ||
|
|
76bf8d14bc | ||
|
|
4cbede57b1 | ||
|
|
ad8e2c5ac3 | ||
|
|
273a98d985 | ||
|
|
7ee5ccd513 | ||
|
|
b07b50a042 | ||
|
|
3752fde5e1 | ||
|
|
8b45d698c2 | ||
|
|
8a5b489c55 | ||
|
|
bc9f0c19b7 | ||
|
|
998e00d245 | ||
|
|
ba7beb4d5a | ||
|
|
ff2baaeb79 | ||
|
|
e0b01e2672 | ||
|
|
00251a7439 | ||
|
|
003a8688d8 | ||
|
|
933b1e3a4b | ||
|
|
541e9ee48a | ||
|
|
993efa29b2 | ||
|
|
13a0879c1d | ||
|
|
1b5b461c9b | ||
|
|
2e20380ecd | ||
|
|
18c3a14eb8 | ||
|
|
25260793c6 | ||
|
|
8ba457bd12 | ||
|
|
2aaac963ce | ||
|
|
80fb78ad36 | ||
|
|
0da185508b | ||
|
|
9c41f8d51f | ||
|
|
84356efa86 | ||
|
|
9e95f21cbd | ||
|
|
79cce17c2e | ||
|
|
c9faaf4e01 | ||
|
|
9cbf3a81bc | ||
|
|
3780d31a21 | ||
|
|
38a1a22f0c | ||
|
|
897b4900c2 | ||
|
|
93674aeabc | ||
|
|
8c97888410 | ||
|
|
379a750f4e | ||
|
|
c234046f91 | ||
|
|
a775006e2b | ||
|
|
77aa5a65d9 | ||
|
|
7bb3d5e144 | ||
|
|
978a320b43 | ||
|
|
5200b54857 | ||
|
|
ea95c1a3c2 | ||
|
|
ed54533a20 | ||
|
|
7f0960937e | ||
|
|
0d2de36ac0 | ||
|
|
0ef1df6dfb | ||
|
|
b59edc4b6b | ||
|
|
2347591c69 | ||
|
|
b8bdbacbce | ||
|
|
8cfe3df997 | ||
|
|
f7b330985d | ||
|
|
830fe45487 | ||
|
|
a377b56fe4 | ||
|
|
60d8ad9879 | ||
|
|
e01d1438b5 | ||
|
|
9b752d0e96 | ||
|
|
9b196a6521 | ||
|
|
efd7cafd59 | ||
|
|
a01d14f541 | ||
|
|
69b21cd810 | ||
|
|
d41045aa63 | ||
|
|
41709d7dd3 | ||
|
|
4270bd25b5 | ||
|
|
f3a2944d60 | ||
|
|
212553d63d | ||
|
|
4754c9704b | ||
|
|
ca1d0a43b1 | ||
|
|
0e95dbfecf | ||
|
|
ce2c2eaecc | ||
|
|
592a802d5d | ||
|
|
b17c33256a | ||
|
|
6d38f381ce | ||
|
|
404f128310 | ||
|
|
bd0f419fab | ||
|
|
b2fd9d2211 | ||
|
|
3f41c60f2b | ||
|
|
5a606b8854 | ||
|
|
5e87a66472 | ||
|
|
d41f736234 | ||
|
|
53f0c04f95 | ||
|
|
fa123f4ab0 | ||
|
|
c6acf63977 | ||
|
|
e743e34849 | ||
|
|
d5244cf6e3 | ||
|
|
fe6b71059d | ||
|
|
7b60bc49ac | ||
|
|
54bff9012e | ||
|
|
1a9f48de92 | ||
|
|
d08510b8d1 | ||
|
|
4f3c8334be | ||
|
|
d157c561b7 | ||
|
|
2d00719a05 | ||
|
|
b1c6c7e44e | ||
|
|
e384d549a2 | ||
|
|
bf4e4de38c | ||
|
|
89715bdca7 | ||
|
|
2ea1ddddb8 | ||
|
|
77e4f9b6f9 | ||
|
|
cd0f601afa | ||
|
|
246e0aa674 | ||
|
|
dec7cc6ee3 | ||
|
|
d175be5728 | ||
|
|
78537ce028 | ||
|
|
e4686d93e5 | ||
|
|
b294d13254 | ||
|
|
b0f2f875aa | ||
|
|
266fd9436c | ||
|
|
d3e62b418a | ||
|
|
257b9cba8e | ||
|
|
b026d228df | ||
|
|
9882ebc267 | ||
|
|
d52d539e9d | ||
|
|
c099e04434 | ||
|
|
cf63d29b42 | ||
|
|
07b17491cb | ||
|
|
28f4f03dff | ||
|
|
1ab430d088 | ||
|
|
0efa617075 | ||
|
|
0dcb95fbcd | ||
|
|
a17b102756 | ||
|
|
fb64d4d840 | ||
|
|
0d6bbd9258 | ||
|
|
86118686ad | ||
|
|
48a64edce7 | ||
|
|
8868bd92fe | ||
|
|
a140834288 | ||
|
|
550d1826a9 | ||
|
|
70184e1554 | ||
|
|
38209fc1fb | ||
|
|
1ea9f03f57 | ||
|
|
a957bd3864 | ||
|
|
6986fb1633 | ||
|
|
19e1b6f22b | ||
|
|
45a0e43fe4 | ||
|
|
02ae77645d | ||
|
|
f50c876ec8 | ||
|
|
e709febfb2 | ||
|
|
e4ad5b6485 | ||
|
|
660f8d839b | ||
|
|
3eedbf4188 | ||
|
|
588bab92a0 | ||
|
|
110ec01b0c | ||
|
|
307f517ee5 | ||
|
|
dfa19d2aaf | ||
|
|
3d195f4b6b | ||
|
|
f41a7f8c6a | ||
|
|
fe2c8bdade | ||
|
|
8a058b8b23 | ||
|
|
44bfe5cdbe | ||
|
|
b30038011f | ||
|
|
84d1f0df06 | ||
|
|
1a892b05cf | ||
|
|
1c81926f35 | ||
|
|
203719b999 | ||
|
|
1d1045ec03 | ||
|
|
3f4c50bce4 | ||
|
|
26704a72e9 | ||
|
|
ebfdbdd508 | ||
|
|
2588697197 | ||
|
|
634ab66aa4 | ||
|
|
85cdb1c189 | ||
|
|
0e9f5423d3 | ||
|
|
d5c2ad63fc | ||
|
|
b8b52462e1 | ||
|
|
95a269c8d2 | ||
|
|
a379a56cd1 | ||
|
|
35528f6024 | ||
|
|
57c09e263d | ||
|
|
b8016c1d44 | ||
|
|
2ec62c6d77 | ||
|
|
394fef40ca | ||
|
|
e42e0843e7 | ||
|
|
5ecff24268 | ||
|
|
c56af9f87c | ||
|
|
15af9eec36 | ||
|
|
cff04397d1 | ||
|
|
55d07ca757 | ||
|
|
65114e2ea9 | ||
|
|
9d106fee5b | ||
|
|
4d935a0a3b | ||
|
|
46cee8c9ed | ||
|
|
24c59e40ee | ||
|
|
0474bd37a1 | ||
|
|
5b821c0d2a | ||
|
|
e7469c848c | ||
|
|
9247866bb6 | ||
|
|
9b15cbaaca | ||
|
|
5395188ccf | ||
|
|
d0dfb9d8be | ||
|
|
d2f2cdb40c | ||
|
|
1ee6a4c7cb | ||
|
|
bc1a0cf678 | ||
|
|
49a9edfe6c | ||
|
|
883e51c84d | ||
|
|
d5a801cff5 | ||
|
|
20df88bbde | ||
|
|
90b884534c | ||
|
|
acff22a19e | ||
|
|
59977218e2 | ||
|
|
2ac69e480e | ||
|
|
8265774754 | ||
|
|
a23d8933b7 | ||
|
|
231c571787 | ||
|
|
a06924829e | ||
|
|
7323df8ca9 | ||
|
|
d2c9fd4500 | ||
|
|
de775e95b0 | ||
|
|
3d95401a98 | ||
|
|
c5fbe7e56b | ||
|
|
acc2699b0e | ||
|
|
dbfb8cbabc | ||
|
|
df6f3659fd | ||
|
|
8c9a501bc8 | ||
|
|
029fead117 | ||
|
|
6490f09662 | ||
|
|
d11af440a4 | ||
|
|
97829406fe | ||
|
|
af653405d0 | ||
|
|
67dba37ea3 | ||
|
|
02d2b948a3 | ||
|
|
0a118c4891 | ||
|
|
3424f15a02 | ||
|
|
de1e6af58f | ||
|
|
b411c3501e | ||
|
|
b07a137fb9 | ||
|
|
8372abb4e1 | ||
|
|
43797b1b84 | ||
|
|
e6534e134b | ||
|
|
8570c4e318 | ||
|
|
ed06aadd21 | ||
|
|
45024837ff | ||
|
|
3df678bc80 | ||
|
|
2a1e3241dd | ||
|
|
8f704e6de0 | ||
|
|
2f66bd3e9c | ||
|
|
eea64a3014 | ||
|
|
c2bd392c84 | ||
|
|
475904a266 | ||
|
|
f15c806415 | ||
|
|
2940519b5f | ||
|
|
0e62047498 | ||
|
|
2bd5bf742d | ||
|
|
1ed6a6f7da | ||
|
|
092e9096b8 | ||
|
|
6f53ceb768 | ||
|
|
f64db4b867 | ||
|
|
cfa735604d | ||
|
|
11cd75fe17 | ||
|
|
64a78a96cb | ||
|
|
70b2068f3f | ||
|
|
43dbdd8d68 | ||
|
|
fefbdbd764 | ||
|
|
1a1883d79a | ||
|
|
ec05045246 | ||
|
|
9278cd14d7 | ||
|
|
697fed7f84 | ||
|
|
4ac1f633d4 | ||
|
|
53a9bb5fc5 | ||
|
|
210718c79b | ||
|
|
0fceefbc10 | ||
|
|
24b7495571 |
No files matched your search
@@ -10,5 +10,5 @@ updates:
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
ignore:
|
||||
- dependency-name: "github.com/anytypeio/go-chash"
|
||||
- dependency-name: "github.com/anyproto/go-chash"
|
||||
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GOPRIVATE: github.com/anytypeio
|
||||
GOPRIVATE: github.com/anyproto
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Any Association
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,5 +1,5 @@
|
||||
.PHONY: proto test deps
|
||||
export GOPRIVATE=github.com/anytypeio
|
||||
export GOPRIVATE=github.com/anyproto
|
||||
export PATH:=deps:$(PATH)
|
||||
|
||||
proto:
|
||||
@@ -8,8 +8,8 @@ proto:
|
||||
@$(eval P_ACL_RECORDS_PATH_PB := commonspace/object/acl/aclrecordproto)
|
||||
@$(eval P_TREE_CHANGES_PATH_PB := commonspace/object/tree/treechangeproto)
|
||||
@$(eval P_CRYPTO_PATH_PB := util/crypto/cryptoproto)
|
||||
@$(eval P_ACL_RECORDS := M$(P_ACL_RECORDS_PATH_PB)/protos/aclrecord.proto=github.com/anytypeio/any-sync/$(P_ACL_RECORDS_PATH_PB))
|
||||
@$(eval P_TREE_CHANGES := M$(P_TREE_CHANGES_PATH_PB)/protos/treechange.proto=github.com/anytypeio/any-sync/$(P_TREE_CHANGES_PATH_PB))
|
||||
@$(eval P_ACL_RECORDS := M$(P_ACL_RECORDS_PATH_PB)/protos/aclrecord.proto=github.com/anyproto/any-sync/$(P_ACL_RECORDS_PATH_PB))
|
||||
@$(eval P_TREE_CHANGES := M$(P_TREE_CHANGES_PATH_PB)/protos/treechange.proto=github.com/anyproto/any-sync/$(P_TREE_CHANGES_PATH_PB))
|
||||
|
||||
protoc --gogofaster_out=:. $(P_ACL_RECORDS_PATH_PB)/protos/*.proto
|
||||
protoc --gogofaster_out=:. $(P_TREE_CHANGES_PATH_PB)/protos/*.proto
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//go:generate mockgen -destination mock_accountservice/mock_accountservice.go github.com/anytypeio/any-sync/accountservice Service
|
||||
//go:generate mockgen -destination mock_accountservice/mock_accountservice.go github.com/anyproto/any-sync/accountservice Service
|
||||
package accountservice
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/app"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
)
|
||||
|
||||
const CName = "common.accountservice"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package mock_accountservice
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/accountservice"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/accountservice"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/accountservice (interfaces: Service)
|
||||
// Source: github.com/anyproto/any-sync/accountservice (interfaces: Service)
|
||||
|
||||
// Package mock_accountservice is a generated GoMock package.
|
||||
package mock_accountservice
|
||||
@@ -7,8 +7,8 @@ package mock_accountservice
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
app "github.com/anytypeio/any-sync/app"
|
||||
accountdata "github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
accountdata "github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
|
||||
+64
-18
@@ -4,10 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"go.uber.org/zap"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -15,8 +16,8 @@ import (
|
||||
|
||||
var (
|
||||
// values of this vars will be defined while compilation
|
||||
GitCommit, GitBranch, GitState, GitSummary, BuildDate string
|
||||
name string
|
||||
AppName, GitCommit, GitBranch, GitState, GitSummary, BuildDate string
|
||||
name string
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -54,11 +55,13 @@ type ComponentStatable interface {
|
||||
// App is the central part of the application
|
||||
// It contains and manages all components
|
||||
type App struct {
|
||||
components []Component
|
||||
mu sync.RWMutex
|
||||
startStat Stat
|
||||
stopStat Stat
|
||||
deviceState int
|
||||
parent *App
|
||||
components []Component
|
||||
mu sync.RWMutex
|
||||
startStat Stat
|
||||
stopStat Stat
|
||||
deviceState int
|
||||
anySyncVersion string
|
||||
}
|
||||
|
||||
// Name returns app name
|
||||
@@ -66,6 +69,10 @@ func (app *App) Name() string {
|
||||
return name
|
||||
}
|
||||
|
||||
func (app *App) AppName() string {
|
||||
return AppName
|
||||
}
|
||||
|
||||
// Version return app version
|
||||
func (app *App) Version() string {
|
||||
return GitSummary
|
||||
@@ -103,6 +110,16 @@ func VersionDescription() string {
|
||||
return fmt.Sprintf("build on %s from %s at #%s(%s)", BuildDate, GitBranch, GitCommit, GitState)
|
||||
}
|
||||
|
||||
// ChildApp creates a child container which has access to parent's components
|
||||
// It doesn't call Start on any of the parent's components
|
||||
func (app *App) ChildApp() *App {
|
||||
return &App{
|
||||
parent: app,
|
||||
deviceState: app.deviceState,
|
||||
anySyncVersion: app.AnySyncVersion(),
|
||||
}
|
||||
}
|
||||
|
||||
// Register adds service to registry
|
||||
// All components will be started in the order they were registered
|
||||
func (app *App) Register(s Component) *App {
|
||||
@@ -122,10 +139,14 @@ func (app *App) Register(s Component) *App {
|
||||
func (app *App) Component(name string) Component {
|
||||
app.mu.RLock()
|
||||
defer app.mu.RUnlock()
|
||||
for _, s := range app.components {
|
||||
if s.Name() == name {
|
||||
return s
|
||||
current := app
|
||||
for current != nil {
|
||||
for _, s := range current.components {
|
||||
if s.Name() == name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -143,10 +164,14 @@ func (app *App) MustComponent(name string) Component {
|
||||
func MustComponent[i any](app *App) i {
|
||||
app.mu.RLock()
|
||||
defer app.mu.RUnlock()
|
||||
for _, s := range app.components {
|
||||
if v, ok := s.(i); ok {
|
||||
return v
|
||||
current := app
|
||||
for current != nil {
|
||||
for _, s := range current.components {
|
||||
if v, ok := s.(i); ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
empty := new(i)
|
||||
panic(fmt.Errorf("component with interface %T is not found", empty))
|
||||
@@ -156,9 +181,13 @@ func MustComponent[i any](app *App) i {
|
||||
func (app *App) ComponentNames() (names []string) {
|
||||
app.mu.RLock()
|
||||
defer app.mu.RUnlock()
|
||||
names = make([]string, len(app.components))
|
||||
for i, c := range app.components {
|
||||
names[i] = c.Name()
|
||||
names = make([]string, 0, len(app.components))
|
||||
current := app
|
||||
for current != nil {
|
||||
for _, c := range current.components {
|
||||
names = append(names, c.Name())
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -257,7 +286,7 @@ func (app *App) Close(ctx context.Context) error {
|
||||
case <-time.After(StopWarningAfter):
|
||||
statLogger(app.stopStat, log).
|
||||
With(zap.String("in_progress", currentComponentStopping)).
|
||||
Warn("components close in progress")
|
||||
Warn("components close in progress")
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
@@ -311,3 +340,20 @@ func (app *App) SetDeviceState(state int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var onceVersion sync.Once
|
||||
|
||||
func (app *App) AnySyncVersion() string {
|
||||
onceVersion.Do(func() {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if ok {
|
||||
for _, mod := range info.Deps {
|
||||
if mod.Path == "github.com/anyproto/any-sync" {
|
||||
app.anySyncVersion = mod.Version
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return app.anySyncVersion
|
||||
}
|
||||
@@ -34,6 +34,25 @@ func TestAppServiceRegistry(t *testing.T) {
|
||||
names := app.ComponentNames()
|
||||
assert.Equal(t, names, []string{"c1", "r1", "s1"})
|
||||
})
|
||||
t.Run("Child MustComponent", func(t *testing.T) {
|
||||
app := app.ChildApp()
|
||||
app.Register(newTestService(testTypeComponent, "x1", nil, nil))
|
||||
for _, name := range []string{"c1", "r1", "s1", "x1"} {
|
||||
assert.NotPanics(t, func() { app.MustComponent(name) }, name)
|
||||
}
|
||||
assert.Panics(t, func() { app.MustComponent("not-registered") })
|
||||
})
|
||||
t.Run("Child ComponentNames", func(t *testing.T) {
|
||||
app := app.ChildApp()
|
||||
app.Register(newTestService(testTypeComponent, "x1", nil, nil))
|
||||
names := app.ComponentNames()
|
||||
assert.Equal(t, names, []string{"x1", "c1", "r1", "s1"})
|
||||
})
|
||||
t.Run("Child override", func(t *testing.T) {
|
||||
app := app.ChildApp()
|
||||
app.Register(newTestService(testTypeRunnable, "s1", nil, nil))
|
||||
_ = app.MustComponent("s1").(*testRunnable)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAppStart(t *testing.T) {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
// Package ldiff provides a container of elements with fixed id and changeable content.
|
||||
// Diff can calculate the difference with another diff container (you can make it remote) with minimum hops and traffic.
|
||||
//
|
||||
//go:generate mockgen -destination mock_ldiff/mock_ldiff.go github.com/anytypeio/any-sync/app/ldiff Diff,Remote
|
||||
//go:generate mockgen -destination mock_ldiff/mock_ldiff.go github.com/anyproto/any-sync/app/ldiff Diff,Remote
|
||||
package ldiff
|
||||
|
||||
import (
|
||||
|
||||
@@ -3,9 +3,9 @@ package ldiff
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
"math"
|
||||
"sort"
|
||||
"testing"
|
||||
@@ -44,7 +44,7 @@ func TestDiff_Diff(t *testing.T) {
|
||||
d2 := New(16, 16)
|
||||
for i := 0; i < 1000; i++ {
|
||||
id := fmt.Sprint(i)
|
||||
head := bson.NewObjectId().Hex()
|
||||
head := uuid.NewString()
|
||||
d1.Set(Element{
|
||||
Id: id,
|
||||
Head: head,
|
||||
@@ -92,7 +92,7 @@ func TestDiff_Diff(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
d2.Set(Element{
|
||||
Id: fmt.Sprint(i),
|
||||
Head: bson.NewObjectId().Hex(),
|
||||
Head: uuid.NewString(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ func TestDiff_Diff(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
d2.Set(Element{
|
||||
Id: fmt.Sprint(i),
|
||||
Head: bson.NewObjectId().Hex(),
|
||||
Head: uuid.NewString(),
|
||||
})
|
||||
}
|
||||
var cancel func()
|
||||
@@ -123,7 +123,7 @@ func BenchmarkDiff_Ranges(b *testing.B) {
|
||||
d := New(16, 16)
|
||||
for i := 0; i < 10000; i++ {
|
||||
id := fmt.Sprint(i)
|
||||
head := bson.NewObjectId().Hex()
|
||||
head := uuid.NewString()
|
||||
d.Set(Element{
|
||||
Id: id,
|
||||
Head: head,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/app/ldiff (interfaces: Diff,Remote)
|
||||
// Source: github.com/anyproto/any-sync/app/ldiff (interfaces: Diff,Remote)
|
||||
|
||||
// Package mock_ldiff is a generated GoMock package.
|
||||
package mock_ldiff
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
ldiff "github.com/anytypeio/any-sync/app/ldiff"
|
||||
ldiff "github.com/anyproto/any-sync/app/ldiff"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
)
|
||||
|
||||
type LogFormat int
|
||||
|
||||
@@ -6,6 +6,9 @@ import (
|
||||
)
|
||||
|
||||
func WithPrometheus(reg *prometheus.Registry, namespace, subsystem string) Option {
|
||||
if reg == nil {
|
||||
return nil
|
||||
}
|
||||
if subsystem == "" {
|
||||
subsystem = "cache"
|
||||
}
|
||||
@@ -13,9 +16,7 @@ func WithPrometheus(reg *prometheus.Registry, namespace, subsystem string) Optio
|
||||
subSplit := strings.Split(subsystem, ".")
|
||||
namespace = strings.Join(nameSplit, "_")
|
||||
subsystem = strings.Join(subSplit, "_")
|
||||
if reg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return func(cache *oCache) {
|
||||
cache.metrics = &metrics{
|
||||
hit: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
|
||||
@@ -3,7 +3,7 @@ package ocache
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -2,8 +2,8 @@ package fileblockstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonfile/fileproto/fileprotoerr"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonfile/fileproto/fileprotoerr"
|
||||
blocks "github.com/ipfs/go-block-format"
|
||||
"github.com/ipfs/go-cid"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by protoc-gen-go-drpc. DO NOT EDIT.
|
||||
// protoc-gen-go-drpc version: v0.0.32
|
||||
// protoc-gen-go-drpc version: v0.0.33
|
||||
// source: commonfile/fileproto/protos/file.proto
|
||||
|
||||
package fileproto
|
||||
|
||||
@@ -2,8 +2,8 @@ package fileprotoerr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonfile/fileproto"
|
||||
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||
"github.com/anyproto/any-sync/commonfile/fileproto"
|
||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -2,7 +2,7 @@ package fileservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonfile/fileblockstore"
|
||||
"github.com/anyproto/any-sync/commonfile/fileblockstore"
|
||||
blocks "github.com/ipfs/go-block-format"
|
||||
"github.com/ipfs/go-blockservice"
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
@@ -3,9 +3,9 @@ package fileservice
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/app"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonfile/fileblockstore"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonfile/fileblockstore"
|
||||
"github.com/ipfs/go-cid"
|
||||
chunker "github.com/ipfs/go-ipfs-chunker"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
package commonspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/syncobjectgetter"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/treemanager"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type commonGetter struct {
|
||||
treemanager.TreeManager
|
||||
spaceId string
|
||||
reservedObjects []syncobjectgetter.SyncObject
|
||||
spaceIsClosed *atomic.Bool
|
||||
}
|
||||
|
||||
func newCommonGetter(spaceId string, getter treemanager.TreeManager, spaceIsClosed *atomic.Bool) *commonGetter {
|
||||
return &commonGetter{
|
||||
TreeManager: getter,
|
||||
spaceId: spaceId,
|
||||
spaceIsClosed: spaceIsClosed,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *commonGetter) AddObject(object syncobjectgetter.SyncObject) {
|
||||
c.reservedObjects = append(c.reservedObjects, object)
|
||||
}
|
||||
|
||||
func (c *commonGetter) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error) {
|
||||
if c.spaceIsClosed.Load() {
|
||||
return nil, ErrSpaceClosed
|
||||
}
|
||||
if obj := c.getReservedObject(treeId); obj != nil {
|
||||
return obj.(objecttree.ObjectTree), nil
|
||||
}
|
||||
return c.TreeManager.GetTree(ctx, spaceId, treeId)
|
||||
}
|
||||
|
||||
func (c *commonGetter) getReservedObject(id string) syncobjectgetter.SyncObject {
|
||||
for _, obj := range c.reservedObjects {
|
||||
if obj != nil && obj.Id() == id {
|
||||
return obj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *commonGetter) GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error) {
|
||||
if c.spaceIsClosed.Load() {
|
||||
return nil, ErrSpaceClosed
|
||||
}
|
||||
if obj := c.getReservedObject(objectId); obj != nil {
|
||||
return obj, nil
|
||||
}
|
||||
t, err := c.TreeManager.GetTree(ctx, c.spaceId, objectId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
obj = t.(syncobjectgetter.SyncObject)
|
||||
return
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package commonspace
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
)
|
||||
|
||||
type commonStorage struct {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package commonspace
|
||||
package config
|
||||
|
||||
type ConfigGetter interface {
|
||||
GetSpace() Config
|
||||
@@ -1,9 +1,10 @@
|
||||
//go:generate mockgen -destination mock_credentialprovider/mock_credentialprovider.go github.com/anytypeio/any-sync/commonspace/credentialprovider CredentialProvider
|
||||
//go:generate mockgen -destination mock_credentialprovider/mock_credentialprovider.go github.com/anyproto/any-sync/commonspace/credentialprovider CredentialProvider
|
||||
package credentialprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
)
|
||||
|
||||
const CName = "common.commonspace.credentialprovider"
|
||||
@@ -13,12 +14,21 @@ func NewNoOp() CredentialProvider {
|
||||
}
|
||||
|
||||
type CredentialProvider interface {
|
||||
app.Component
|
||||
GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error)
|
||||
}
|
||||
|
||||
type noOpProvider struct {
|
||||
}
|
||||
|
||||
func (n noOpProvider) Init(a *app.App) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n noOpProvider) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (n noOpProvider) GetCredential(ctx context.Context, spaceHeader *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/credentialprovider (interfaces: CredentialProvider)
|
||||
// Source: github.com/anyproto/any-sync/commonspace/credentialprovider (interfaces: CredentialProvider)
|
||||
|
||||
// Package mock_credentialprovider is a generated GoMock package.
|
||||
package mock_credentialprovider
|
||||
@@ -8,7 +8,8 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
spacesyncproto "github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
@@ -49,3 +50,31 @@ func (mr *MockCredentialProviderMockRecorder) GetCredential(arg0, arg1 interface
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCredential", reflect.TypeOf((*MockCredentialProvider)(nil).GetCredential), arg0, arg1)
|
||||
}
|
||||
|
||||
// Init mocks base method.
|
||||
func (m *MockCredentialProvider) Init(arg0 *app.App) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Init", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init.
|
||||
func (mr *MockCredentialProviderMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockCredentialProvider)(nil).Init), arg0)
|
||||
}
|
||||
|
||||
// Name mocks base method.
|
||||
func (m *MockCredentialProvider) Name() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Name")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Name indicates an expected call of Name.
|
||||
func (mr *MockCredentialProviderMockRecorder) Name() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockCredentialProvider)(nil).Name))
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package commonspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/settings"
|
||||
"github.com/anyproto/any-sync/commonspace/settings/settingsstate"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func addIncorrectSnapshot(settingsObject settings.SettingsObject, acc *accountdata.AccountKeys, partialIds map[string]struct{}, newId string) (err error) {
|
||||
factory := settingsstate.NewChangeFactory()
|
||||
bytes, err := factory.CreateObjectDeleteChange(newId, &settingsstate.State{DeletedIds: partialIds}, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ch, err := settingsObject.PrepareChange(objecttree.SignableChangeContent{
|
||||
Data: bytes,
|
||||
Key: acc.SignKey,
|
||||
IsSnapshot: true,
|
||||
IsEncrypted: false,
|
||||
Timestamp: time.Now().Unix(),
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res, err := settingsObject.AddRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: []string{ch.Id},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{ch},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.Mode != objecttree.Rebuild {
|
||||
return fmt.Errorf("incorrect mode: %d", res.Mode)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestSpaceDeleteIds(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
acc := fx.account.Account()
|
||||
rk := crypto.NewAES()
|
||||
ctx := context.Background()
|
||||
totalObjs := 1500
|
||||
|
||||
// creating space
|
||||
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
|
||||
SigningKey: acc.SignKey,
|
||||
SpaceType: "type",
|
||||
ReadKey: rk.Bytes(),
|
||||
ReplicationKey: 10,
|
||||
MasterKey: acc.PeerKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sp)
|
||||
|
||||
// initializing space
|
||||
spc, err := fx.spaceService.NewSpace(ctx, sp)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
// adding space to tree manager
|
||||
fx.treeManager.space = spc
|
||||
err = spc.Init(ctx)
|
||||
require.NoError(t, err)
|
||||
close(fx.treeManager.waitLoad)
|
||||
|
||||
var ids []string
|
||||
for i := 0; i < totalObjs; i++ {
|
||||
// creating a tree
|
||||
bytes := make([]byte, 32)
|
||||
rand.Read(bytes)
|
||||
doc, err := spc.TreeBuilder().CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
|
||||
PrivKey: acc.SignKey,
|
||||
ChangeType: "some",
|
||||
SpaceId: spc.Id(),
|
||||
IsEncrypted: false,
|
||||
Seed: bytes,
|
||||
Timestamp: time.Now().Unix(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
tr, err := spc.TreeBuilder().PutTree(ctx, doc, nil)
|
||||
require.NoError(t, err)
|
||||
ids = append(ids, tr.Id())
|
||||
tr.Close()
|
||||
}
|
||||
// deleting trees
|
||||
for _, id := range ids {
|
||||
err = spc.DeleteTree(ctx, id)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
spc.Close()
|
||||
require.Equal(t, len(ids), len(fx.treeManager.deletedIds))
|
||||
}
|
||||
|
||||
func createTree(t *testing.T, ctx context.Context, spc Space, acc *accountdata.AccountKeys) string {
|
||||
bytes := make([]byte, 32)
|
||||
rand.Read(bytes)
|
||||
doc, err := spc.TreeBuilder().CreateTree(ctx, objecttree.ObjectTreeCreatePayload{
|
||||
PrivKey: acc.SignKey,
|
||||
ChangeType: "some",
|
||||
SpaceId: spc.Id(),
|
||||
IsEncrypted: false,
|
||||
Seed: bytes,
|
||||
Timestamp: time.Now().Unix(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
tr, err := spc.TreeBuilder().PutTree(ctx, doc, nil)
|
||||
require.NoError(t, err)
|
||||
tr.Close()
|
||||
return tr.Id()
|
||||
}
|
||||
|
||||
func TestSpaceDeleteIdsIncorrectSnapshot(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
acc := fx.account.Account()
|
||||
rk := crypto.NewAES()
|
||||
ctx := context.Background()
|
||||
totalObjs := 1500
|
||||
partialObjs := 300
|
||||
|
||||
// creating space
|
||||
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
|
||||
SigningKey: acc.SignKey,
|
||||
SpaceType: "type",
|
||||
ReadKey: rk.Bytes(),
|
||||
ReplicationKey: 10,
|
||||
MasterKey: acc.PeerKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sp)
|
||||
|
||||
// initializing space
|
||||
spc, err := fx.spaceService.NewSpace(ctx, sp)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
// adding space to tree manager
|
||||
fx.treeManager.space = spc
|
||||
err = spc.Init(ctx)
|
||||
close(fx.treeManager.waitLoad)
|
||||
require.NoError(t, err)
|
||||
|
||||
settingsObject := spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
||||
var ids []string
|
||||
for i := 0; i < totalObjs; i++ {
|
||||
id := createTree(t, ctx, spc, acc)
|
||||
ids = append(ids, id)
|
||||
}
|
||||
// copying storage, so we will have all the trees locally
|
||||
inmemory := spc.Storage().(*commonStorage).SpaceStorage.(*spacestorage.InMemorySpaceStorage)
|
||||
storageCopy := inmemory.CopyStorage()
|
||||
treesCopy := inmemory.AllTrees()
|
||||
|
||||
// deleting trees
|
||||
for _, id := range ids {
|
||||
err = spc.DeleteTree(ctx, id)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
mapIds := map[string]struct{}{}
|
||||
for _, id := range ids[:partialObjs] {
|
||||
mapIds[id] = struct{}{}
|
||||
}
|
||||
// adding snapshot that breaks the state
|
||||
err = addIncorrectSnapshot(settingsObject, acc, mapIds, ids[partialObjs])
|
||||
require.NoError(t, err)
|
||||
// copying the contents of the settings tree
|
||||
treesCopy[settingsObject.Id()] = settingsObject.Storage()
|
||||
storageCopy.SetTrees(treesCopy)
|
||||
spc.Close()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// now we replace the storage, so the trees are back, but the settings object says that they are deleted
|
||||
fx.storageProvider.(*spacestorage.InMemorySpaceStorageProvider).SetStorage(storageCopy)
|
||||
|
||||
spc, err = fx.spaceService.NewSpace(ctx, sp)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
fx.treeManager.waitLoad = make(chan struct{})
|
||||
fx.treeManager.space = spc
|
||||
fx.treeManager.deletedIds = nil
|
||||
err = spc.Init(ctx)
|
||||
require.NoError(t, err)
|
||||
close(fx.treeManager.waitLoad)
|
||||
|
||||
// waiting until everything is deleted
|
||||
time.Sleep(3 * time.Second)
|
||||
require.Equal(t, len(ids), len(fx.treeManager.deletedIds))
|
||||
|
||||
// checking that new snapshot will contain all the changes
|
||||
settingsObject = spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
||||
settings.DoSnapshot = func(treeLen int) bool {
|
||||
return true
|
||||
}
|
||||
id := createTree(t, ctx, spc, acc)
|
||||
err = spc.DeleteTree(ctx, id)
|
||||
require.NoError(t, err)
|
||||
delIds := settingsObject.Root().Model.(*spacesyncproto.SettingsData).Snapshot.DeletedIds
|
||||
require.Equal(t, totalObjs+1, len(delIds))
|
||||
}
|
||||
|
||||
func TestSpaceDeleteIdsMarkDeleted(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
acc := fx.account.Account()
|
||||
rk := crypto.NewAES()
|
||||
ctx := context.Background()
|
||||
totalObjs := 1500
|
||||
|
||||
// creating space
|
||||
sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{
|
||||
SigningKey: acc.SignKey,
|
||||
SpaceType: "type",
|
||||
ReadKey: rk.Bytes(),
|
||||
ReplicationKey: 10,
|
||||
MasterKey: acc.PeerKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sp)
|
||||
|
||||
// initializing space
|
||||
spc, err := fx.spaceService.NewSpace(ctx, sp)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
// adding space to tree manager
|
||||
fx.treeManager.space = spc
|
||||
err = spc.Init(ctx)
|
||||
require.NoError(t, err)
|
||||
close(fx.treeManager.waitLoad)
|
||||
|
||||
settingsObject := spc.(*space).app.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
||||
var ids []string
|
||||
for i := 0; i < totalObjs; i++ {
|
||||
id := createTree(t, ctx, spc, acc)
|
||||
ids = append(ids, id)
|
||||
}
|
||||
// copying storage, so we will have the same contents, except for empty trees
|
||||
inmemory := spc.Storage().(*commonStorage).SpaceStorage.(*spacestorage.InMemorySpaceStorage)
|
||||
storageCopy := inmemory.CopyStorage()
|
||||
|
||||
// deleting trees, this will prepare the document to have all the deletion changes
|
||||
for _, id := range ids {
|
||||
err = spc.DeleteTree(ctx, id)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
treesMap := map[string]treestorage.TreeStorage{}
|
||||
// copying the contents of the settings tree
|
||||
treesMap[settingsObject.Id()] = settingsObject.Storage()
|
||||
storageCopy.SetTrees(treesMap)
|
||||
spc.Close()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// now we replace the storage, so the trees are back, but the settings object says that they are deleted
|
||||
fx.storageProvider.(*spacestorage.InMemorySpaceStorageProvider).SetStorage(storageCopy)
|
||||
|
||||
spc, err = fx.spaceService.NewSpace(ctx, sp)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, spc)
|
||||
fx.treeManager.space = spc
|
||||
fx.treeManager.waitLoad = make(chan struct{})
|
||||
fx.treeManager.deletedIds = nil
|
||||
fx.treeManager.markedIds = nil
|
||||
err = spc.Init(ctx)
|
||||
require.NoError(t, err)
|
||||
close(fx.treeManager.waitLoad)
|
||||
|
||||
// waiting until everything is deleted
|
||||
time.Sleep(3 * time.Second)
|
||||
require.Equal(t, len(ids), len(fx.treeManager.markedIds))
|
||||
require.Zero(t, len(fx.treeManager.deletedIds))
|
||||
}
|
||||
+41
-28
@@ -1,35 +1,53 @@
|
||||
//go:generate mockgen -destination mock_settingsstate/mock_settingsstate.go github.com/anytypeio/any-sync/commonspace/settings/settingsstate ObjectDeletionState,StateBuilder,ChangeFactory
|
||||
package settingsstate
|
||||
//go:generate mockgen -destination mock_deletionstate/mock_deletionstate.go github.com/anyproto/any-sync/commonspace/deletionstate ObjectDeletionState
|
||||
package deletionstate
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
const CName = "common.commonspace.deletionstate"
|
||||
|
||||
type StateUpdateObserver func(ids []string)
|
||||
|
||||
type ObjectDeletionState interface {
|
||||
app.Component
|
||||
AddObserver(observer StateUpdateObserver)
|
||||
Add(ids []string) (err error)
|
||||
Add(ids map[string]struct{})
|
||||
GetQueued() (ids []string)
|
||||
Delete(id string) (err error)
|
||||
Exists(id string) bool
|
||||
FilterJoin(ids ...[]string) (filtered []string)
|
||||
Filter(ids []string) (filtered []string)
|
||||
}
|
||||
|
||||
type objectDeletionState struct {
|
||||
sync.RWMutex
|
||||
log logger.CtxLogger
|
||||
queued map[string]struct{}
|
||||
deleted map[string]struct{}
|
||||
stateUpdateObservers []StateUpdateObserver
|
||||
storage spacestorage.SpaceStorage
|
||||
}
|
||||
|
||||
func NewObjectDeletionState(storage spacestorage.SpaceStorage) ObjectDeletionState {
|
||||
func (st *objectDeletionState) Init(a *app.App) (err error) {
|
||||
st.storage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *objectDeletionState) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func New() ObjectDeletionState {
|
||||
return &objectDeletionState{
|
||||
log: log,
|
||||
queued: map[string]struct{}{},
|
||||
deleted: map[string]struct{}{},
|
||||
storage: storage,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,19 +57,17 @@ func (st *objectDeletionState) AddObserver(observer StateUpdateObserver) {
|
||||
st.stateUpdateObservers = append(st.stateUpdateObservers, observer)
|
||||
}
|
||||
|
||||
func (st *objectDeletionState) Add(ids []string) (err error) {
|
||||
func (st *objectDeletionState) Add(ids map[string]struct{}) {
|
||||
var added []string
|
||||
st.Lock()
|
||||
defer func() {
|
||||
st.Unlock()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, ob := range st.stateUpdateObservers {
|
||||
ob(ids)
|
||||
ob(added)
|
||||
}
|
||||
}()
|
||||
|
||||
for _, id := range ids {
|
||||
for id := range ids {
|
||||
if _, exists := st.deleted[id]; exists {
|
||||
continue
|
||||
}
|
||||
@@ -60,9 +76,10 @@ func (st *objectDeletionState) Add(ids []string) (err error) {
|
||||
}
|
||||
|
||||
var status string
|
||||
status, err = st.storage.TreeDeletedStatus(id)
|
||||
status, err := st.storage.TreeDeletedStatus(id)
|
||||
if err != nil {
|
||||
return
|
||||
st.log.Warn("failed to get deleted status", zap.String("treeId", id), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
switch status {
|
||||
@@ -71,14 +88,15 @@ func (st *objectDeletionState) Add(ids []string) (err error) {
|
||||
case spacestorage.TreeDeletedStatusDeleted:
|
||||
st.deleted[id] = struct{}{}
|
||||
default:
|
||||
st.queued[id] = struct{}{}
|
||||
err = st.storage.SetTreeDeletedStatus(id, spacestorage.TreeDeletedStatusQueued)
|
||||
err := st.storage.SetTreeDeletedStatus(id, spacestorage.TreeDeletedStatusQueued)
|
||||
if err != nil {
|
||||
return
|
||||
st.log.Warn("failed to set deleted status", zap.String("treeId", id), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
st.queued[id] = struct{}{}
|
||||
}
|
||||
added = append(added, id)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (st *objectDeletionState) GetQueued() (ids []string) {
|
||||
@@ -109,19 +127,14 @@ func (st *objectDeletionState) Exists(id string) bool {
|
||||
return st.exists(id)
|
||||
}
|
||||
|
||||
func (st *objectDeletionState) FilterJoin(ids ...[]string) (filtered []string) {
|
||||
func (st *objectDeletionState) Filter(ids []string) (filtered []string) {
|
||||
st.RLock()
|
||||
defer st.RUnlock()
|
||||
filter := func(ids []string) {
|
||||
for _, id := range ids {
|
||||
if !st.exists(id) {
|
||||
filtered = append(filtered, id)
|
||||
}
|
||||
for _, id := range ids {
|
||||
if !st.exists(id) {
|
||||
filtered = append(filtered, id)
|
||||
}
|
||||
}
|
||||
for _, arr := range ids {
|
||||
filter(arr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
+11
-14
@@ -1,8 +1,8 @@
|
||||
package settingsstate
|
||||
package deletionstate
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"sort"
|
||||
@@ -18,7 +18,8 @@ type fixture struct {
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
ctrl := gomock.NewController(t)
|
||||
spaceStorage := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||
delState := NewObjectDeletionState(spaceStorage).(*objectDeletionState)
|
||||
delState := New().(*objectDeletionState)
|
||||
delState.storage = spaceStorage
|
||||
return &fixture{
|
||||
ctrl: ctrl,
|
||||
delState: delState,
|
||||
@@ -37,8 +38,7 @@ func TestDeletionState_Add(t *testing.T) {
|
||||
id := "newId"
|
||||
fx.spaceStorage.EXPECT().TreeDeletedStatus(id).Return("", nil)
|
||||
fx.spaceStorage.EXPECT().SetTreeDeletedStatus(id, spacestorage.TreeDeletedStatusQueued).Return(nil)
|
||||
err := fx.delState.Add([]string{id})
|
||||
require.NoError(t, err)
|
||||
fx.delState.Add(map[string]struct{}{id: {}})
|
||||
require.Contains(t, fx.delState.queued, id)
|
||||
})
|
||||
|
||||
@@ -47,8 +47,7 @@ func TestDeletionState_Add(t *testing.T) {
|
||||
defer fx.stop()
|
||||
id := "newId"
|
||||
fx.spaceStorage.EXPECT().TreeDeletedStatus(id).Return(spacestorage.TreeDeletedStatusQueued, nil)
|
||||
err := fx.delState.Add([]string{id})
|
||||
require.NoError(t, err)
|
||||
fx.delState.Add(map[string]struct{}{id: {}})
|
||||
require.Contains(t, fx.delState.queued, id)
|
||||
})
|
||||
|
||||
@@ -57,8 +56,7 @@ func TestDeletionState_Add(t *testing.T) {
|
||||
defer fx.stop()
|
||||
id := "newId"
|
||||
fx.spaceStorage.EXPECT().TreeDeletedStatus(id).Return(spacestorage.TreeDeletedStatusDeleted, nil)
|
||||
err := fx.delState.Add([]string{id})
|
||||
require.NoError(t, err)
|
||||
fx.delState.Add(map[string]struct{}{id: {}})
|
||||
require.Contains(t, fx.delState.deleted, id)
|
||||
})
|
||||
}
|
||||
@@ -82,8 +80,8 @@ func TestDeletionState_FilterJoin(t *testing.T) {
|
||||
fx.delState.queued["id1"] = struct{}{}
|
||||
fx.delState.queued["id2"] = struct{}{}
|
||||
|
||||
filtered := fx.delState.FilterJoin([]string{"id1"}, []string{"id3", "id2"}, []string{"id4"})
|
||||
require.Equal(t, []string{"id3", "id4"}, filtered)
|
||||
filtered := fx.delState.Filter([]string{"id3", "id2"})
|
||||
require.Equal(t, []string{"id3"}, filtered)
|
||||
}
|
||||
|
||||
func TestDeletionState_AddObserver(t *testing.T) {
|
||||
@@ -98,8 +96,7 @@ func TestDeletionState_AddObserver(t *testing.T) {
|
||||
id := "newId"
|
||||
fx.spaceStorage.EXPECT().TreeDeletedStatus(id).Return("", nil)
|
||||
fx.spaceStorage.EXPECT().SetTreeDeletedStatus(id, spacestorage.TreeDeletedStatusQueued).Return(nil)
|
||||
err := fx.delState.Add([]string{id})
|
||||
require.NoError(t, err)
|
||||
fx.delState.Add(map[string]struct{}{id: {}})
|
||||
require.Contains(t, fx.delState.queued, id)
|
||||
require.Equal(t, []string{id}, queued)
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/deletionstate (interfaces: ObjectDeletionState)
|
||||
|
||||
// Package mock_deletionstate is a generated GoMock package.
|
||||
package mock_deletionstate
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
deletionstate "github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockObjectDeletionState is a mock of ObjectDeletionState interface.
|
||||
type MockObjectDeletionState struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockObjectDeletionStateMockRecorder
|
||||
}
|
||||
|
||||
// MockObjectDeletionStateMockRecorder is the mock recorder for MockObjectDeletionState.
|
||||
type MockObjectDeletionStateMockRecorder struct {
|
||||
mock *MockObjectDeletionState
|
||||
}
|
||||
|
||||
// NewMockObjectDeletionState creates a new mock instance.
|
||||
func NewMockObjectDeletionState(ctrl *gomock.Controller) *MockObjectDeletionState {
|
||||
mock := &MockObjectDeletionState{ctrl: ctrl}
|
||||
mock.recorder = &MockObjectDeletionStateMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockObjectDeletionState) EXPECT() *MockObjectDeletionStateMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Add mocks base method.
|
||||
func (m *MockObjectDeletionState) Add(arg0 map[string]struct{}) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Add", arg0)
|
||||
}
|
||||
|
||||
// Add indicates an expected call of Add.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) Add(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockObjectDeletionState)(nil).Add), arg0)
|
||||
}
|
||||
|
||||
// AddObserver mocks base method.
|
||||
func (m *MockObjectDeletionState) AddObserver(arg0 deletionstate.StateUpdateObserver) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "AddObserver", arg0)
|
||||
}
|
||||
|
||||
// AddObserver indicates an expected call of AddObserver.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) AddObserver(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddObserver", reflect.TypeOf((*MockObjectDeletionState)(nil).AddObserver), arg0)
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
func (m *MockObjectDeletionState) Delete(arg0 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Delete", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Delete indicates an expected call of Delete.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) Delete(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockObjectDeletionState)(nil).Delete), arg0)
|
||||
}
|
||||
|
||||
// Exists mocks base method.
|
||||
func (m *MockObjectDeletionState) Exists(arg0 string) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Exists", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Exists indicates an expected call of Exists.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) Exists(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockObjectDeletionState)(nil).Exists), arg0)
|
||||
}
|
||||
|
||||
// Filter mocks base method.
|
||||
func (m *MockObjectDeletionState) Filter(arg0 []string) []string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Filter", arg0)
|
||||
ret0, _ := ret[0].([]string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Filter indicates an expected call of Filter.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) Filter(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filter", reflect.TypeOf((*MockObjectDeletionState)(nil).Filter), arg0)
|
||||
}
|
||||
|
||||
// GetQueued mocks base method.
|
||||
func (m *MockObjectDeletionState) GetQueued() []string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetQueued")
|
||||
ret0, _ := ret[0].([]string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetQueued indicates an expected call of GetQueued.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) GetQueued() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueued", reflect.TypeOf((*MockObjectDeletionState)(nil).GetQueued))
|
||||
}
|
||||
|
||||
// Init mocks base method.
|
||||
func (m *MockObjectDeletionState) Init(arg0 *app.App) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Init", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockObjectDeletionState)(nil).Init), arg0)
|
||||
}
|
||||
|
||||
// Name mocks base method.
|
||||
func (m *MockObjectDeletionState) Name() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Name")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Name indicates an expected call of Name.
|
||||
func (mr *MockObjectDeletionStateMockRecorder) Name() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockObjectDeletionState)(nil).Name))
|
||||
}
|
||||
@@ -3,18 +3,17 @@ package headsync
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/credentialprovider"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/peermanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/settings/settingsstate"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/net/peer"
|
||||
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
@@ -23,29 +22,22 @@ type DiffSyncer interface {
|
||||
Sync(ctx context.Context) error
|
||||
RemoveObjects(ids []string)
|
||||
UpdateHeads(id string, heads []string)
|
||||
Init(deletionState settingsstate.ObjectDeletionState)
|
||||
Init()
|
||||
Close() error
|
||||
}
|
||||
|
||||
func newDiffSyncer(
|
||||
spaceId string,
|
||||
diff ldiff.Diff,
|
||||
peerManager peermanager.PeerManager,
|
||||
cache treemanager.TreeManager,
|
||||
storage spacestorage.SpaceStorage,
|
||||
clientFactory spacesyncproto.ClientFactory,
|
||||
syncStatus syncstatus.StatusUpdater,
|
||||
credentialProvider credentialprovider.CredentialProvider,
|
||||
log logger.CtxLogger) DiffSyncer {
|
||||
func newDiffSyncer(hs *headSync) DiffSyncer {
|
||||
return &diffSyncer{
|
||||
diff: diff,
|
||||
spaceId: spaceId,
|
||||
cache: cache,
|
||||
storage: storage,
|
||||
peerManager: peerManager,
|
||||
clientFactory: clientFactory,
|
||||
credentialProvider: credentialProvider,
|
||||
diff: hs.diff,
|
||||
spaceId: hs.spaceId,
|
||||
treeManager: hs.treeManager,
|
||||
storage: hs.storage,
|
||||
peerManager: hs.peerManager,
|
||||
clientFactory: spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient),
|
||||
credentialProvider: hs.credentialProvider,
|
||||
log: log,
|
||||
syncStatus: syncStatus,
|
||||
syncStatus: hs.syncStatus,
|
||||
deletionState: hs.deletionState,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,18 +45,19 @@ type diffSyncer struct {
|
||||
spaceId string
|
||||
diff ldiff.Diff
|
||||
peerManager peermanager.PeerManager
|
||||
cache treemanager.TreeManager
|
||||
treeManager treemanager.TreeManager
|
||||
storage spacestorage.SpaceStorage
|
||||
clientFactory spacesyncproto.ClientFactory
|
||||
log logger.CtxLogger
|
||||
deletionState settingsstate.ObjectDeletionState
|
||||
deletionState deletionstate.ObjectDeletionState
|
||||
credentialProvider credentialprovider.CredentialProvider
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
treeSyncer treemanager.TreeSyncer
|
||||
}
|
||||
|
||||
func (d *diffSyncer) Init(deletionState settingsstate.ObjectDeletionState) {
|
||||
d.deletionState = deletionState
|
||||
func (d *diffSyncer) Init() {
|
||||
d.deletionState.AddObserver(d.RemoveObjects)
|
||||
d.treeSyncer = d.treeManager.NewTreeSyncer(d.spaceId, d.treeManager)
|
||||
}
|
||||
|
||||
func (d *diffSyncer) RemoveObjects(ids []string) {
|
||||
@@ -113,8 +106,14 @@ func (d *diffSyncer) Sync(ctx context.Context) error {
|
||||
|
||||
func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error) {
|
||||
ctx = logger.CtxWithFields(ctx, zap.String("peerId", p.Id()))
|
||||
conn, err := p.AcquireDrpcConn(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer p.ReleaseDrpcConn(conn)
|
||||
|
||||
var (
|
||||
cl = d.clientFactory.Client(p)
|
||||
cl = d.clientFactory.Client(conn)
|
||||
rdiff = NewRemoteDiff(d.spaceId, cl)
|
||||
stateCounter = d.syncStatus.StateCounter()
|
||||
)
|
||||
@@ -124,7 +123,7 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
|
||||
if err != nil && err != spacesyncproto.ErrSpaceMissing {
|
||||
if err == spacesyncproto.ErrSpaceIsDeleted {
|
||||
d.log.Debug("got space deleted while syncing")
|
||||
d.syncTrees(ctx, p.Id(), []string{d.storage.SpaceSettingsId()})
|
||||
d.treeSyncer.SyncAll(ctx, p.Id(), []string{d.storage.SpaceSettingsId()}, nil)
|
||||
}
|
||||
d.syncStatus.SetNodesOnline(p.Id(), false)
|
||||
return fmt.Errorf("diff error: %v", err)
|
||||
@@ -137,45 +136,24 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
|
||||
|
||||
totalLen := len(newIds) + len(changedIds) + len(removedIds)
|
||||
// not syncing ids which were removed through settings document
|
||||
filteredIds := d.deletionState.FilterJoin(newIds, changedIds, removedIds)
|
||||
missingIds := d.deletionState.Filter(newIds)
|
||||
existingIds := append(d.deletionState.Filter(removedIds), d.deletionState.Filter(changedIds)...)
|
||||
|
||||
d.syncStatus.RemoveAllExcept(p.Id(), filteredIds, stateCounter)
|
||||
|
||||
d.syncTrees(ctx, p.Id(), filteredIds)
|
||||
d.syncStatus.RemoveAllExcept(p.Id(), existingIds, stateCounter)
|
||||
|
||||
err = d.treeSyncer.SyncAll(ctx, p.Id(), existingIds, missingIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.log.Info("sync done:", zap.Int("newIds", len(newIds)),
|
||||
zap.Int("changedIds", len(changedIds)),
|
||||
zap.Int("removedIds", len(removedIds)),
|
||||
zap.Int("already deleted ids", totalLen-len(filteredIds)),
|
||||
zap.Int("already deleted ids", totalLen-len(existingIds)-len(missingIds)),
|
||||
zap.String("peerId", p.Id()),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *diffSyncer) syncTrees(ctx context.Context, peerId string, trees []string) {
|
||||
for _, tId := range trees {
|
||||
tree, err := d.cache.GetTree(ctx, d.spaceId, tId)
|
||||
if err != nil {
|
||||
d.log.InfoCtx(ctx, "can't load tree", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
syncTree, ok := tree.(synctree.SyncTree)
|
||||
if !ok {
|
||||
d.log.InfoCtx(ctx, "not a sync tree", zap.String("objectId", tId))
|
||||
continue
|
||||
}
|
||||
// the idea why we call it directly is that if we try to get it from cache
|
||||
// it may be already there (i.e. loaded)
|
||||
// and build func will not be called, thus we won't sync the tree
|
||||
// therefore we just do it manually
|
||||
if err = syncTree.SyncWithPeer(ctx, peerId); err != nil {
|
||||
d.log.WarnCtx(ctx, "synctree.SyncWithPeer error", zap.Error(err), zap.String("treeId", tId))
|
||||
} else {
|
||||
d.log.DebugCtx(ctx, "success synctree.SyncWithPeer", zap.String("treeId", tId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, peerId string, cl spacesyncproto.DRPCSpaceSyncClient) (err error) {
|
||||
aclStorage, err := d.storage.AclStorage()
|
||||
if err != nil {
|
||||
@@ -238,3 +216,7 @@ func (d *diffSyncer) subscribe(ctx context.Context, peerId string) (err error) {
|
||||
Payload: payload,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *diffSyncer) Close() error {
|
||||
return d.treeSyncer.Close()
|
||||
}
|
||||
@@ -4,24 +4,14 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/app/ldiff/mock_ldiff"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/credentialprovider/mock_credentialprovider"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
mock_treestorage "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/treemanager/mock_treemanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/peermanager/mock_peermanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto/mock_spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage/mock_liststorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/libp2p/go-libp2p/core/sec"
|
||||
"github.com/stretchr/testify/require"
|
||||
"storj.io/drpc"
|
||||
"testing"
|
||||
@@ -36,56 +26,6 @@ type pushSpaceRequestMatcher struct {
|
||||
spaceHeader *spacesyncproto.RawSpaceHeaderWithId
|
||||
}
|
||||
|
||||
func (p pushSpaceRequestMatcher) Matches(x interface{}) bool {
|
||||
res, ok := x.(*spacesyncproto.SpacePushRequest)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId && bytes.Equal(p.credential, res.Credential)
|
||||
}
|
||||
|
||||
func (p pushSpaceRequestMatcher) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type mockPeer struct{}
|
||||
|
||||
func (m mockPeer) TryClose(objectTTL time.Duration) (res bool, err error) {
|
||||
return true, m.Close()
|
||||
}
|
||||
|
||||
func (m mockPeer) Id() string {
|
||||
return "mockId"
|
||||
}
|
||||
|
||||
func (m mockPeer) LastUsage() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (m mockPeer) Secure() sec.SecureConn {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mockPeer) UpdateLastUsage() {
|
||||
}
|
||||
|
||||
func (m mockPeer) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mockPeer) Closed() <-chan struct{} {
|
||||
return make(chan struct{})
|
||||
}
|
||||
|
||||
func (m mockPeer) Invoke(ctx context.Context, rpc string, enc drpc.Encoding, in, out drpc.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mockPeer) NewStream(ctx context.Context, rpc string, enc drpc.Encoding) (drpc.Stream, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func newPushSpaceRequestMatcher(
|
||||
spaceId string,
|
||||
aclRootId string,
|
||||
@@ -101,79 +41,134 @@ func newPushSpaceRequestMatcher(
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffSyncer_Sync(t *testing.T) {
|
||||
// setup
|
||||
ctx := context.Background()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
func (p pushSpaceRequestMatcher) Matches(x interface{}) bool {
|
||||
res, ok := x.(*spacesyncproto.SpacePushRequest)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
||||
peerManagerMock := mock_peermanager.NewMockPeerManager(ctrl)
|
||||
cacheMock := mock_treemanager.NewMockTreeManager(ctrl)
|
||||
stMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
|
||||
factory := spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
|
||||
return clientMock
|
||||
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId && bytes.Equal(p.credential, res.Credential)
|
||||
}
|
||||
|
||||
func (p pushSpaceRequestMatcher) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type mockPeer struct {
|
||||
}
|
||||
|
||||
func (m mockPeer) Id() string {
|
||||
return "peerId"
|
||||
}
|
||||
|
||||
func (m mockPeer) Context() context.Context {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func (m mockPeer) AcquireDrpcConn(ctx context.Context) (drpc.Conn, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m mockPeer) ReleaseDrpcConn(conn drpc.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m mockPeer) DoDrpc(ctx context.Context, do func(conn drpc.Conn) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m mockPeer) IsClosed() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m mockPeer) TryClose(objectTTL time.Duration) (res bool, err error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (m mockPeer) Close() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fx *headSyncFixture) initDiffSyncer(t *testing.T) {
|
||||
fx.init(t)
|
||||
fx.diffSyncer = newDiffSyncer(fx.headSync).(*diffSyncer)
|
||||
fx.diffSyncer.clientFactory = spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
|
||||
return fx.clientMock
|
||||
})
|
||||
credentialProvider := mock_credentialprovider.NewMockCredentialProvider(ctrl)
|
||||
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
|
||||
spaceId := "spaceId"
|
||||
aclRootId := "aclRootId"
|
||||
l := logger.NewNamed(spaceId)
|
||||
diffSyncer := newDiffSyncer(spaceId, diffMock, peerManagerMock, cacheMock, stMock, factory, syncstatus.NewNoOpSyncStatus(), credentialProvider, l)
|
||||
delState.EXPECT().AddObserver(gomock.Any())
|
||||
diffSyncer.Init(delState)
|
||||
fx.deletionStateMock.EXPECT().AddObserver(gomock.Any())
|
||||
fx.treeManagerMock.EXPECT().NewTreeSyncer(fx.spaceState.SpaceId, fx.treeManagerMock).Return(fx.treeSyncerMock)
|
||||
fx.diffSyncer.Init()
|
||||
}
|
||||
|
||||
func TestDiffSyncer(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("diff syncer sync", func(t *testing.T) {
|
||||
peerManagerMock.EXPECT().
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
mPeer := mockPeer{}
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return([]peer.Peer{mockPeer{}}, nil)
|
||||
diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||
Return([]peer.Peer{mPeer}, nil)
|
||||
fx.diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
||||
Return([]string{"new"}, []string{"changed"}, nil, nil)
|
||||
delState.EXPECT().FilterJoin(gomock.Any()).Return([]string{"new", "changed"})
|
||||
for _, arg := range []string{"new", "changed"} {
|
||||
cacheMock.EXPECT().
|
||||
GetTree(gomock.Any(), spaceId, arg).
|
||||
Return(nil, nil)
|
||||
}
|
||||
require.NoError(t, diffSyncer.Sync(ctx))
|
||||
fx.deletionStateMock.EXPECT().Filter([]string{"new"}).Return([]string{"new"}).Times(1)
|
||||
fx.deletionStateMock.EXPECT().Filter([]string{"changed"}).Return([]string{"changed"}).Times(1)
|
||||
fx.deletionStateMock.EXPECT().Filter(nil).Return(nil).Times(1)
|
||||
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"changed"}, []string{"new"}).Return(nil)
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync conf error", func(t *testing.T) {
|
||||
peerManagerMock.EXPECT().
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
ctx := context.Background()
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return(nil, fmt.Errorf("some error"))
|
||||
|
||||
require.Error(t, diffSyncer.Sync(ctx))
|
||||
require.Error(t, fx.diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("deletion state remove objects", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
deletedId := "id"
|
||||
delState.EXPECT().Exists(deletedId).Return(true)
|
||||
fx.deletionStateMock.EXPECT().Exists(deletedId).Return(true)
|
||||
|
||||
// this should not result in any mock being called
|
||||
diffSyncer.UpdateHeads(deletedId, []string{"someHead"})
|
||||
fx.diffSyncer.UpdateHeads(deletedId, []string{"someHead"})
|
||||
})
|
||||
|
||||
t.Run("update heads updates diff", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
newId := "newId"
|
||||
newHeads := []string{"h1", "h2"}
|
||||
hash := "hash"
|
||||
diffMock.EXPECT().Set(ldiff.Element{
|
||||
fx.diffMock.EXPECT().Set(ldiff.Element{
|
||||
Id: newId,
|
||||
Head: concatStrings(newHeads),
|
||||
})
|
||||
diffMock.EXPECT().Hash().Return(hash)
|
||||
delState.EXPECT().Exists(newId).Return(false)
|
||||
stMock.EXPECT().WriteSpaceHash(hash)
|
||||
diffSyncer.UpdateHeads(newId, newHeads)
|
||||
fx.diffMock.EXPECT().Hash().Return(hash)
|
||||
fx.deletionStateMock.EXPECT().Exists(newId).Return(false)
|
||||
fx.storageMock.EXPECT().WriteSpaceHash(hash)
|
||||
fx.diffSyncer.UpdateHeads(newId, newHeads)
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync space missing", func(t *testing.T) {
|
||||
aclStorageMock := mock_liststorage.NewMockListStorage(ctrl)
|
||||
settingsStorage := mock_treestorage.NewMockTreeStorage(ctrl)
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
aclStorageMock := mock_liststorage.NewMockListStorage(fx.ctrl)
|
||||
settingsStorage := mock_treestorage.NewMockTreeStorage(fx.ctrl)
|
||||
settingsId := "settingsId"
|
||||
aclRootId := "aclRootId"
|
||||
aclRoot := &aclrecordproto.RawAclRecordWithId{
|
||||
Id: aclRootId,
|
||||
}
|
||||
@@ -184,56 +179,61 @@ func TestDiffSyncer_Sync(t *testing.T) {
|
||||
spaceSettingsId := "spaceSettingsId"
|
||||
credential := []byte("credential")
|
||||
|
||||
peerManagerMock.EXPECT().
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return([]peer.Peer{mockPeer{}}, nil)
|
||||
diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||
fx.diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
||||
Return(nil, nil, nil, spacesyncproto.ErrSpaceMissing)
|
||||
|
||||
stMock.EXPECT().AclStorage().Return(aclStorageMock, nil)
|
||||
stMock.EXPECT().SpaceHeader().Return(spaceHeader, nil)
|
||||
stMock.EXPECT().SpaceSettingsId().Return(spaceSettingsId)
|
||||
stMock.EXPECT().TreeStorage(spaceSettingsId).Return(settingsStorage, nil)
|
||||
fx.storageMock.EXPECT().AclStorage().Return(aclStorageMock, nil)
|
||||
fx.storageMock.EXPECT().SpaceHeader().Return(spaceHeader, nil)
|
||||
fx.storageMock.EXPECT().SpaceSettingsId().Return(spaceSettingsId)
|
||||
fx.storageMock.EXPECT().TreeStorage(spaceSettingsId).Return(settingsStorage, nil)
|
||||
|
||||
settingsStorage.EXPECT().Root().Return(settingsRoot, nil)
|
||||
aclStorageMock.EXPECT().
|
||||
Root().
|
||||
Return(aclRoot, nil)
|
||||
credentialProvider.EXPECT().
|
||||
fx.credentialProviderMock.EXPECT().
|
||||
GetCredential(gomock.Any(), spaceHeader).
|
||||
Return(credential, nil)
|
||||
clientMock.EXPECT().
|
||||
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRootId, settingsId, credential, spaceHeader)).
|
||||
fx.clientMock.EXPECT().
|
||||
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(fx.spaceState.SpaceId, aclRootId, settingsId, credential, spaceHeader)).
|
||||
Return(nil, nil)
|
||||
peerManagerMock.EXPECT().SendPeer(gomock.Any(), "mockId", gomock.Any())
|
||||
fx.peerManagerMock.EXPECT().SendPeer(gomock.Any(), "peerId", gomock.Any())
|
||||
|
||||
require.NoError(t, diffSyncer.Sync(ctx))
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync unexpected", func(t *testing.T) {
|
||||
peerManagerMock.EXPECT().
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return([]peer.Peer{mockPeer{}}, nil)
|
||||
diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||
fx.diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
||||
Return(nil, nil, nil, spacesyncproto.ErrUnexpected)
|
||||
|
||||
require.NoError(t, diffSyncer.Sync(ctx))
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync space is deleted error", func(t *testing.T) {
|
||||
peerManagerMock.EXPECT().
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
mPeer := mockPeer{}
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return([]peer.Peer{mockPeer{}}, nil)
|
||||
diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||
Return([]peer.Peer{mPeer}, nil)
|
||||
fx.diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
||||
Return(nil, nil, nil, spacesyncproto.ErrSpaceIsDeleted)
|
||||
stMock.EXPECT().SpaceSettingsId().Return("settingsId")
|
||||
cacheMock.EXPECT().
|
||||
GetTree(gomock.Any(), spaceId, "settingsId").
|
||||
Return(nil, nil)
|
||||
fx.storageMock.EXPECT().SpaceSettingsId().Return("settingsId")
|
||||
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"settingsId"}, nil).Return(nil)
|
||||
|
||||
require.NoError(t, diffSyncer.Sync(ctx))
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
})
|
||||
}
|
||||
+103
-105
@@ -1,125 +1,147 @@
|
||||
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anytypeio/any-sync/commonspace/headsync DiffSyncer
|
||||
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anyproto/any-sync/commonspace/headsync DiffSyncer
|
||||
package headsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/credentialprovider"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/peermanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/settings/settingsstate"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/net/peer"
|
||||
"github.com/anytypeio/any-sync/nodeconf"
|
||||
"github.com/anytypeio/any-sync/util/periodicsync"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
config2 "github.com/anyproto/any-sync/commonspace/config"
|
||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"github.com/anyproto/any-sync/util/periodicsync"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
const CName = "common.commonspace.headsync"
|
||||
|
||||
type TreeHeads struct {
|
||||
Id string
|
||||
Heads []string
|
||||
}
|
||||
|
||||
type HeadSync interface {
|
||||
Init(objectIds []string, deletionState settingsstate.ObjectDeletionState)
|
||||
|
||||
app.ComponentRunnable
|
||||
ExternalIds() []string
|
||||
DebugAllHeads() (res []TreeHeads)
|
||||
AllIds() []string
|
||||
UpdateHeads(id string, heads []string)
|
||||
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
|
||||
RemoveObjects(ids []string)
|
||||
AllIds() []string
|
||||
DebugAllHeads() (res []TreeHeads)
|
||||
|
||||
Close() (err error)
|
||||
}
|
||||
|
||||
type headSync struct {
|
||||
spaceId string
|
||||
periodicSync periodicsync.PeriodicSync
|
||||
storage spacestorage.SpaceStorage
|
||||
diff ldiff.Diff
|
||||
log logger.CtxLogger
|
||||
syncer DiffSyncer
|
||||
configuration nodeconf.NodeConf
|
||||
spaceIsDeleted *atomic.Bool
|
||||
syncPeriod int
|
||||
|
||||
syncPeriod int
|
||||
periodicSync periodicsync.PeriodicSync
|
||||
storage spacestorage.SpaceStorage
|
||||
diff ldiff.Diff
|
||||
log logger.CtxLogger
|
||||
syncer DiffSyncer
|
||||
configuration nodeconf.NodeConf
|
||||
peerManager peermanager.PeerManager
|
||||
treeManager treemanager.TreeManager
|
||||
credentialProvider credentialprovider.CredentialProvider
|
||||
syncStatus syncstatus.StatusService
|
||||
deletionState deletionstate.ObjectDeletionState
|
||||
}
|
||||
|
||||
func NewHeadSync(
|
||||
spaceId string,
|
||||
spaceIsDeleted *atomic.Bool,
|
||||
syncPeriod int,
|
||||
configuration nodeconf.NodeConf,
|
||||
storage spacestorage.SpaceStorage,
|
||||
peerManager peermanager.PeerManager,
|
||||
cache treemanager.TreeManager,
|
||||
syncStatus syncstatus.StatusUpdater,
|
||||
credentialProvider credentialprovider.CredentialProvider,
|
||||
log logger.CtxLogger) HeadSync {
|
||||
func New() HeadSync {
|
||||
return &headSync{}
|
||||
}
|
||||
|
||||
diff := ldiff.New(16, 16)
|
||||
l := log.With(zap.String("spaceId", spaceId))
|
||||
factory := spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient)
|
||||
syncer := newDiffSyncer(spaceId, diff, peerManager, cache, storage, factory, syncStatus, credentialProvider, l)
|
||||
var createDiffSyncer = newDiffSyncer
|
||||
|
||||
func (h *headSync) Init(a *app.App) (err error) {
|
||||
shared := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||
cfg := a.MustComponent("config").(config2.ConfigGetter)
|
||||
h.spaceId = shared.SpaceId
|
||||
h.spaceIsDeleted = shared.SpaceIsDeleted
|
||||
h.syncPeriod = cfg.GetSpace().SyncPeriod
|
||||
h.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
||||
h.log = log.With(zap.String("spaceId", h.spaceId))
|
||||
h.storage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
||||
h.diff = ldiff.New(16, 16)
|
||||
h.peerManager = a.MustComponent(peermanager.CName).(peermanager.PeerManager)
|
||||
h.credentialProvider = a.MustComponent(credentialprovider.CName).(credentialprovider.CredentialProvider)
|
||||
h.syncStatus = a.MustComponent(syncstatus.CName).(syncstatus.StatusService)
|
||||
h.treeManager = a.MustComponent(treemanager.CName).(treemanager.TreeManager)
|
||||
h.deletionState = a.MustComponent(deletionstate.CName).(deletionstate.ObjectDeletionState)
|
||||
h.syncer = createDiffSyncer(h)
|
||||
sync := func(ctx context.Context) (err error) {
|
||||
// for clients cancelling the sync process
|
||||
if spaceIsDeleted.Load() && !configuration.IsResponsible(spaceId) {
|
||||
if h.spaceIsDeleted.Load() && !h.configuration.IsResponsible(h.spaceId) {
|
||||
return spacesyncproto.ErrSpaceIsDeleted
|
||||
}
|
||||
return syncer.Sync(ctx)
|
||||
}
|
||||
periodicSync := periodicsync.NewPeriodicSync(syncPeriod, time.Minute, sync, l)
|
||||
|
||||
return &headSync{
|
||||
spaceId: spaceId,
|
||||
storage: storage,
|
||||
syncer: syncer,
|
||||
periodicSync: periodicSync,
|
||||
diff: diff,
|
||||
log: log,
|
||||
syncPeriod: syncPeriod,
|
||||
configuration: configuration,
|
||||
spaceIsDeleted: spaceIsDeleted,
|
||||
return h.syncer.Sync(ctx)
|
||||
}
|
||||
h.periodicSync = periodicsync.NewPeriodicSync(h.syncPeriod, time.Minute, sync, h.log)
|
||||
// TODO: move to run?
|
||||
h.syncer.Init()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *headSync) Init(objectIds []string, deletionState settingsstate.ObjectDeletionState) {
|
||||
d.fillDiff(objectIds)
|
||||
d.syncer.Init(deletionState)
|
||||
d.periodicSync.Run()
|
||||
func (h *headSync) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (d *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
||||
if d.spaceIsDeleted.Load() {
|
||||
func (h *headSync) Run(ctx context.Context) (err error) {
|
||||
initialIds, err := h.storage.StoredIds()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h.fillDiff(initialIds)
|
||||
h.periodicSync.Run()
|
||||
return
|
||||
}
|
||||
|
||||
func (h *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
||||
if h.spaceIsDeleted.Load() {
|
||||
peerId, err := peer.CtxPeerId(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// stop receiving all request for sync from clients
|
||||
if !slices.Contains(d.configuration.NodeIds(d.spaceId), peerId) {
|
||||
if !slices.Contains(h.configuration.NodeIds(h.spaceId), peerId) {
|
||||
return nil, spacesyncproto.ErrSpaceIsDeleted
|
||||
}
|
||||
}
|
||||
return HandleRangeRequest(ctx, d.diff, req)
|
||||
return HandleRangeRequest(ctx, h.diff, req)
|
||||
}
|
||||
|
||||
func (d *headSync) UpdateHeads(id string, heads []string) {
|
||||
d.syncer.UpdateHeads(id, heads)
|
||||
func (h *headSync) UpdateHeads(id string, heads []string) {
|
||||
h.syncer.UpdateHeads(id, heads)
|
||||
}
|
||||
|
||||
func (d *headSync) AllIds() []string {
|
||||
return d.diff.Ids()
|
||||
func (h *headSync) AllIds() []string {
|
||||
return h.diff.Ids()
|
||||
}
|
||||
|
||||
func (d *headSync) DebugAllHeads() (res []TreeHeads) {
|
||||
els := d.diff.Elements()
|
||||
func (h *headSync) ExternalIds() []string {
|
||||
settingsId := h.storage.SpaceSettingsId()
|
||||
return slice.DiscardFromSlice(h.AllIds(), func(id string) bool {
|
||||
return id == settingsId
|
||||
})
|
||||
}
|
||||
|
||||
func (h *headSync) DebugAllHeads() (res []TreeHeads) {
|
||||
els := h.diff.Elements()
|
||||
for _, el := range els {
|
||||
idHead := TreeHeads{
|
||||
Id: el.Id,
|
||||
@@ -130,19 +152,19 @@ func (d *headSync) DebugAllHeads() (res []TreeHeads) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d *headSync) RemoveObjects(ids []string) {
|
||||
d.syncer.RemoveObjects(ids)
|
||||
func (h *headSync) RemoveObjects(ids []string) {
|
||||
h.syncer.RemoveObjects(ids)
|
||||
}
|
||||
|
||||
func (d *headSync) Close() (err error) {
|
||||
d.periodicSync.Close()
|
||||
return nil
|
||||
func (h *headSync) Close(ctx context.Context) (err error) {
|
||||
h.periodicSync.Close()
|
||||
return h.syncer.Close()
|
||||
}
|
||||
|
||||
func (d *headSync) fillDiff(objectIds []string) {
|
||||
func (h *headSync) fillDiff(objectIds []string) {
|
||||
var els = make([]ldiff.Element, 0, len(objectIds))
|
||||
for _, id := range objectIds {
|
||||
st, err := d.storage.TreeStorage(id)
|
||||
st, err := h.storage.TreeStorage(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -155,32 +177,8 @@ func (d *headSync) fillDiff(objectIds []string) {
|
||||
Head: concatStrings(heads),
|
||||
})
|
||||
}
|
||||
d.diff.Set(els...)
|
||||
if err := d.storage.WriteSpaceHash(d.diff.Hash()); err != nil {
|
||||
d.log.Error("can't write space hash", zap.Error(err))
|
||||
h.diff.Set(els...)
|
||||
if err := h.storage.WriteSpaceHash(h.diff.Hash()); err != nil {
|
||||
h.log.Error("can't write space hash", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func concatStrings(strs []string) string {
|
||||
var (
|
||||
b strings.Builder
|
||||
totalLen int
|
||||
)
|
||||
for _, s := range strs {
|
||||
totalLen += len(s)
|
||||
}
|
||||
|
||||
b.Grow(totalLen)
|
||||
for _, s := range strs {
|
||||
b.WriteString(s)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func splitString(str string) (res []string) {
|
||||
const cidLen = 59
|
||||
for i := 0; i < len(str); i += cidLen {
|
||||
res = append(res, str[i:i+cidLen])
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,70 +1,179 @@
|
||||
package headsync
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/app/ldiff/mock_ldiff"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/headsync/mock_headsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/settings/settingsstate/mock_settingsstate"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||
"github.com/anytypeio/any-sync/util/periodicsync/mock_periodicsync"
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/app/ldiff/mock_ldiff"
|
||||
"github.com/anyproto/any-sync/commonspace/config"
|
||||
"github.com/anyproto/any-sync/commonspace/credentialprovider"
|
||||
"github.com/anyproto/any-sync/commonspace/credentialprovider/mock_credentialprovider"
|
||||
"github.com/anyproto/any-sync/commonspace/deletionstate"
|
||||
"github.com/anyproto/any-sync/commonspace/deletionstate/mock_deletionstate"
|
||||
"github.com/anyproto/any-sync/commonspace/headsync/mock_headsync"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage/mock_treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager/mock_treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager/mock_peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto/mock_spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"github.com/anyproto/any-sync/nodeconf/mock_nodeconf"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDiffService(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
type mockConfig struct {
|
||||
}
|
||||
|
||||
spaceId := "spaceId"
|
||||
l := logger.NewNamed("sync")
|
||||
pSyncMock := mock_periodicsync.NewMockPeriodicSync(ctrl)
|
||||
storageMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||
treeStorageMock := mock_treestorage.NewMockTreeStorage(ctrl)
|
||||
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
||||
syncer := mock_headsync.NewMockDiffSyncer(ctrl)
|
||||
delState := mock_settingsstate.NewMockObjectDeletionState(ctrl)
|
||||
syncPeriod := 1
|
||||
initId := "initId"
|
||||
func (m mockConfig) Init(a *app.App) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
service := &headSync{
|
||||
spaceId: spaceId,
|
||||
storage: storageMock,
|
||||
periodicSync: pSyncMock,
|
||||
syncer: syncer,
|
||||
diff: diffMock,
|
||||
log: l,
|
||||
syncPeriod: syncPeriod,
|
||||
func (m mockConfig) Name() (name string) {
|
||||
return "config"
|
||||
}
|
||||
|
||||
func (m mockConfig) GetSpace() config.Config {
|
||||
return config.Config{}
|
||||
}
|
||||
|
||||
type headSyncFixture struct {
|
||||
spaceState *spacestate.SpaceState
|
||||
ctrl *gomock.Controller
|
||||
app *app.App
|
||||
|
||||
configurationMock *mock_nodeconf.MockService
|
||||
storageMock *mock_spacestorage.MockSpaceStorage
|
||||
peerManagerMock *mock_peermanager.MockPeerManager
|
||||
credentialProviderMock *mock_credentialprovider.MockCredentialProvider
|
||||
syncStatus syncstatus.StatusService
|
||||
treeManagerMock *mock_treemanager.MockTreeManager
|
||||
deletionStateMock *mock_deletionstate.MockObjectDeletionState
|
||||
diffSyncerMock *mock_headsync.MockDiffSyncer
|
||||
treeSyncerMock *mock_treemanager.MockTreeSyncer
|
||||
diffMock *mock_ldiff.MockDiff
|
||||
clientMock *mock_spacesyncproto.MockDRPCSpaceSyncClient
|
||||
headSync *headSync
|
||||
diffSyncer *diffSyncer
|
||||
}
|
||||
|
||||
func newHeadSyncFixture(t *testing.T) *headSyncFixture {
|
||||
spaceState := &spacestate.SpaceState{
|
||||
SpaceId: "spaceId",
|
||||
SpaceIsDeleted: &atomic.Bool{},
|
||||
}
|
||||
ctrl := gomock.NewController(t)
|
||||
configurationMock := mock_nodeconf.NewMockService(ctrl)
|
||||
configurationMock.EXPECT().Name().AnyTimes().Return(nodeconf.CName)
|
||||
storageMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||
storageMock.EXPECT().Name().AnyTimes().Return(spacestorage.CName)
|
||||
peerManagerMock := mock_peermanager.NewMockPeerManager(ctrl)
|
||||
peerManagerMock.EXPECT().Name().AnyTimes().Return(peermanager.CName)
|
||||
credentialProviderMock := mock_credentialprovider.NewMockCredentialProvider(ctrl)
|
||||
credentialProviderMock.EXPECT().Name().AnyTimes().Return(credentialprovider.CName)
|
||||
syncStatus := syncstatus.NewNoOpSyncStatus()
|
||||
treeManagerMock := mock_treemanager.NewMockTreeManager(ctrl)
|
||||
treeManagerMock.EXPECT().Name().AnyTimes().Return(treemanager.CName)
|
||||
deletionStateMock := mock_deletionstate.NewMockObjectDeletionState(ctrl)
|
||||
deletionStateMock.EXPECT().Name().AnyTimes().Return(deletionstate.CName)
|
||||
diffSyncerMock := mock_headsync.NewMockDiffSyncer(ctrl)
|
||||
treeSyncerMock := mock_treemanager.NewMockTreeSyncer(ctrl)
|
||||
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
||||
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
|
||||
hs := &headSync{}
|
||||
a := &app.App{}
|
||||
a.Register(spaceState).
|
||||
Register(mockConfig{}).
|
||||
Register(configurationMock).
|
||||
Register(storageMock).
|
||||
Register(peerManagerMock).
|
||||
Register(credentialProviderMock).
|
||||
Register(syncStatus).
|
||||
Register(treeManagerMock).
|
||||
Register(deletionStateMock).
|
||||
Register(hs)
|
||||
return &headSyncFixture{
|
||||
spaceState: spaceState,
|
||||
ctrl: ctrl,
|
||||
app: a,
|
||||
configurationMock: configurationMock,
|
||||
storageMock: storageMock,
|
||||
peerManagerMock: peerManagerMock,
|
||||
credentialProviderMock: credentialProviderMock,
|
||||
syncStatus: syncStatus,
|
||||
treeManagerMock: treeManagerMock,
|
||||
deletionStateMock: deletionStateMock,
|
||||
headSync: hs,
|
||||
diffSyncerMock: diffSyncerMock,
|
||||
treeSyncerMock: treeSyncerMock,
|
||||
diffMock: diffMock,
|
||||
clientMock: clientMock,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("init", func(t *testing.T) {
|
||||
storageMock.EXPECT().TreeStorage(initId).Return(treeStorageMock, nil)
|
||||
treeStorageMock.EXPECT().Heads().Return([]string{"h1", "h2"}, nil)
|
||||
syncer.EXPECT().Init(delState)
|
||||
diffMock.EXPECT().Set(ldiff.Element{
|
||||
Id: initId,
|
||||
func (fx *headSyncFixture) init(t *testing.T) {
|
||||
createDiffSyncer = func(hs *headSync) DiffSyncer {
|
||||
return fx.diffSyncerMock
|
||||
}
|
||||
fx.diffSyncerMock.EXPECT().Init()
|
||||
err := fx.headSync.Init(fx.app)
|
||||
require.NoError(t, err)
|
||||
fx.headSync.diff = fx.diffMock
|
||||
}
|
||||
|
||||
func (fx *headSyncFixture) stop() {
|
||||
fx.ctrl.Finish()
|
||||
}
|
||||
|
||||
func TestHeadSync(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("run close", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.init(t)
|
||||
defer fx.stop()
|
||||
|
||||
ids := []string{"id1"}
|
||||
treeMock := mock_treestorage.NewMockTreeStorage(fx.ctrl)
|
||||
fx.storageMock.EXPECT().StoredIds().Return(ids, nil)
|
||||
fx.storageMock.EXPECT().TreeStorage(ids[0]).Return(treeMock, nil)
|
||||
treeMock.EXPECT().Heads().Return([]string{"h1", "h2"}, nil)
|
||||
fx.diffMock.EXPECT().Set(ldiff.Element{
|
||||
Id: "id1",
|
||||
Head: "h1h2",
|
||||
})
|
||||
hash := "123"
|
||||
diffMock.EXPECT().Hash().Return(hash)
|
||||
storageMock.EXPECT().WriteSpaceHash(hash)
|
||||
pSyncMock.EXPECT().Run()
|
||||
service.Init([]string{initId}, delState)
|
||||
fx.diffMock.EXPECT().Hash().Return("hash")
|
||||
fx.storageMock.EXPECT().WriteSpaceHash("hash").Return(nil)
|
||||
fx.diffSyncerMock.EXPECT().Sync(gomock.Any()).Return(nil)
|
||||
fx.diffSyncerMock.EXPECT().Close().Return(nil)
|
||||
err := fx.headSync.Run(ctx)
|
||||
require.NoError(t, err)
|
||||
err = fx.headSync.Close(ctx)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("update heads", func(t *testing.T) {
|
||||
syncer.EXPECT().UpdateHeads(initId, []string{"h1", "h2"})
|
||||
service.UpdateHeads(initId, []string{"h1", "h2"})
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.init(t)
|
||||
defer fx.stop()
|
||||
|
||||
fx.diffSyncerMock.EXPECT().UpdateHeads("id1", []string{"h1"})
|
||||
fx.headSync.UpdateHeads("id1", []string{"h1"})
|
||||
})
|
||||
|
||||
t.Run("remove objects", func(t *testing.T) {
|
||||
syncer.EXPECT().RemoveObjects([]string{"h1", "h2"})
|
||||
service.RemoveObjects([]string{"h1", "h2"})
|
||||
})
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.init(t)
|
||||
defer fx.stop()
|
||||
|
||||
t.Run("close", func(t *testing.T) {
|
||||
pSyncMock.EXPECT().Close()
|
||||
service.Close()
|
||||
fx.diffSyncerMock.EXPECT().RemoveObjects([]string{"id1"})
|
||||
fx.headSync.RemoveObjects([]string{"id1"})
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/headsync (interfaces: DiffSyncer)
|
||||
// Source: github.com/anyproto/any-sync/commonspace/headsync (interfaces: DiffSyncer)
|
||||
|
||||
// Package mock_headsync is a generated GoMock package.
|
||||
package mock_headsync
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
settingsstate "github.com/anytypeio/any-sync/commonspace/settings/settingsstate"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
@@ -35,16 +34,30 @@ func (m *MockDiffSyncer) EXPECT() *MockDiffSyncerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Init mocks base method.
|
||||
func (m *MockDiffSyncer) Init(arg0 settingsstate.ObjectDeletionState) {
|
||||
// Close mocks base method.
|
||||
func (m *MockDiffSyncer) Close() error {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Init", arg0)
|
||||
ret := m.ctrl.Call(m, "Close")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockDiffSyncerMockRecorder) Close() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDiffSyncer)(nil).Close))
|
||||
}
|
||||
|
||||
// Init mocks base method.
|
||||
func (m *MockDiffSyncer) Init() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Init")
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init.
|
||||
func (mr *MockDiffSyncerMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockDiffSyncerMockRecorder) Init() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init))
|
||||
}
|
||||
|
||||
// RemoveObjects mocks base method.
|
||||
|
||||
@@ -2,8 +2,8 @@ package headsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
|
||||
@@ -3,8 +3,8 @@ package headsync
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package headsync
|
||||
|
||||
import "strings"
|
||||
|
||||
func concatStrings(strs []string) string {
|
||||
var (
|
||||
b strings.Builder
|
||||
totalLen int
|
||||
)
|
||||
for _, s := range strs {
|
||||
totalLen += len(s)
|
||||
}
|
||||
|
||||
b.Grow(totalLen)
|
||||
for _, s := range strs {
|
||||
b.WriteString(s)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func splitString(str string) (res []string) {
|
||||
const cidLen = 59
|
||||
for i := 0; i < len(str); i += cidLen {
|
||||
res = append(res, str[i:i+cidLen])
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package accountdata
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type AccountKeys struct {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/util/cidutil"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -3,9 +3,9 @@ package list
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type aclStateBuilder struct {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
//go:generate mockgen -destination mock_list/mock_list.go github.com/anytypeio/any-sync/commonspace/object/acl/list AclList
|
||||
//go:generate mockgen -destination mock_list/mock_list.go github.com/anyproto/any-sync/commonspace/object/acl/list AclList
|
||||
package list
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package list
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
func NewTestDerivedAcl(spaceId string, keys *accountdata.AccountKeys) (AclList, error) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/acl/list (interfaces: AclList)
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/acl/list (interfaces: AclList)
|
||||
|
||||
// Package mock_list is a generated GoMock package.
|
||||
package mock_list
|
||||
@@ -7,9 +7,9 @@ package mock_list
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
aclrecordproto "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
list "github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
crypto "github.com/anytypeio/any-sync/util/crypto"
|
||||
aclrecordproto "github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
crypto "github.com/anyproto/any-sync/util/crypto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type AclRecord struct {
|
||||
|
||||
@@ -3,7 +3,7 @@ package liststorage
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//go:generate mockgen -destination mock_liststorage/mock_liststorage.go github.com/anytypeio/any-sync/commonspace/object/acl/liststorage ListStorage
|
||||
//go:generate mockgen -destination mock_liststorage/mock_liststorage.go github.com/anyproto/any-sync/commonspace/object/acl/liststorage ListStorage
|
||||
package liststorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/acl/liststorage (interfaces: ListStorage)
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/acl/liststorage (interfaces: ListStorage)
|
||||
|
||||
// Package mock_liststorage is a generated GoMock package.
|
||||
package mock_liststorage
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
aclrecordproto "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
aclrecordproto "github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,21 +1,43 @@
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/accountservice"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
)
|
||||
|
||||
const CName = "common.acl.syncacl"
|
||||
|
||||
func New() *SyncAcl {
|
||||
return &SyncAcl{}
|
||||
}
|
||||
|
||||
type SyncAcl struct {
|
||||
list.AclList
|
||||
synchandler.SyncHandler
|
||||
messagePool objectsync.MessagePool
|
||||
}
|
||||
|
||||
func NewSyncAcl(aclList list.AclList, messagePool objectsync.MessagePool) *SyncAcl {
|
||||
return &SyncAcl{
|
||||
AclList: aclList,
|
||||
SyncHandler: nil,
|
||||
messagePool: messagePool,
|
||||
}
|
||||
func (s *SyncAcl) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *SyncAcl) HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SyncAcl) Init(a *app.App) (err error) {
|
||||
storage := a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
||||
aclStorage, err := storage.AclStorage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acc := a.MustComponent(accountservice.CName).(accountservice.Service)
|
||||
s.AclList, err = list.BuildAclListWithIdentity(acc.Account(), aclStorage)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SyncAcl) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
@@ -3,9 +3,9 @@ package syncacl
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
)
|
||||
|
||||
type syncAclHandler struct {
|
||||
|
||||
@@ -2,7 +2,7 @@ package syncobjectgetter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
)
|
||||
|
||||
type SyncObject interface {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package exporter
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type DataConverter interface {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package exporter
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
)
|
||||
|
||||
type TreeImportParams struct {
|
||||
|
||||
@@ -2,8 +2,8 @@ package objecttree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ package objecttree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/util/cidutil"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,34 +14,47 @@ type historyTree struct {
|
||||
*objectTree
|
||||
}
|
||||
|
||||
func (h *historyTree) rebuildFromStorage(beforeId string, include bool) (err error) {
|
||||
ot := h.objectTree
|
||||
ot.treeBuilder.Reset()
|
||||
if beforeId == ot.Id() && !include {
|
||||
func (h *historyTree) rebuildFromStorage(params HistoryTreeParams) (err error) {
|
||||
err = h.rebuild(params)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h.aclList.RLock()
|
||||
defer h.aclList.RUnlock()
|
||||
state := h.aclList.AclState()
|
||||
|
||||
return h.readKeysFromAclState(state)
|
||||
}
|
||||
|
||||
func (h *historyTree) rebuild(params HistoryTreeParams) (err error) {
|
||||
var (
|
||||
beforeId = params.BeforeId
|
||||
include = params.IncludeBeforeId
|
||||
full = params.BuildFullTree
|
||||
)
|
||||
h.treeBuilder.Reset()
|
||||
if full {
|
||||
h.tree, err = h.treeBuilder.BuildFull()
|
||||
return
|
||||
}
|
||||
if beforeId == h.Id() && !include {
|
||||
return ErrLoadBeforeRoot
|
||||
}
|
||||
|
||||
heads := []string{beforeId}
|
||||
if beforeId == "" {
|
||||
heads, err = ot.treeStorage.Heads()
|
||||
heads, err = h.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if !include {
|
||||
beforeChange, err := ot.treeBuilder.loadChange(beforeId)
|
||||
beforeChange, err := h.treeBuilder.loadChange(beforeId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
heads = beforeChange.PreviousIds
|
||||
}
|
||||
|
||||
ot.tree, err = ot.treeBuilder.build(heads, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ot.aclList.RLock()
|
||||
defer ot.aclList.RUnlock()
|
||||
state := ot.aclList.AclState()
|
||||
|
||||
return ot.readKeysFromAclState(state)
|
||||
h.tree, err = h.treeBuilder.build(heads, nil, nil)
|
||||
return
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/objecttree (interfaces: ObjectTree)
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/objecttree (interfaces: ObjectTree)
|
||||
|
||||
// Package mock_objecttree is a generated GoMock package.
|
||||
package mock_objecttree
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
list "github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
objecttree "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
treechangeproto "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
treestorage "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anytypeio/any-sync/commonspace/object/tree/objecttree ObjectTree
|
||||
//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anyproto/any-sync/commonspace/object/tree/objecttree ObjectTree
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
)
|
||||
|
||||
type RWLocker interface {
|
||||
@@ -116,13 +116,25 @@ type objectTree struct {
|
||||
}
|
||||
|
||||
func (ot *objectTree) rebuildFromStorage(theirHeads []string, newChanges []*Change) (err error) {
|
||||
oldTree := ot.tree
|
||||
ot.treeBuilder.Reset()
|
||||
|
||||
ot.tree, err = ot.treeBuilder.Build(theirHeads, newChanges)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// in case there are new heads
|
||||
if theirHeads != nil && oldTree != nil {
|
||||
// checking that old root is still in tree
|
||||
rootCh, rootExists := ot.tree.attached[oldTree.RootId()]
|
||||
|
||||
// checking the case where theirHeads were actually below prevHeads
|
||||
// so if we did load some extra data in the tree, let's reduce it to old root
|
||||
if slice.UnsortedEquals(oldTree.headIds, ot.tree.headIds) && rootExists && ot.tree.RootId() != oldTree.RootId() {
|
||||
ot.tree.makeRootAndRemove(rootCh)
|
||||
}
|
||||
}
|
||||
|
||||
// during building the tree we may have marked some changes as possible roots,
|
||||
// but obviously they are not roots, because of the way how we construct the tree
|
||||
ot.tree.clearPossibleRoots()
|
||||
@@ -187,7 +199,7 @@ func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeCont
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ot.treeStorage.TransactionAdd([]*treechangeproto.RawTreeChangeWithId{rawChange}, []string{objChange.Id})
|
||||
err = ot.treeStorage.AddRawChangesSetHeads([]*treechangeproto.RawTreeChangeWithId{rawChange}, []string{objChange.Id})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -284,7 +296,11 @@ func (ot *objectTree) AddRawChanges(ctx context.Context, changesPayload RawChang
|
||||
addResult.Mode = Rebuild
|
||||
}
|
||||
|
||||
err = ot.treeStorage.TransactionAdd(addResult.Added, addResult.Heads)
|
||||
err = ot.treeStorage.AddRawChangesSetHeads(addResult.Added, addResult.Heads)
|
||||
if err != nil {
|
||||
// rolling back all changes made to inmemory state
|
||||
ot.rebuildFromStorage(nil, nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -347,7 +363,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
|
||||
}
|
||||
|
||||
// checks if we need to go to database
|
||||
isOldSnapshot := func(ch *Change) bool {
|
||||
snapshotNotInTree := func(ch *Change) bool {
|
||||
if ch.SnapshotId == ot.tree.RootId() {
|
||||
return false
|
||||
}
|
||||
@@ -362,26 +378,12 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
|
||||
|
||||
shouldRebuildFromStorage := false
|
||||
// checking if we have some changes with different snapshot and then rebuilding
|
||||
for idx, ch := range ot.newChangesBuf {
|
||||
if isOldSnapshot(ch) {
|
||||
var exists bool
|
||||
// checking if it exists in the storage, if yes, then at some point it was added to the tree
|
||||
// thus we don't need to look at this change
|
||||
exists, err = ot.treeStorage.HasChange(ctx, ch.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if exists {
|
||||
// marking as nil to delete after
|
||||
ot.newChangesBuf[idx] = nil
|
||||
continue
|
||||
}
|
||||
// we haven't seen the change, and it refers to old snapshot, so we should rebuild
|
||||
for _, ch := range ot.newChangesBuf {
|
||||
if snapshotNotInTree(ch) {
|
||||
shouldRebuildFromStorage = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// discarding all previously seen changes
|
||||
ot.newChangesBuf = slice.DiscardFromSlice(ot.newChangesBuf, func(ch *Change) bool { return ch == nil })
|
||||
|
||||
if shouldRebuildFromStorage {
|
||||
err = ot.rebuildFromStorage(changesPayload.NewHeads, ot.newChangesBuf)
|
||||
@@ -549,22 +551,8 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate
|
||||
}
|
||||
|
||||
func (ot *objectTree) HasChanges(chs ...string) bool {
|
||||
hasChange := func(s string) bool {
|
||||
_, attachedExists := ot.tree.attached[s]
|
||||
if attachedExists {
|
||||
return attachedExists
|
||||
}
|
||||
|
||||
has, err := ot.treeStorage.HasChange(context.Background(), s)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return has
|
||||
}
|
||||
|
||||
for _, ch := range chs {
|
||||
if !hasChange(ch) {
|
||||
if _, attachedExists := ot.tree.attached[ch]; !attachedExists {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ package objecttree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -64,7 +65,7 @@ func prepareContext(
|
||||
treeStorage := changeCreator.CreateNewTreeStorage("0", aclList.Head().Id)
|
||||
if additionalChanges != nil {
|
||||
payload := additionalChanges(changeCreator)
|
||||
err := treeStorage.TransactionAdd(payload.RawChanges, payload.NewHeads)
|
||||
err := treeStorage.AddRawChangesSetHeads(payload.RawChanges, payload.NewHeads)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
objTree, err := objTreeBuilder(treeStorage, aclList)
|
||||
@@ -421,6 +422,99 @@ func TestObjectTree(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("rollback when add to storage returns error", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
store := ctx.treeStorage.(*treestorage.InMemoryTreeStorage)
|
||||
addErr := fmt.Errorf("error saving")
|
||||
store.SetReturnErrorOnAdd(addErr)
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
|
||||
}
|
||||
payload := RawChangesPayload{
|
||||
NewHeads: []string{"1"},
|
||||
RawChanges: rawChanges,
|
||||
}
|
||||
_, err := objTree.AddRawChanges(context.Background(), payload)
|
||||
require.Error(t, err, addErr)
|
||||
require.Equal(t, "0", objTree.Root().Id)
|
||||
})
|
||||
|
||||
t.Run("their heads before common snapshot", func(t *testing.T) {
|
||||
// checking that adding old changes did not affect the tree
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "1", false, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "1", true, "2"),
|
||||
changeCreator.CreateRaw("4", aclList.Head().Id, "1", false, "2"),
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "1", false, "1"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "1", true, "3", "4", "5"),
|
||||
}
|
||||
payload := RawChangesPayload{
|
||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||
RawChanges: rawChanges,
|
||||
}
|
||||
_, err := objTree.AddRawChanges(context.Background(), payload)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "6", objTree.Root().Id)
|
||||
|
||||
rawChangesPrevious := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
|
||||
}
|
||||
payload = RawChangesPayload{
|
||||
NewHeads: []string{"1"},
|
||||
RawChanges: rawChangesPrevious,
|
||||
}
|
||||
_, err = objTree.AddRawChanges(context.Background(), payload)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "6", objTree.Root().Id)
|
||||
})
|
||||
|
||||
t.Run("stored changes will not break the pipeline if heads were not updated", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
store := ctx.treeStorage.(*treestorage.InMemoryTreeStorage)
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
|
||||
}
|
||||
payload := RawChangesPayload{
|
||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||
RawChanges: rawChanges,
|
||||
}
|
||||
_, err := objTree.AddRawChanges(context.Background(), payload)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "1", objTree.Root().Id)
|
||||
|
||||
// creating changes to save in the storage
|
||||
// to imitate the condition where all changes are in the storage
|
||||
// but the head was not updated
|
||||
storageChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "1", false, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "1", true, "2"),
|
||||
changeCreator.CreateRaw("4", aclList.Head().Id, "1", false, "2"),
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "1", false, "1"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "1", true, "3", "4", "5"),
|
||||
}
|
||||
store.AddRawChangesSetHeads(storageChanges, []string{"1"})
|
||||
|
||||
// updating with subset of those changes to see that everything will still work
|
||||
payload = RawChangesPayload{
|
||||
NewHeads: []string{"6"},
|
||||
RawChanges: storageChanges,
|
||||
}
|
||||
_, err = objTree.AddRawChanges(context.Background(), payload)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "6", objTree.Root().Id)
|
||||
})
|
||||
|
||||
t.Run("changes from tree after common snapshot complex", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
@@ -654,7 +748,7 @@ func TestObjectTree(t *testing.T) {
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
}
|
||||
deps.treeStorage.TransactionAdd(rawChanges, []string{"6"})
|
||||
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
|
||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||
BeforeId: "6",
|
||||
IncludeBeforeId: false,
|
||||
@@ -674,6 +768,37 @@ func TestObjectTree(t *testing.T) {
|
||||
assert.Equal(t, "0", hTree.Root().Id)
|
||||
})
|
||||
|
||||
t.Run("test history tree build full", func(t *testing.T) {
|
||||
changeCreator, deps := prepareHistoryTreeDeps(aclList)
|
||||
|
||||
// sequence of snapshots: 5->1->0
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", true, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "1", false, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "1", true, "2"),
|
||||
changeCreator.CreateRaw("4", aclList.Head().Id, "1", false, "2"),
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "1", true, "3", "4"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "5", false, "5"),
|
||||
}
|
||||
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
|
||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||
BuildFullTree: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// check tree heads
|
||||
assert.Equal(t, []string{"6"}, hTree.Heads())
|
||||
|
||||
// check tree iterate
|
||||
var iterChangesId []string
|
||||
err = hTree.IterateFrom(hTree.Root().Id, nil, func(change *Change) bool {
|
||||
iterChangesId = append(iterChangesId, change.Id)
|
||||
return true
|
||||
})
|
||||
require.NoError(t, err, "iterate should be without error")
|
||||
assert.Equal(t, []string{"0", "1", "2", "3", "4", "5", "6"}, iterChangesId)
|
||||
assert.Equal(t, "0", hTree.Root().Id)
|
||||
})
|
||||
|
||||
t.Run("test history tree include", func(t *testing.T) {
|
||||
changeCreator, deps := prepareHistoryTreeDeps(aclList)
|
||||
|
||||
@@ -685,7 +810,7 @@ func TestObjectTree(t *testing.T) {
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
}
|
||||
deps.treeStorage.TransactionAdd(rawChanges, []string{"6"})
|
||||
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
|
||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||
BeforeId: "6",
|
||||
IncludeBeforeId: true,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type ObjectTreeCreatePayload struct {
|
||||
@@ -22,6 +22,7 @@ type HistoryTreeParams struct {
|
||||
AclList list.AclList
|
||||
BeforeId string
|
||||
IncludeBeforeId bool
|
||||
BuildFullTree bool
|
||||
}
|
||||
|
||||
type objectTreeDeps struct {
|
||||
@@ -230,7 +231,7 @@ func buildHistoryTree(deps objectTreeDeps, params HistoryTreeParams) (ht History
|
||||
}
|
||||
|
||||
hTree := &historyTree{objectTree: objTree}
|
||||
err = hTree.rebuildFromStorage(params.BeforeId, params.IncludeBeforeId)
|
||||
err = hTree.rebuildFromStorage(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package objecttree
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
)
|
||||
|
||||
type ObjectTreeValidator interface {
|
||||
|
||||
@@ -2,9 +2,9 @@ package objecttree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
// SignableChangeContent is a payload to be passed when we are creating change
|
||||
|
||||
@@ -2,9 +2,9 @@ package objecttree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
libcrypto "github.com/libp2p/go-libp2p/core/crypto"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -51,6 +51,22 @@ func (tb *treeBuilder) Build(theirHeads []string, newChanges []*Change) (*Tree,
|
||||
return tb.build(heads, theirHeads, newChanges)
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) BuildFull() (*Tree, error) {
|
||||
defer func() {
|
||||
tb.cache = make(map[string]*Change)
|
||||
}()
|
||||
tb.cache = make(map[string]*Change)
|
||||
heads, err := tb.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tb.buildTree(heads, tb.treeStorage.Id())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tb.tree, nil
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) build(heads []string, theirHeads []string, newChanges []*Change) (*Tree, error) {
|
||||
defer func() {
|
||||
tb.cache = make(map[string]*Change)
|
||||
@@ -105,8 +121,8 @@ func (tb *treeBuilder) buildTree(heads []string, breakpoint string) (err error)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tb.tree.AddFast(ch)
|
||||
changes := tb.dfs(heads, breakpoint)
|
||||
tb.tree.AddFast(ch)
|
||||
tb.tree.AddFast(changes...)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package objecttree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/synctree (interfaces: SyncTree,ReceiveQueue,HeadNotifiable)
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/synctree (interfaces: SyncTree,ReceiveQueue,HeadNotifiable,SyncClient,RequestFactory,TreeSyncProtocol)
|
||||
|
||||
// Package mock_synctree is a generated GoMock package.
|
||||
package mock_synctree
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
list "github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
objecttree "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
updatelistener "github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
treechangeproto "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
treestorage "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
spacesyncproto "github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
list "github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
updatelistener "github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
@@ -186,6 +186,21 @@ func (mr *MockSyncTreeMockRecorder) HandleMessage(arg0, arg1, arg2 interface{})
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncTree)(nil).HandleMessage), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// HandleRequest mocks base method.
|
||||
func (m *MockSyncTree) HandleRequest(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HandleRequest", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// HandleRequest indicates an expected call of HandleRequest.
|
||||
func (mr *MockSyncTreeMockRecorder) HandleRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleRequest", reflect.TypeOf((*MockSyncTree)(nil).HandleRequest), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// HasChanges mocks base method.
|
||||
func (m *MockSyncTree) HasChanges(arg0 ...string) bool {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -590,3 +605,287 @@ func (mr *MockHeadNotifiableMockRecorder) UpdateHeads(arg0, arg1 interface{}) *g
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateHeads", reflect.TypeOf((*MockHeadNotifiable)(nil).UpdateHeads), arg0, arg1)
|
||||
}
|
||||
|
||||
// MockSyncClient is a mock of SyncClient interface.
|
||||
type MockSyncClient struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockSyncClientMockRecorder
|
||||
}
|
||||
|
||||
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
|
||||
type MockSyncClientMockRecorder struct {
|
||||
mock *MockSyncClient
|
||||
}
|
||||
|
||||
// NewMockSyncClient creates a new mock instance.
|
||||
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
|
||||
mock := &MockSyncClient{ctrl: ctrl}
|
||||
mock.recorder = &MockSyncClientMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Broadcast mocks base method.
|
||||
func (m *MockSyncClient) Broadcast(arg0 *treechangeproto.TreeSyncMessage) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Broadcast", arg0)
|
||||
}
|
||||
|
||||
// Broadcast indicates an expected call of Broadcast.
|
||||
func (mr *MockSyncClientMockRecorder) Broadcast(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0)
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest mocks base method.
|
||||
func (m *MockSyncClient) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse mocks base method.
|
||||
func (m *MockSyncClient) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// CreateHeadUpdate mocks base method.
|
||||
func (m *MockSyncClient) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
||||
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateNewTreeRequest mocks base method.
|
||||
func (m *MockSyncClient) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
|
||||
func (mr *MockSyncClientMockRecorder) CreateNewTreeRequest() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateNewTreeRequest))
|
||||
}
|
||||
|
||||
// QueueRequest mocks base method.
|
||||
func (m *MockSyncClient) QueueRequest(arg0, arg1 string, arg2 *treechangeproto.TreeSyncMessage) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "QueueRequest", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// QueueRequest indicates an expected call of QueueRequest.
|
||||
func (mr *MockSyncClientMockRecorder) QueueRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueRequest", reflect.TypeOf((*MockSyncClient)(nil).QueueRequest), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// SendRequest mocks base method.
|
||||
func (m *MockSyncClient) SendRequest(arg0 context.Context, arg1, arg2 string, arg3 *treechangeproto.TreeSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SendRequest", arg0, arg1, arg2, arg3)
|
||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SendRequest indicates an expected call of SendRequest.
|
||||
func (mr *MockSyncClientMockRecorder) SendRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRequest", reflect.TypeOf((*MockSyncClient)(nil).SendRequest), arg0, arg1, arg2, arg3)
|
||||
}
|
||||
|
||||
// SendUpdate mocks base method.
|
||||
func (m *MockSyncClient) SendUpdate(arg0, arg1 string, arg2 *treechangeproto.TreeSyncMessage) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SendUpdate", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SendUpdate indicates an expected call of SendUpdate.
|
||||
func (mr *MockSyncClientMockRecorder) SendUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendUpdate", reflect.TypeOf((*MockSyncClient)(nil).SendUpdate), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// MockRequestFactory is a mock of RequestFactory interface.
|
||||
type MockRequestFactory struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockRequestFactoryMockRecorder
|
||||
}
|
||||
|
||||
// MockRequestFactoryMockRecorder is the mock recorder for MockRequestFactory.
|
||||
type MockRequestFactoryMockRecorder struct {
|
||||
mock *MockRequestFactory
|
||||
}
|
||||
|
||||
// NewMockRequestFactory creates a new mock instance.
|
||||
func NewMockRequestFactory(ctrl *gomock.Controller) *MockRequestFactory {
|
||||
mock := &MockRequestFactory{ctrl: ctrl}
|
||||
mock.recorder = &MockRequestFactoryMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockRequestFactory) EXPECT() *MockRequestFactoryMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest mocks base method.
|
||||
func (m *MockRequestFactory) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse mocks base method.
|
||||
func (m *MockRequestFactory) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
||||
func (mr *MockRequestFactoryMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockRequestFactory)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// CreateHeadUpdate mocks base method.
|
||||
func (m *MockRequestFactory) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
||||
func (mr *MockRequestFactoryMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockRequestFactory)(nil).CreateHeadUpdate), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateNewTreeRequest mocks base method.
|
||||
func (m *MockRequestFactory) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
|
||||
func (mr *MockRequestFactoryMockRecorder) CreateNewTreeRequest() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockRequestFactory)(nil).CreateNewTreeRequest))
|
||||
}
|
||||
|
||||
// MockTreeSyncProtocol is a mock of TreeSyncProtocol interface.
|
||||
type MockTreeSyncProtocol struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockTreeSyncProtocolMockRecorder
|
||||
}
|
||||
|
||||
// MockTreeSyncProtocolMockRecorder is the mock recorder for MockTreeSyncProtocol.
|
||||
type MockTreeSyncProtocolMockRecorder struct {
|
||||
mock *MockTreeSyncProtocol
|
||||
}
|
||||
|
||||
// NewMockTreeSyncProtocol creates a new mock instance.
|
||||
func NewMockTreeSyncProtocol(ctrl *gomock.Controller) *MockTreeSyncProtocol {
|
||||
mock := &MockTreeSyncProtocol{ctrl: ctrl}
|
||||
mock.recorder = &MockTreeSyncProtocolMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockTreeSyncProtocol) EXPECT() *MockTreeSyncProtocolMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// FullSyncRequest mocks base method.
|
||||
func (m *MockTreeSyncProtocol) FullSyncRequest(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeFullSyncRequest) (*treechangeproto.TreeSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FullSyncRequest", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FullSyncRequest indicates an expected call of FullSyncRequest.
|
||||
func (mr *MockTreeSyncProtocolMockRecorder) FullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncRequest", reflect.TypeOf((*MockTreeSyncProtocol)(nil).FullSyncRequest), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// FullSyncResponse mocks base method.
|
||||
func (m *MockTreeSyncProtocol) FullSyncResponse(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeFullSyncResponse) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FullSyncResponse", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// FullSyncResponse indicates an expected call of FullSyncResponse.
|
||||
func (mr *MockTreeSyncProtocolMockRecorder) FullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullSyncResponse", reflect.TypeOf((*MockTreeSyncProtocol)(nil).FullSyncResponse), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// HeadUpdate mocks base method.
|
||||
func (m *MockTreeSyncProtocol) HeadUpdate(arg0 context.Context, arg1 string, arg2 *treechangeproto.TreeHeadUpdate) (*treechangeproto.TreeSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HeadUpdate", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// HeadUpdate indicates an expected call of HeadUpdate.
|
||||
func (mr *MockTreeSyncProtocolMockRecorder) HeadUpdate(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadUpdate", reflect.TypeOf((*MockTreeSyncProtocol)(nil).HeadUpdate), arg0, arg1, arg2)
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEmptyClientGetsFullHistory(t *testing.T) {
|
||||
treeId := "treeId"
|
||||
spaceId := "spaceId"
|
||||
keys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
||||
require.NoError(t, err)
|
||||
storage := createStorage(treeId, aclList)
|
||||
changeCreator := objecttree.NewMockChangeCreator()
|
||||
deps := fixtureDeps{
|
||||
aclList: aclList,
|
||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
||||
connectionMap: map[string][]string{
|
||||
"peer1": []string{"peer2"},
|
||||
"peer2": []string{"peer1"},
|
||||
},
|
||||
emptyTrees: []string{"peer2"},
|
||||
treeBuilder: objecttree.BuildTestableTree,
|
||||
}
|
||||
fx := newProtocolFixture(t, spaceId, deps)
|
||||
fx.run(t)
|
||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: nil,
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Id(), treeId, true, treeId),
|
||||
},
|
||||
})
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fx.stop()
|
||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
require.Equal(t, []string{"1"}, firstHeads)
|
||||
logMsgs := fx.log.batcher.GetAll()
|
||||
|
||||
var fullResponseMsg msgDescription
|
||||
for _, msg := range logMsgs {
|
||||
descr := msg.description()
|
||||
if descr.name == "FullSyncResponse" {
|
||||
fullResponseMsg = descr
|
||||
}
|
||||
}
|
||||
// that means that we got not only the last snapshot, but also the first one
|
||||
require.Len(t, fullResponseMsg.changes, 2)
|
||||
}
|
||||
|
||||
func TestTreeFuzzyMerge(t *testing.T) {
|
||||
testTreeFuzzyMerge(t, true)
|
||||
testTreeFuzzyMerge(t, false)
|
||||
}
|
||||
|
||||
func testTreeFuzzyMerge(t *testing.T, withData bool) {
|
||||
var (
|
||||
rnd = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
levels = 20
|
||||
perLevel = 20
|
||||
rounds = 10
|
||||
)
|
||||
for i := 0; i < rounds; i++ {
|
||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
||||
return true
|
||||
})
|
||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
||||
return false
|
||||
})
|
||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
||||
return rnd.Intn(10) > 8
|
||||
})
|
||||
levels += 2
|
||||
}
|
||||
}
|
||||
|
||||
func testTreeMerge(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
|
||||
treeId := "treeId"
|
||||
spaceId := "spaceId"
|
||||
keys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
||||
storage := createStorage(treeId, aclList)
|
||||
changeCreator := objecttree.NewMockChangeCreator()
|
||||
builder := objecttree.BuildTestableTree
|
||||
if hasData {
|
||||
builder = objecttree.BuildEmptyDataTestableTree
|
||||
}
|
||||
params := genParams{
|
||||
prefix: "peer1",
|
||||
aclId: aclList.Id(),
|
||||
startIdx: 0,
|
||||
levels: levels,
|
||||
perLevel: perLevel,
|
||||
snapshotId: treeId,
|
||||
prevHeads: []string{treeId},
|
||||
isSnapshot: isSnapshot,
|
||||
hasData: hasData,
|
||||
}
|
||||
// generating initial tree
|
||||
initialRes := genChanges(changeCreator, params)
|
||||
err = storage.AddRawChangesSetHeads(initialRes.changes, initialRes.heads)
|
||||
require.NoError(t, err)
|
||||
deps := fixtureDeps{
|
||||
aclList: aclList,
|
||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
||||
connectionMap: map[string][]string{
|
||||
"peer1": []string{"node1"},
|
||||
"peer2": []string{"node1"},
|
||||
"node1": []string{"peer1", "peer2"},
|
||||
},
|
||||
emptyTrees: []string{"peer2", "node1"},
|
||||
treeBuilder: builder,
|
||||
}
|
||||
fx := newProtocolFixture(t, spaceId, deps)
|
||||
fx.run(t)
|
||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: initialRes.heads,
|
||||
RawChanges: initialRes.changes,
|
||||
})
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
params = genParams{
|
||||
prefix: "peer1",
|
||||
aclId: aclList.Id(),
|
||||
startIdx: levels,
|
||||
levels: levels,
|
||||
perLevel: perLevel,
|
||||
|
||||
snapshotId: initialRes.snapshotId,
|
||||
prevHeads: initialRes.heads,
|
||||
isSnapshot: isSnapshot,
|
||||
hasData: hasData,
|
||||
}
|
||||
// generating different additions to the tree for different peers
|
||||
peer1Res := genChanges(changeCreator, params)
|
||||
params.prefix = "peer2"
|
||||
peer2Res := genChanges(changeCreator, params)
|
||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: peer1Res.heads,
|
||||
RawChanges: peer1Res.changes,
|
||||
})
|
||||
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: peer2Res.heads,
|
||||
RawChanges: peer2Res.changes,
|
||||
})
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
fx.stop()
|
||||
firstTree := fx.handlers["peer1"].tree()
|
||||
secondTree := fx.handlers["peer2"].tree()
|
||||
firstHeads = firstTree.Heads()
|
||||
secondHeads = secondTree.Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, append(peer1Res.heads, peer2Res.heads...)))
|
||||
firstStorage := firstTree.Storage().(*treestorage.InMemoryTreeStorage)
|
||||
secondStorage := secondTree.Storage().(*treestorage.InMemoryTreeStorage)
|
||||
require.True(t, firstStorage.Equal(secondStorage))
|
||||
if hasData {
|
||||
for _, ch := range secondStorage.Changes {
|
||||
if ch.Id == treeId {
|
||||
continue
|
||||
}
|
||||
unmarshallRaw := &treechangeproto.RawTreeChange{}
|
||||
proto.Unmarshal(ch.RawChange, unmarshallRaw)
|
||||
treeCh := &treechangeproto.TreeChange{}
|
||||
proto.Unmarshal(unmarshallRaw.Payload, treeCh)
|
||||
require.Equal(t, ch.Id, string(treeCh.ChangesData))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeStorageHasExtraChanges(t *testing.T) {
|
||||
var (
|
||||
rnd = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
levels = 20
|
||||
perLevel = 40
|
||||
)
|
||||
|
||||
// simulating cases where one peer has some extra changes saved in storage
|
||||
// and checking that this will not break the sync
|
||||
t.Run("tree storage has extra simple", func(t *testing.T) {
|
||||
testTreeStorageHasExtra(t, levels, perLevel, false, func() bool {
|
||||
return false
|
||||
})
|
||||
testTreeStorageHasExtra(t, levels, perLevel, false, func() bool {
|
||||
return rnd.Intn(10) > 5
|
||||
})
|
||||
testTreeStorageHasExtra(t, levels, perLevel, true, func() bool {
|
||||
return false
|
||||
})
|
||||
testTreeStorageHasExtra(t, levels, perLevel, true, func() bool {
|
||||
return rnd.Intn(10) > 5
|
||||
})
|
||||
})
|
||||
t.Run("tree storage has extra three parts", func(t *testing.T) {
|
||||
testTreeStorageHasExtraThreeParts(t, levels, perLevel, false, func() bool {
|
||||
return false
|
||||
})
|
||||
testTreeStorageHasExtraThreeParts(t, levels, perLevel, false, func() bool {
|
||||
return rnd.Intn(10) > 5
|
||||
})
|
||||
testTreeStorageHasExtraThreeParts(t, levels, perLevel, true, func() bool {
|
||||
return false
|
||||
})
|
||||
testTreeStorageHasExtraThreeParts(t, levels, perLevel, true, func() bool {
|
||||
return rnd.Intn(10) > 5
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testTreeStorageHasExtra(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
|
||||
treeId := "treeId"
|
||||
spaceId := "spaceId"
|
||||
keys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
||||
storage := createStorage(treeId, aclList)
|
||||
changeCreator := objecttree.NewMockChangeCreator()
|
||||
builder := objecttree.BuildTestableTree
|
||||
if hasData {
|
||||
builder = objecttree.BuildEmptyDataTestableTree
|
||||
}
|
||||
params := genParams{
|
||||
prefix: "peer1",
|
||||
aclId: aclList.Id(),
|
||||
startIdx: 0,
|
||||
levels: levels,
|
||||
perLevel: perLevel,
|
||||
snapshotId: treeId,
|
||||
prevHeads: []string{treeId},
|
||||
isSnapshot: isSnapshot,
|
||||
hasData: hasData,
|
||||
}
|
||||
deps := fixtureDeps{
|
||||
aclList: aclList,
|
||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
||||
connectionMap: map[string][]string{
|
||||
"peer1": []string{"peer2"},
|
||||
"peer2": []string{"peer1"},
|
||||
},
|
||||
treeBuilder: builder,
|
||||
}
|
||||
fx := newProtocolFixture(t, spaceId, deps)
|
||||
|
||||
// generating initial tree
|
||||
initialRes := genChanges(changeCreator, params)
|
||||
fx.run(t)
|
||||
|
||||
// adding some changes to store, but without updating heads
|
||||
store := fx.handlers["peer1"].tree().Storage().(*treestorage.InMemoryTreeStorage)
|
||||
oldHeads, _ := store.Heads()
|
||||
store.AddRawChangesSetHeads(initialRes.changes, oldHeads)
|
||||
|
||||
// sending those changes to other peer
|
||||
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: initialRes.heads,
|
||||
RawChanges: initialRes.changes,
|
||||
})
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// here we want that the saved changes in storage should not affect the sync protocol
|
||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
require.True(t, slice.UnsortedEquals(initialRes.heads, firstHeads))
|
||||
}
|
||||
|
||||
func testTreeStorageHasExtraThreeParts(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
|
||||
treeId := "treeId"
|
||||
spaceId := "spaceId"
|
||||
keys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
||||
storage := createStorage(treeId, aclList)
|
||||
changeCreator := objecttree.NewMockChangeCreator()
|
||||
builder := objecttree.BuildTestableTree
|
||||
if hasData {
|
||||
builder = objecttree.BuildEmptyDataTestableTree
|
||||
}
|
||||
params := genParams{
|
||||
prefix: "peer1",
|
||||
aclId: aclList.Id(),
|
||||
startIdx: 0,
|
||||
levels: levels,
|
||||
perLevel: perLevel,
|
||||
snapshotId: treeId,
|
||||
prevHeads: []string{treeId},
|
||||
isSnapshot: isSnapshot,
|
||||
hasData: hasData,
|
||||
}
|
||||
deps := fixtureDeps{
|
||||
aclList: aclList,
|
||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
||||
connectionMap: map[string][]string{
|
||||
"peer1": []string{"peer2"},
|
||||
"peer2": []string{"peer1"},
|
||||
},
|
||||
treeBuilder: builder,
|
||||
}
|
||||
fx := newProtocolFixture(t, spaceId, deps)
|
||||
|
||||
// generating parts
|
||||
firstPart := genChanges(changeCreator, params)
|
||||
params.startIdx = levels
|
||||
params.snapshotId = firstPart.snapshotId
|
||||
params.prevHeads = firstPart.heads
|
||||
secondPart := genChanges(changeCreator, params)
|
||||
params.startIdx = levels * 2
|
||||
params.snapshotId = secondPart.snapshotId
|
||||
params.prevHeads = secondPart.heads
|
||||
thirdPart := genChanges(changeCreator, params)
|
||||
|
||||
// adding part1 to first peer and saving part2 and part3 in its storage
|
||||
res, _ := fx.handlers["peer1"].tree().AddRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: firstPart.heads,
|
||||
RawChanges: firstPart.changes,
|
||||
})
|
||||
require.True(t, slice.UnsortedEquals(res.Heads, firstPart.heads))
|
||||
store := fx.handlers["peer1"].tree().Storage().(*treestorage.InMemoryTreeStorage)
|
||||
oldHeads, _ := store.Heads()
|
||||
store.AddRawChangesSetHeads(secondPart.changes, oldHeads)
|
||||
store.AddRawChangesSetHeads(thirdPart.changes, oldHeads)
|
||||
|
||||
var peer2Initial []*treechangeproto.RawTreeChangeWithId
|
||||
peer2Initial = append(peer2Initial, firstPart.changes...)
|
||||
peer2Initial = append(peer2Initial, secondPart.changes...)
|
||||
|
||||
// adding part1 and part2 to second peer
|
||||
res, _ = fx.handlers["peer2"].tree().AddRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: secondPart.heads,
|
||||
RawChanges: peer2Initial,
|
||||
})
|
||||
require.True(t, slice.UnsortedEquals(res.Heads, secondPart.heads))
|
||||
fx.run(t)
|
||||
|
||||
// sending part3 changes to other peer
|
||||
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: thirdPart.heads,
|
||||
RawChanges: thirdPart.changes,
|
||||
})
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
require.True(t, slice.UnsortedEquals(thirdPart.heads, firstHeads))
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package synctree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
package objectsync
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
)
|
||||
|
||||
type RequestFactory interface {
|
||||
@@ -0,0 +1,82 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type SyncClient interface {
|
||||
RequestFactory
|
||||
Broadcast(msg *treechangeproto.TreeSyncMessage)
|
||||
SendUpdate(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error)
|
||||
QueueRequest(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error)
|
||||
SendRequest(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
||||
}
|
||||
|
||||
type syncClient struct {
|
||||
RequestFactory
|
||||
spaceId string
|
||||
requestManager requestmanager.RequestManager
|
||||
peerManager peermanager.PeerManager
|
||||
}
|
||||
|
||||
func NewSyncClient(spaceId string, requestManager requestmanager.RequestManager, peerManager peermanager.PeerManager) SyncClient {
|
||||
return &syncClient{
|
||||
RequestFactory: &requestFactory{},
|
||||
spaceId: spaceId,
|
||||
requestManager: requestManager,
|
||||
peerManager: peerManager,
|
||||
}
|
||||
}
|
||||
func (s *syncClient) Broadcast(msg *treechangeproto.TreeSyncMessage) {
|
||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, msg.RootChange.Id, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = s.peerManager.Broadcast(context.Background(), objMsg)
|
||||
if err != nil {
|
||||
log.Debug("broadcast error", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncClient) SendUpdate(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
|
||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.peerManager.SendPeer(context.Background(), peerId, objMsg)
|
||||
}
|
||||
|
||||
func (s *syncClient) SendRequest(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.requestManager.SendRequest(ctx, peerId, objMsg)
|
||||
}
|
||||
|
||||
func (s *syncClient) QueueRequest(peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (err error) {
|
||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.requestManager.QueueRequest(peerId, objMsg)
|
||||
}
|
||||
|
||||
func MarshallTreeMessage(message *treechangeproto.TreeSyncMessage, spaceId, objectId, replyId string) (objMsg *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
payload, err := message.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
objMsg = &spacesyncproto.ObjectSyncMessage{
|
||||
ReplyId: replyId,
|
||||
Payload: payload,
|
||||
ObjectId: objectId,
|
||||
SpaceId: spaceId,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEmptyClientGetsFullHistory(t *testing.T) {
|
||||
treeId := "treeId"
|
||||
spaceId := "spaceId"
|
||||
keys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
||||
require.NoError(t, err)
|
||||
storage := createStorage(treeId, aclList)
|
||||
changeCreator := objecttree.NewMockChangeCreator()
|
||||
deps := fixtureDeps{
|
||||
aclList: aclList,
|
||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
||||
connectionMap: map[string][]string{
|
||||
"peer1": []string{"peer2"},
|
||||
"peer2": []string{"peer1"},
|
||||
},
|
||||
emptyTrees: []string{"peer2"},
|
||||
treeBuilder: objecttree.BuildTestableTree,
|
||||
}
|
||||
fx := newProtocolFixture(t, spaceId, deps)
|
||||
fx.run(t)
|
||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: nil,
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Id(), treeId, true, treeId),
|
||||
},
|
||||
})
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fx.stop()
|
||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
require.Equal(t, []string{"1"}, firstHeads)
|
||||
logMsgs := fx.log.batcher.GetAll()
|
||||
|
||||
var fullResponseMsg msgDescription
|
||||
for _, msg := range logMsgs {
|
||||
descr := msg.description()
|
||||
if descr.name == "FullSyncResponse" {
|
||||
fullResponseMsg = descr
|
||||
}
|
||||
}
|
||||
// that means that we got not only the last snapshot, but also the first one
|
||||
require.Len(t, fullResponseMsg.changes, 2)
|
||||
}
|
||||
|
||||
func TestTreeFuzzyMerge(t *testing.T) {
|
||||
testTreeFuzzyMerge(t, true)
|
||||
testTreeFuzzyMerge(t, false)
|
||||
}
|
||||
|
||||
func testTreeFuzzyMerge(t *testing.T, withData bool) {
|
||||
var (
|
||||
rnd = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
levels = 20
|
||||
perLevel = 20
|
||||
rounds = 10
|
||||
)
|
||||
for i := 0; i < rounds; i++ {
|
||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
||||
return true
|
||||
})
|
||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
||||
return false
|
||||
})
|
||||
testTreeMerge(t, levels, perLevel, withData, func() bool {
|
||||
return rnd.Intn(10) > 8
|
||||
})
|
||||
levels += 2
|
||||
}
|
||||
}
|
||||
|
||||
func testTreeMerge(t *testing.T, levels, perLevel int, hasData bool, isSnapshot func() bool) {
|
||||
treeId := "treeId"
|
||||
spaceId := "spaceId"
|
||||
keys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl(spaceId, keys)
|
||||
storage := createStorage(treeId, aclList)
|
||||
changeCreator := objecttree.NewMockChangeCreator()
|
||||
builder := objecttree.BuildTestableTree
|
||||
if hasData {
|
||||
builder = objecttree.BuildEmptyDataTestableTree
|
||||
}
|
||||
params := genParams{
|
||||
prefix: "peer1",
|
||||
aclId: aclList.Id(),
|
||||
startIdx: 0,
|
||||
levels: levels,
|
||||
perLevel: perLevel,
|
||||
snapshotId: treeId,
|
||||
prevHeads: []string{treeId},
|
||||
isSnapshot: isSnapshot,
|
||||
hasData: hasData,
|
||||
}
|
||||
// generating initial tree
|
||||
initialRes := genChanges(changeCreator, params)
|
||||
err = storage.TransactionAdd(initialRes.changes, initialRes.heads)
|
||||
require.NoError(t, err)
|
||||
deps := fixtureDeps{
|
||||
aclList: aclList,
|
||||
initStorage: storage.(*treestorage.InMemoryTreeStorage),
|
||||
connectionMap: map[string][]string{
|
||||
"peer1": []string{"node1"},
|
||||
"peer2": []string{"node1"},
|
||||
"node1": []string{"peer1", "peer2"},
|
||||
},
|
||||
emptyTrees: []string{"peer2", "node1"},
|
||||
treeBuilder: builder,
|
||||
}
|
||||
fx := newProtocolFixture(t, spaceId, deps)
|
||||
fx.run(t)
|
||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: initialRes.heads,
|
||||
RawChanges: initialRes.changes,
|
||||
})
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
firstHeads := fx.handlers["peer1"].tree().Heads()
|
||||
secondHeads := fx.handlers["peer2"].tree().Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
params = genParams{
|
||||
prefix: "peer1",
|
||||
aclId: aclList.Id(),
|
||||
startIdx: levels,
|
||||
levels: levels,
|
||||
perLevel: perLevel,
|
||||
|
||||
snapshotId: initialRes.snapshotId,
|
||||
prevHeads: initialRes.heads,
|
||||
isSnapshot: isSnapshot,
|
||||
hasData: hasData,
|
||||
}
|
||||
// generating different additions to the tree for different peers
|
||||
peer1Res := genChanges(changeCreator, params)
|
||||
params.prefix = "peer2"
|
||||
peer2Res := genChanges(changeCreator, params)
|
||||
fx.handlers["peer1"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: peer1Res.heads,
|
||||
RawChanges: peer1Res.changes,
|
||||
})
|
||||
fx.handlers["peer2"].sendRawChanges(context.Background(), objecttree.RawChangesPayload{
|
||||
NewHeads: peer2Res.heads,
|
||||
RawChanges: peer2Res.changes,
|
||||
})
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
fx.stop()
|
||||
firstTree := fx.handlers["peer1"].tree()
|
||||
secondTree := fx.handlers["peer2"].tree()
|
||||
firstHeads = firstTree.Heads()
|
||||
secondHeads = secondTree.Heads()
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, secondHeads))
|
||||
require.True(t, slice.UnsortedEquals(firstHeads, append(peer1Res.heads, peer2Res.heads...)))
|
||||
firstStorage := firstTree.Storage().(*treestorage.InMemoryTreeStorage)
|
||||
secondStorage := secondTree.Storage().(*treestorage.InMemoryTreeStorage)
|
||||
require.True(t, firstStorage.Equal(secondStorage))
|
||||
if hasData {
|
||||
for _, ch := range secondStorage.Changes {
|
||||
if ch.Id == treeId {
|
||||
continue
|
||||
}
|
||||
unmarshallRaw := &treechangeproto.RawTreeChange{}
|
||||
proto.Unmarshal(ch.RawChange, unmarshallRaw)
|
||||
treeCh := &treechangeproto.TreeChange{}
|
||||
proto.Unmarshal(unmarshallRaw.Payload, treeCh)
|
||||
require.Equal(t, ch.Id, string(treeCh.ChangesData))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anytypeio/any-sync/commonspace/object/tree/synctree SyncTree,ReceiveQueue,HeadNotifiable
|
||||
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anyproto/any-sync/commonspace/object/tree/synctree SyncTree,ReceiveQueue,HeadNotifiable,SyncClient,RequestFactory,TreeSyncProtocol
|
||||
package synctree
|
||||
|
||||
import (
|
||||
@@ -6,17 +6,16 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/net/peer"
|
||||
"github.com/anytypeio/any-sync/nodeconf"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -44,7 +43,7 @@ type SyncTree interface {
|
||||
type syncTree struct {
|
||||
objecttree.ObjectTree
|
||||
synchandler.SyncHandler
|
||||
syncClient objectsync.SyncClient
|
||||
syncClient SyncClient
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
notifiable HeadNotifiable
|
||||
listener updatelistener.UpdateListener
|
||||
@@ -61,7 +60,7 @@ type ResponsiblePeersGetter interface {
|
||||
|
||||
type BuildDeps struct {
|
||||
SpaceId string
|
||||
SyncClient objectsync.SyncClient
|
||||
SyncClient SyncClient
|
||||
Configuration nodeconf.NodeConf
|
||||
HeadNotifiable HeadNotifiable
|
||||
Listener updatelistener.UpdateListener
|
||||
@@ -76,12 +75,15 @@ type BuildDeps struct {
|
||||
}
|
||||
|
||||
func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t SyncTree, err error) {
|
||||
remoteGetter := treeRemoteGetter{treeId: id, deps: deps}
|
||||
deps.TreeStorage, err = remoteGetter.getTree(ctx)
|
||||
var (
|
||||
remoteGetter = treeRemoteGetter{treeId: id, deps: deps}
|
||||
isRemote bool
|
||||
)
|
||||
deps.TreeStorage, isRemote, err = remoteGetter.getTree(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return buildSyncTree(ctx, true, deps)
|
||||
return buildSyncTree(ctx, isRemote, deps)
|
||||
}
|
||||
|
||||
func PutSyncTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, deps BuildDeps) (t SyncTree, err error) {
|
||||
@@ -92,7 +94,7 @@ func PutSyncTree(ctx context.Context, payload treestorage.TreeStorageCreatePaylo
|
||||
return buildSyncTree(ctx, true, deps)
|
||||
}
|
||||
|
||||
func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t SyncTree, err error) {
|
||||
func buildSyncTree(ctx context.Context, sendUpdate bool, deps BuildDeps) (t SyncTree, err error) {
|
||||
objTree, err := deps.BuildObjectTree(deps.TreeStorage, deps.AclList)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -113,10 +115,10 @@ func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t Sy
|
||||
syncTree.afterBuild()
|
||||
syncTree.Unlock()
|
||||
|
||||
if isFirstBuild {
|
||||
if sendUpdate {
|
||||
headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil)
|
||||
// send to everybody, because everybody should know that the node or client got new tree
|
||||
syncTree.syncClient.Broadcast(ctx, headUpdate)
|
||||
syncTree.syncClient.Broadcast(headUpdate)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -153,7 +155,7 @@ func (s *syncTree) AddContent(ctx context.Context, content objecttree.SignableCh
|
||||
}
|
||||
s.syncStatus.HeadsChange(s.Id(), res.Heads)
|
||||
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
||||
s.syncClient.Broadcast(ctx, headUpdate)
|
||||
s.syncClient.Broadcast(headUpdate)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -180,7 +182,7 @@ func (s *syncTree) AddRawChanges(ctx context.Context, changesPayload objecttree.
|
||||
s.notifiable.UpdateHeads(s.Id(), res.Heads)
|
||||
}
|
||||
headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added)
|
||||
s.syncClient.Broadcast(ctx, headUpdate)
|
||||
s.syncClient.Broadcast(headUpdate)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -236,7 +238,7 @@ func (s *syncTree) SyncWithPeer(ctx context.Context, peerId string) (err error)
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
headUpdate := s.syncClient.CreateHeadUpdate(s, nil)
|
||||
return s.syncClient.SendWithReply(ctx, peerId, headUpdate.RootChange.Id, headUpdate, "")
|
||||
return s.syncClient.SendUpdate(peerId, headUpdate.RootChange.Id, headUpdate)
|
||||
}
|
||||
|
||||
func (s *syncTree) afterBuild() {
|
||||
|
||||
@@ -2,15 +2,15 @@ package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener/mock_updatelistener"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/mock_objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/nodeconf"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener/mock_updatelistener"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
type syncTreeMatcher struct {
|
||||
objTree objecttree.ObjectTree
|
||||
client objectsync.SyncClient
|
||||
client SyncClient
|
||||
listener updatelistener.UpdateListener
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ func (s syncTreeMatcher) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func syncClientFuncCreator(client objectsync.SyncClient) func(spaceId string, factory objectsync.RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) objectsync.SyncClient {
|
||||
return func(spaceId string, factory objectsync.RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) objectsync.SyncClient {
|
||||
func syncClientFuncCreator(client SyncClient) func(spaceId string, factory RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) SyncClient {
|
||||
return func(spaceId string, factory RequestFactory, objectSync objectsync.ObjectSync, configuration nodeconf.NodeConf) SyncClient {
|
||||
return client
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
||||
defer ctrl.Finish()
|
||||
|
||||
updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl)
|
||||
syncClientMock := mock_objectsync.NewMockSyncClient(ctrl)
|
||||
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
|
||||
objTreeMock := newTestObjMock(mock_objecttree.NewMockObjectTree(ctrl))
|
||||
tr := &syncTree{
|
||||
ObjectTree: objTreeMock,
|
||||
@@ -73,7 +73,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
||||
updateListenerMock.EXPECT().Update(tr)
|
||||
|
||||
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
||||
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate))
|
||||
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
|
||||
res, err := tr.AddRawChanges(ctx, payload)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedRes, res)
|
||||
@@ -95,7 +95,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
||||
updateListenerMock.EXPECT().Rebuild(tr)
|
||||
|
||||
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
||||
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate))
|
||||
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
|
||||
res, err := tr.AddRawChanges(ctx, payload)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedRes, res)
|
||||
@@ -133,7 +133,7 @@ func Test_BuildSyncTree(t *testing.T) {
|
||||
Return(expectedRes, nil)
|
||||
|
||||
syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate)
|
||||
syncClientMock.EXPECT().Broadcast(gomock.Any(), gomock.Eq(headUpdate))
|
||||
syncClientMock.EXPECT().Broadcast(gomock.Eq(headUpdate))
|
||||
res, err := tr.AddContent(ctx, content)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedRes, res)
|
||||
|
||||
@@ -2,39 +2,66 @@ package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMessageIsRequest = errors.New("message is request")
|
||||
ErrMessageIsNotRequest = errors.New("message is not request")
|
||||
)
|
||||
|
||||
type syncTreeHandler struct {
|
||||
objTree objecttree.ObjectTree
|
||||
syncClient objectsync.SyncClient
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
handlerLock sync.Mutex
|
||||
spaceId string
|
||||
queue ReceiveQueue
|
||||
objTree objecttree.ObjectTree
|
||||
syncClient SyncClient
|
||||
syncProtocol TreeSyncProtocol
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
handlerLock sync.Mutex
|
||||
spaceId string
|
||||
queue ReceiveQueue
|
||||
}
|
||||
|
||||
const maxQueueSize = 5
|
||||
|
||||
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient objectsync.SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
||||
func newSyncTreeHandler(spaceId string, objTree objecttree.ObjectTree, syncClient SyncClient, syncStatus syncstatus.StatusUpdater) synchandler.SyncHandler {
|
||||
return &syncTreeHandler{
|
||||
objTree: objTree,
|
||||
syncClient: syncClient,
|
||||
syncStatus: syncStatus,
|
||||
spaceId: spaceId,
|
||||
queue: newReceiveQueue(maxQueueSize),
|
||||
objTree: objTree,
|
||||
syncProtocol: newTreeSyncProtocol(spaceId, objTree, syncClient),
|
||||
syncClient: syncClient,
|
||||
syncStatus: syncStatus,
|
||||
spaceId: spaceId,
|
||||
queue: newReceiveQueue(maxQueueSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
||||
err = proto.Unmarshal(request.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fullSyncRequest := unmarshalled.GetContent().GetFullSyncRequest()
|
||||
if fullSyncRequest == nil {
|
||||
err = ErrMessageIsNotRequest
|
||||
return
|
||||
}
|
||||
s.syncStatus.HeadsReceive(senderId, request.ObjectId, treechangeproto.GetHeads(unmarshalled))
|
||||
s.objTree.Lock()
|
||||
defer s.objTree.Unlock()
|
||||
treeResp, err := s.syncProtocol.FullSyncRequest(ctx, senderId, fullSyncRequest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
response, err = MarshallTreeMessage(treeResp, s.spaceId, request.ObjectId, "")
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) HandleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
||||
err = proto.Unmarshal(msg.Payload, unmarshalled)
|
||||
@@ -54,181 +81,27 @@ func (s *syncTreeHandler) HandleMessage(ctx context.Context, senderId string, ms
|
||||
func (s *syncTreeHandler) handleMessage(ctx context.Context, senderId string) (err error) {
|
||||
s.objTree.Lock()
|
||||
defer s.objTree.Unlock()
|
||||
msg, replyId, err := s.queue.GetMessage(senderId)
|
||||
msg, _, err := s.queue.GetMessage(senderId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer s.queue.ClearQueue(senderId)
|
||||
|
||||
treeId := s.objTree.Id()
|
||||
content := msg.GetContent()
|
||||
switch {
|
||||
case content.GetHeadUpdate() != nil:
|
||||
return s.handleHeadUpdate(ctx, senderId, content.GetHeadUpdate(), replyId)
|
||||
var syncReq *treechangeproto.TreeSyncMessage
|
||||
syncReq, err = s.syncProtocol.HeadUpdate(ctx, senderId, content.GetHeadUpdate())
|
||||
if err != nil || syncReq == nil {
|
||||
return
|
||||
}
|
||||
return s.syncClient.QueueRequest(senderId, treeId, syncReq)
|
||||
case content.GetFullSyncRequest() != nil:
|
||||
return s.handleFullSyncRequest(ctx, senderId, content.GetFullSyncRequest(), replyId)
|
||||
return ErrMessageIsRequest
|
||||
case content.GetFullSyncResponse() != nil:
|
||||
return s.handleFullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
|
||||
return s.syncProtocol.FullSyncResponse(ctx, senderId, content.GetFullSyncResponse())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) handleHeadUpdate(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
update *treechangeproto.TreeHeadUpdate,
|
||||
replyId string) (err error) {
|
||||
var (
|
||||
fullRequest *treechangeproto.TreeSyncMessage
|
||||
isEmptyUpdate = len(update.Changes) == 0
|
||||
objTree = s.objTree
|
||||
treeId = objTree.Id()
|
||||
)
|
||||
log := log.With(
|
||||
zap.Strings("update heads", update.Heads),
|
||||
zap.String("treeId", treeId),
|
||||
zap.String("spaceId", s.spaceId),
|
||||
zap.Int("len(update changes)", len(update.Changes)))
|
||||
log.DebugCtx(ctx, "received head update message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).Debug("head update finished with error")
|
||||
} else if fullRequest != nil {
|
||||
cnt := fullRequest.Content.GetFullSyncRequest()
|
||||
log = log.With(zap.Strings("request heads", cnt.Heads), zap.Int("len(request changes)", len(cnt.Changes)))
|
||||
log.DebugCtx(ctx, "sending full sync request")
|
||||
} else {
|
||||
if !isEmptyUpdate {
|
||||
log.DebugCtx(ctx, "head update finished correctly")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// isEmptyUpdate is sent when the tree is brought up from cache
|
||||
if isEmptyUpdate {
|
||||
headEquals := slice.UnsortedEquals(objTree.Heads(), update.Heads)
|
||||
log.DebugCtx(ctx, "is empty update", zap.String("treeId", objTree.Id()), zap.Bool("headEquals", headEquals))
|
||||
if headEquals {
|
||||
return
|
||||
}
|
||||
|
||||
// we need to sync in any case
|
||||
fullRequest, err = s.syncClient.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullRequest, replyId)
|
||||
}
|
||||
|
||||
if s.alreadyHasHeads(objTree, update.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: update.Heads,
|
||||
RawChanges: update.Changes,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.alreadyHasHeads(objTree, update.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
fullRequest, err = s.syncClient.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullRequest, replyId)
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) handleFullSyncRequest(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
request *treechangeproto.TreeFullSyncRequest,
|
||||
replyId string) (err error) {
|
||||
var (
|
||||
fullResponse *treechangeproto.TreeSyncMessage
|
||||
header = s.objTree.Header()
|
||||
objTree = s.objTree
|
||||
treeId = s.objTree.Id()
|
||||
)
|
||||
|
||||
log := log.With(zap.String("senderId", senderId),
|
||||
zap.Strings("request heads", request.Heads),
|
||||
zap.String("treeId", treeId),
|
||||
zap.String("replyId", replyId),
|
||||
zap.String("spaceId", s.spaceId),
|
||||
zap.Int("len(request changes)", len(request.Changes)))
|
||||
log.DebugCtx(ctx, "received full sync request message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).DebugCtx(ctx, "full sync request finished with error")
|
||||
s.syncClient.SendWithReply(ctx, senderId, treeId, treechangeproto.WrapError(treechangeproto.ErrFullSync, header), replyId)
|
||||
return
|
||||
} else if fullResponse != nil {
|
||||
cnt := fullResponse.Content.GetFullSyncResponse()
|
||||
log = log.With(zap.Strings("response heads", cnt.Heads), zap.Int("len(response changes)", len(cnt.Changes)))
|
||||
log.DebugCtx(ctx, "full sync response sent")
|
||||
}
|
||||
}()
|
||||
|
||||
if len(request.Changes) != 0 && !s.alreadyHasHeads(objTree, request.Heads) {
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: request.Heads,
|
||||
RawChanges: request.Changes,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
fullResponse, err = s.syncClient.CreateFullSyncResponse(objTree, request.Heads, request.SnapshotPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return s.syncClient.SendWithReply(ctx, senderId, treeId, fullResponse, replyId)
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) handleFullSyncResponse(
|
||||
ctx context.Context,
|
||||
senderId string,
|
||||
response *treechangeproto.TreeFullSyncResponse) (err error) {
|
||||
var (
|
||||
objTree = s.objTree
|
||||
treeId = s.objTree.Id()
|
||||
)
|
||||
log := log.With(
|
||||
zap.Strings("heads", response.Heads),
|
||||
zap.String("treeId", treeId),
|
||||
zap.String("spaceId", s.spaceId),
|
||||
zap.Int("len(changes)", len(response.Changes)))
|
||||
log.DebugCtx(ctx, "received full sync response message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.With(zap.Error(err)).DebugCtx(ctx, "full sync response failed")
|
||||
} else {
|
||||
log.DebugCtx(ctx, "full sync response succeeded")
|
||||
}
|
||||
}()
|
||||
|
||||
if s.alreadyHasHeads(objTree, response.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: response.Heads,
|
||||
RawChanges: response.Changes,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *syncTreeHandler) alreadyHasHeads(t objecttree.ObjectTree, heads []string) bool {
|
||||
return slice.UnsortedEquals(t.Heads(), heads) || t.HasChanges(heads...)
|
||||
}
|
||||
@@ -2,20 +2,15 @@ package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/mock_objectsync"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/stretchr/testify/require"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type testObjTreeMock struct {
|
||||
@@ -55,31 +50,43 @@ func (t *testObjTreeMock) TryRLock() bool {
|
||||
|
||||
type syncHandlerFixture struct {
|
||||
ctrl *gomock.Controller
|
||||
syncClientMock *mock_objectsync.MockSyncClient
|
||||
syncClientMock *mock_synctree.MockSyncClient
|
||||
objectTreeMock *testObjTreeMock
|
||||
receiveQueueMock ReceiveQueue
|
||||
syncProtocolMock *mock_synctree.MockTreeSyncProtocol
|
||||
spaceId string
|
||||
senderId string
|
||||
treeId string
|
||||
|
||||
syncHandler *syncTreeHandler
|
||||
}
|
||||
|
||||
func newSyncHandlerFixture(t *testing.T) *syncHandlerFixture {
|
||||
ctrl := gomock.NewController(t)
|
||||
syncClientMock := mock_objectsync.NewMockSyncClient(ctrl)
|
||||
objectTreeMock := newTestObjMock(mock_objecttree.NewMockObjectTree(ctrl))
|
||||
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
|
||||
syncProtocolMock := mock_synctree.NewMockTreeSyncProtocol(ctrl)
|
||||
spaceId := "spaceId"
|
||||
receiveQueue := newReceiveQueue(5)
|
||||
|
||||
syncHandler := &syncTreeHandler{
|
||||
objTree: objectTreeMock,
|
||||
syncClient: syncClientMock,
|
||||
queue: receiveQueue,
|
||||
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||
objTree: objectTreeMock,
|
||||
syncClient: syncClientMock,
|
||||
syncProtocol: syncProtocolMock,
|
||||
spaceId: spaceId,
|
||||
queue: receiveQueue,
|
||||
syncStatus: syncstatus.NewNoOpSyncStatus(),
|
||||
}
|
||||
return &syncHandlerFixture{
|
||||
ctrl: ctrl,
|
||||
syncClientMock: syncClientMock,
|
||||
objectTreeMock: objectTreeMock,
|
||||
receiveQueueMock: receiveQueue,
|
||||
syncProtocolMock: syncProtocolMock,
|
||||
syncClientMock: syncClientMock,
|
||||
syncHandler: syncHandler,
|
||||
spaceId: spaceId,
|
||||
senderId: "senderId",
|
||||
treeId: "treeId",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,341 +94,128 @@ func (fx *syncHandlerFixture) stop() {
|
||||
fx.ctrl.Finish()
|
||||
}
|
||||
|
||||
func TestSyncHandler_HandleHeadUpdate(t *testing.T) {
|
||||
func TestSyncTreeHandler_HandleMessage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
log = logger.CtxLogger{Logger: zap.NewNop()}
|
||||
fullRequest := &treechangeproto.TreeSyncMessage{
|
||||
Content: &treechangeproto.TreeSyncContentValue{
|
||||
Value: &treechangeproto.TreeSyncContentValue_FullSyncRequest{
|
||||
FullSyncRequest: &treechangeproto.TreeFullSyncRequest{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("head update non empty all heads added", func(t *testing.T) {
|
||||
t.Run("handle head update message", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
objectMsg, _ := MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).Times(2)
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(true)
|
||||
syncReq := &treechangeproto.TreeSyncMessage{}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(syncReq, nil)
|
||||
fx.syncClientMock.EXPECT().QueueRequest(fx.senderId, fx.treeId, syncReq).Return(nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("head update non empty heads not added", func(t *testing.T) {
|
||||
t.Run("handle head update message, empty sync request", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
objectMsg, _ := MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.syncClientMock.EXPECT().
|
||||
CreateFullSyncRequest(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullRequest, nil)
|
||||
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullRequest), gomock.Eq(""))
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.syncProtocolMock.EXPECT().HeadUpdate(ctx, fx.senderId, gomock.Any()).Return(nil, nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("head update non empty equal heads", func(t *testing.T) {
|
||||
t.Run("handle full sync request returns error", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
fullRequest := &treechangeproto.TreeFullSyncRequest{}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullRequest, chWithId)
|
||||
objectMsg, _ := MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.Equal(t, err, ErrMessageIsRequest)
|
||||
})
|
||||
|
||||
t.Run("head update empty", func(t *testing.T) {
|
||||
t.Run("handle full sync response", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: nil,
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||
fx.syncClientMock.EXPECT().
|
||||
CreateFullSyncRequest(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullRequest, nil)
|
||||
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullRequest), gomock.Eq(""))
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("head update empty equal heads", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: nil,
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSyncHandler_HandleFullSyncRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
log = logger.CtxLogger{Logger: zap.NewNop()}
|
||||
fullResponse := &treechangeproto.TreeSyncMessage{
|
||||
Content: &treechangeproto.TreeSyncContentValue{
|
||||
Value: &treechangeproto.TreeSyncContentValue_FullSyncResponse{
|
||||
FullSyncResponse: &treechangeproto.TreeFullSyncResponse{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("full sync request with change", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
fx.syncClientMock.EXPECT().
|
||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullResponse, nil)
|
||||
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullResponse), gomock.Eq(""))
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("full sync request with change same heads", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h1"}).AnyTimes()
|
||||
fx.syncClientMock.EXPECT().
|
||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullResponse, nil)
|
||||
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullResponse), gomock.Eq(""))
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("full sync request without change but with reply id", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
replyId := "replyId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
objectMsg.RequestId = replyId
|
||||
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||
fx.syncClientMock.EXPECT().
|
||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullResponse, nil)
|
||||
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Eq(fullResponse), gomock.Eq(replyId))
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("full sync request with add raw changes error", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullSyncRequest, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().Header().Return(nil)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h2"})
|
||||
fx.objectTreeMock.EXPECT().
|
||||
HasChanges(gomock.Eq([]string{"h1"})).
|
||||
Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, fmt.Errorf(""))
|
||||
fx.syncClientMock.EXPECT().SendWithReply(gomock.Any(), gomock.Eq(senderId), gomock.Eq(treeId), gomock.Any(), gomock.Eq(""))
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSyncHandler_HandleFullSyncResponse(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
log = logger.CtxLogger{Logger: zap.NewNop()}
|
||||
|
||||
t.Run("full sync response with change", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
replyId := "replyId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{}
|
||||
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, replyId)
|
||||
objectMsg, _ := MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h2"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().
|
||||
HasChanges(gomock.Eq([]string{"h1"})).
|
||||
Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.syncProtocolMock.EXPECT().FullSyncResponse(ctx, fx.senderId, gomock.Any()).Return(nil)
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("full sync response with same heads", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
senderId := "senderId"
|
||||
replyId := "replyId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
treeMsg := treechangeproto.WrapFullResponse(fullSyncResponse, chWithId)
|
||||
objectMsg, _ := objectsync.MarshallTreeMessage(treeMsg, "spaceId", treeId, replyId)
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(treeId)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h1"}).AnyTimes()
|
||||
|
||||
err := fx.syncHandler.HandleMessage(ctx, senderId, objectMsg)
|
||||
err := fx.syncHandler.HandleMessage(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSyncTreeHandler_HandleRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("handle request", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullRequest := &treechangeproto.TreeFullSyncRequest{}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullRequest, chWithId)
|
||||
objectMsg, _ := MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
syncResp := &treechangeproto.TreeSyncMessage{}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.syncProtocolMock.EXPECT().FullSyncRequest(ctx, fx.senderId, gomock.Any()).Return(syncResp, nil)
|
||||
|
||||
res, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
})
|
||||
|
||||
t.Run("handle request", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullRequest := &treechangeproto.TreeFullSyncRequest{}
|
||||
treeMsg := treechangeproto.WrapFullRequest(fullRequest, chWithId)
|
||||
objectMsg, _ := MarshallTreeMessage(treeMsg, "spaceId", treeId, "")
|
||||
|
||||
syncResp := &treechangeproto.TreeSyncMessage{}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.syncProtocolMock.EXPECT().FullSyncRequest(ctx, fx.senderId, gomock.Any()).Return(syncResp, nil)
|
||||
|
||||
res, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
})
|
||||
|
||||
t.Run("handle other message", func(t *testing.T) {
|
||||
fx := newSyncHandlerFixture(t)
|
||||
defer fx.stop()
|
||||
treeId := "treeId"
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullResponse := &treechangeproto.TreeFullSyncResponse{}
|
||||
responseMsg := treechangeproto.WrapFullResponse(fullResponse, chWithId)
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{}
|
||||
headUpdateMsg := treechangeproto.WrapHeadUpdate(headUpdate, chWithId)
|
||||
for _, msg := range []*treechangeproto.TreeSyncMessage{responseMsg, headUpdateMsg} {
|
||||
objectMsg, _ := MarshallTreeMessage(msg, "spaceId", treeId, "")
|
||||
|
||||
_, err := fx.syncHandler.HandleRequest(ctx, fx.senderId, objectMsg)
|
||||
require.Equal(t, err, ErrMessageIsNotRequest)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -3,12 +3,12 @@ package synctree
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/net/peer"
|
||||
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
@@ -47,7 +47,7 @@ func (t treeRemoteGetter) getPeers(ctx context.Context) (peerIds []string, err e
|
||||
|
||||
func (t treeRemoteGetter) treeRequest(ctx context.Context, peerId string) (msg *treechangeproto.TreeSyncMessage, err error) {
|
||||
newTreeRequest := t.deps.SyncClient.CreateNewTreeRequest()
|
||||
resp, err := t.deps.SyncClient.SendSync(ctx, peerId, t.treeId, newTreeRequest)
|
||||
resp, err := t.deps.SyncClient.SendRequest(ctx, peerId, t.treeId, newTreeRequest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -90,12 +90,11 @@ Loop:
|
||||
}
|
||||
}
|
||||
|
||||
func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.TreeStorage, err error) {
|
||||
func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.TreeStorage, isRemote bool, err error) {
|
||||
treeStorage, err = t.deps.SpaceStorage.TreeStorage(t.treeId)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil && err != treestorage.ErrUnknownTreeId {
|
||||
return
|
||||
}
|
||||
@@ -109,6 +108,7 @@ func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.
|
||||
return
|
||||
}
|
||||
|
||||
isRemote = true
|
||||
resp, err := t.treeRequestLoop(ctx, t.deps.WaitTreeRemoteSync)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -139,5 +139,6 @@ func (t treeRemoteGetter) getTree(ctx context.Context) (treeStorage treestorage.
|
||||
return
|
||||
}
|
||||
// now we are sure that we can save it to the storage
|
||||
return t.deps.SpaceStorage.CreateTreeStorage(payload)
|
||||
treeStorage, err = t.deps.SpaceStorage.CreateTreeStorage(payload)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type TreeSyncProtocol interface {
|
||||
HeadUpdate(ctx context.Context, senderId string, update *treechangeproto.TreeHeadUpdate) (request *treechangeproto.TreeSyncMessage, err error)
|
||||
FullSyncRequest(ctx context.Context, senderId string, request *treechangeproto.TreeFullSyncRequest) (response *treechangeproto.TreeSyncMessage, err error)
|
||||
FullSyncResponse(ctx context.Context, senderId string, response *treechangeproto.TreeFullSyncResponse) (err error)
|
||||
}
|
||||
|
||||
type treeSyncProtocol struct {
|
||||
log logger.CtxLogger
|
||||
spaceId string
|
||||
objTree objecttree.ObjectTree
|
||||
reqFactory RequestFactory
|
||||
}
|
||||
|
||||
func newTreeSyncProtocol(spaceId string, objTree objecttree.ObjectTree, reqFactory RequestFactory) *treeSyncProtocol {
|
||||
return &treeSyncProtocol{
|
||||
log: log.With(zap.String("spaceId", spaceId), zap.String("treeId", objTree.Id())),
|
||||
spaceId: spaceId,
|
||||
objTree: objTree,
|
||||
reqFactory: reqFactory,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *treeSyncProtocol) HeadUpdate(ctx context.Context, senderId string, update *treechangeproto.TreeHeadUpdate) (fullRequest *treechangeproto.TreeSyncMessage, err error) {
|
||||
var (
|
||||
isEmptyUpdate = len(update.Changes) == 0
|
||||
objTree = t.objTree
|
||||
)
|
||||
log := t.log.With(
|
||||
zap.String("senderId", senderId),
|
||||
zap.Strings("update heads", update.Heads),
|
||||
zap.Int("len(update changes)", len(update.Changes)))
|
||||
log.DebugCtx(ctx, "received head update message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ErrorCtx(ctx, "head update finished with error", zap.Error(err))
|
||||
} else if fullRequest != nil {
|
||||
cnt := fullRequest.Content.GetFullSyncRequest()
|
||||
log = log.With(zap.Strings("request heads", cnt.Heads), zap.Int("len(request changes)", len(cnt.Changes)))
|
||||
log.DebugCtx(ctx, "returning full sync request")
|
||||
} else {
|
||||
if !isEmptyUpdate {
|
||||
log.DebugCtx(ctx, "head update finished correctly")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// isEmptyUpdate is sent when the tree is brought up from cache
|
||||
if isEmptyUpdate {
|
||||
headEquals := slice.UnsortedEquals(objTree.Heads(), update.Heads)
|
||||
log.DebugCtx(ctx, "is empty update", zap.String("treeId", objTree.Id()), zap.Bool("headEquals", headEquals))
|
||||
if headEquals {
|
||||
return
|
||||
}
|
||||
|
||||
// we need to sync in any case
|
||||
fullRequest, err = t.reqFactory.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
||||
return
|
||||
}
|
||||
|
||||
if t.alreadyHasHeads(objTree, update.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: update.Heads,
|
||||
RawChanges: update.Changes,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if t.alreadyHasHeads(objTree, update.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
fullRequest, err = t.reqFactory.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeSyncProtocol) FullSyncRequest(ctx context.Context, senderId string, request *treechangeproto.TreeFullSyncRequest) (fullResponse *treechangeproto.TreeSyncMessage, err error) {
|
||||
var (
|
||||
objTree = t.objTree
|
||||
)
|
||||
log := t.log.With(zap.String("senderId", senderId),
|
||||
zap.Strings("request heads", request.Heads),
|
||||
zap.Int("len(request changes)", len(request.Changes)))
|
||||
log.DebugCtx(ctx, "received full sync request message")
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ErrorCtx(ctx, "full sync request finished with error", zap.Error(err))
|
||||
} else if fullResponse != nil {
|
||||
cnt := fullResponse.Content.GetFullSyncResponse()
|
||||
log = log.With(zap.Strings("response heads", cnt.Heads), zap.Int("len(response changes)", len(cnt.Changes)))
|
||||
log.DebugCtx(ctx, "full sync response sent")
|
||||
}
|
||||
}()
|
||||
|
||||
if len(request.Changes) != 0 && !t.alreadyHasHeads(objTree, request.Heads) {
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: request.Heads,
|
||||
RawChanges: request.Changes,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
fullResponse, err = t.reqFactory.CreateFullSyncResponse(objTree, request.Heads, request.SnapshotPath)
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeSyncProtocol) FullSyncResponse(ctx context.Context, senderId string, response *treechangeproto.TreeFullSyncResponse) (err error) {
|
||||
var (
|
||||
objTree = t.objTree
|
||||
)
|
||||
log := log.With(
|
||||
zap.Strings("heads", response.Heads),
|
||||
zap.Int("len(changes)", len(response.Changes)))
|
||||
log.DebugCtx(ctx, "received full sync response message")
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ErrorCtx(ctx, "full sync response failed", zap.Error(err))
|
||||
} else {
|
||||
log.DebugCtx(ctx, "full sync response succeeded")
|
||||
}
|
||||
}()
|
||||
if t.alreadyHasHeads(objTree, response.Heads) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = objTree.AddRawChanges(ctx, objecttree.RawChangesPayload{
|
||||
NewHeads: response.Heads,
|
||||
RawChanges: response.Changes,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeSyncProtocol) alreadyHasHeads(ot objecttree.ObjectTree, heads []string) bool {
|
||||
return slice.UnsortedEquals(ot.Heads(), heads) || ot.HasChanges(heads...)
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree/mock_objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/mock_synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type treeSyncProtocolFixture struct {
|
||||
log logger.CtxLogger
|
||||
spaceId string
|
||||
senderId string
|
||||
treeId string
|
||||
objectTreeMock *testObjTreeMock
|
||||
reqFactory *mock_synctree.MockRequestFactory
|
||||
ctrl *gomock.Controller
|
||||
syncProtocol TreeSyncProtocol
|
||||
}
|
||||
|
||||
func newSyncProtocolFixture(t *testing.T) *treeSyncProtocolFixture {
|
||||
ctrl := gomock.NewController(t)
|
||||
objTree := &testObjTreeMock{
|
||||
MockObjectTree: mock_objecttree.NewMockObjectTree(ctrl),
|
||||
}
|
||||
spaceId := "spaceId"
|
||||
reqFactory := mock_synctree.NewMockRequestFactory(ctrl)
|
||||
objTree.EXPECT().Id().Return("treeId")
|
||||
syncProtocol := newTreeSyncProtocol(spaceId, objTree, reqFactory)
|
||||
return &treeSyncProtocolFixture{
|
||||
log: log,
|
||||
spaceId: spaceId,
|
||||
senderId: "senderId",
|
||||
treeId: "treeId",
|
||||
objectTreeMock: objTree,
|
||||
reqFactory: reqFactory,
|
||||
ctrl: ctrl,
|
||||
syncProtocol: syncProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
func (fx *treeSyncProtocolFixture) stop() {
|
||||
fx.ctrl.Finish()
|
||||
}
|
||||
|
||||
func TestTreeSyncProtocol_HeadUpdate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fullRequest := &treechangeproto.TreeSyncMessage{
|
||||
Content: &treechangeproto.TreeSyncContentValue{
|
||||
Value: &treechangeproto.TreeSyncContentValue_FullSyncRequest{
|
||||
FullSyncRequest: &treechangeproto.TreeFullSyncRequest{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("head update non empty all heads added", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).Times(2)
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(true)
|
||||
|
||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, res)
|
||||
})
|
||||
|
||||
t.Run("head update non empty equal heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||
|
||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, res)
|
||||
})
|
||||
|
||||
t.Run("head update empty", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: nil,
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||
fx.reqFactory.EXPECT().
|
||||
CreateFullSyncRequest(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullRequest, nil)
|
||||
|
||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fullRequest, res)
|
||||
})
|
||||
|
||||
t.Run("head update empty equal heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
headUpdate := &treechangeproto.TreeHeadUpdate{
|
||||
Heads: []string{"h1"},
|
||||
Changes: nil,
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h1"}).AnyTimes()
|
||||
|
||||
res, err := fx.syncProtocol.HeadUpdate(ctx, fx.senderId, headUpdate)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, res)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTreeSyncProtocol_FullSyncRequest(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
fullResponse := &treechangeproto.TreeSyncMessage{
|
||||
Content: &treechangeproto.TreeSyncContentValue{
|
||||
Value: &treechangeproto.TreeSyncContentValue_FullSyncResponse{
|
||||
FullSyncResponse: &treechangeproto.TreeFullSyncResponse{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("full sync request with change", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
fx.reqFactory.EXPECT().
|
||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullResponse, nil)
|
||||
|
||||
res, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fullResponse, res)
|
||||
})
|
||||
|
||||
t.Run("full sync request with change same heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h1"}).AnyTimes()
|
||||
fx.reqFactory.EXPECT().
|
||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullResponse, nil)
|
||||
|
||||
res, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fullResponse, res)
|
||||
})
|
||||
|
||||
t.Run("full sync request without changes", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.reqFactory.EXPECT().
|
||||
CreateFullSyncResponse(gomock.Eq(fx.objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"})).
|
||||
Return(fullResponse, nil)
|
||||
|
||||
res, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fullResponse, res)
|
||||
})
|
||||
|
||||
t.Run("full sync request with change, raw changes error", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncRequest := &treechangeproto.TreeFullSyncRequest{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Heads().Return([]string{"h2"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().HasChanges(gomock.Eq([]string{"h1"})).Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, fmt.Errorf("addRawChanges error"))
|
||||
|
||||
_, err := fx.syncProtocol.FullSyncRequest(ctx, fx.senderId, fullSyncRequest)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTreeSyncProtocol_FullSyncResponse(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("full sync response with change", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h2"}).AnyTimes()
|
||||
fx.objectTreeMock.EXPECT().
|
||||
HasChanges(gomock.Eq([]string{"h1"})).
|
||||
Return(false)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
AddRawChanges(gomock.Any(), gomock.Eq(objecttree.RawChangesPayload{
|
||||
NewHeads: []string{"h1"},
|
||||
RawChanges: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
})).
|
||||
Return(objecttree.AddResult{}, nil)
|
||||
|
||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullSyncResponse)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("full sync response with same heads", func(t *testing.T) {
|
||||
fx := newSyncProtocolFixture(t)
|
||||
defer fx.stop()
|
||||
chWithId := &treechangeproto.RawTreeChangeWithId{}
|
||||
fullSyncResponse := &treechangeproto.TreeFullSyncResponse{
|
||||
Heads: []string{"h1"},
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{chWithId},
|
||||
SnapshotPath: []string{"h1"},
|
||||
}
|
||||
|
||||
fx.objectTreeMock.EXPECT().Id().AnyTimes().Return(fx.treeId)
|
||||
fx.objectTreeMock.EXPECT().
|
||||
Heads().
|
||||
Return([]string{"h1"}).AnyTimes()
|
||||
|
||||
err := fx.syncProtocol.FullSyncResponse(ctx, fx.senderId, fullSyncResponse)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener (interfaces: UpdateListener)
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener (interfaces: UpdateListener)
|
||||
|
||||
// Package mock_updatelistener is a generated GoMock package.
|
||||
package mock_updatelistener
|
||||
@@ -7,7 +7,7 @@ package mock_updatelistener
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
objecttree "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//go:generate mockgen -destination mock_updatelistener/mock_updatelistener.go github.com/anytypeio/any-sync/commonspace/object/tree/synctree/updatelistener UpdateListener
|
||||
//go:generate mockgen -destination mock_updatelistener/mock_updatelistener.go github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener UpdateListener
|
||||
package updatelistener
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
)
|
||||
|
||||
type UpdateListener interface {
|
||||
|
||||
@@ -3,15 +3,15 @@ package synctree
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/syncstatus"
|
||||
"github.com/anytypeio/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/cheggaaa/mb/v3"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -82,51 +82,124 @@ func (m *messageLog) addMessage(msg protocolMsg) {
|
||||
m.batcher.Add(context.Background(), msg)
|
||||
}
|
||||
|
||||
type requestPeerManager struct {
|
||||
peerId string
|
||||
handlers map[string]*testSyncHandler
|
||||
log *messageLog
|
||||
}
|
||||
|
||||
func newRequestPeerManager(peerId string, log *messageLog) *requestPeerManager {
|
||||
return &requestPeerManager{
|
||||
peerId: peerId,
|
||||
handlers: map[string]*testSyncHandler{},
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) addHandler(peerId string, handler *testSyncHandler) {
|
||||
r.handlers[peerId] = handler
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) Run(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) Close(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) SendRequest(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) QueueRequest(peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
pMsg := protocolMsg{
|
||||
msg: msg,
|
||||
senderId: r.peerId,
|
||||
receiverId: peerId,
|
||||
}
|
||||
r.log.addMessage(pMsg)
|
||||
return r.handlers[peerId].send(context.Background(), pMsg)
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) Init(a *app.App) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) Name() (name string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) SendPeer(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
pMsg := protocolMsg{
|
||||
msg: msg,
|
||||
senderId: r.peerId,
|
||||
receiverId: peerId,
|
||||
}
|
||||
r.log.addMessage(pMsg)
|
||||
return r.handlers[peerId].send(context.Background(), pMsg)
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) Broadcast(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
for _, handler := range r.handlers {
|
||||
pMsg := protocolMsg{
|
||||
msg: msg,
|
||||
senderId: r.peerId,
|
||||
receiverId: handler.peerId,
|
||||
}
|
||||
r.log.addMessage(pMsg)
|
||||
handler.send(context.Background(), pMsg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *requestPeerManager) GetResponsiblePeers(ctx context.Context) (peers []peer.Peer, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// testSyncHandler is the wrapper around individual tree to test sync protocol
|
||||
type testSyncHandler struct {
|
||||
synchandler.SyncHandler
|
||||
batcher *mb.MB[protocolMsg]
|
||||
peerId string
|
||||
aclList list.AclList
|
||||
log *messageLog
|
||||
syncClient objectsync.SyncClient
|
||||
builder objecttree.BuildObjectTreeFunc
|
||||
batcher *mb.MB[protocolMsg]
|
||||
peerId string
|
||||
aclList list.AclList
|
||||
log *messageLog
|
||||
syncClient SyncClient
|
||||
builder objecttree.BuildObjectTreeFunc
|
||||
peerManager *requestPeerManager
|
||||
}
|
||||
|
||||
// createSyncHandler creates a sync handler when a tree is already created
|
||||
func createSyncHandler(peerId, spaceId string, objTree objecttree.ObjectTree, log *messageLog) *testSyncHandler {
|
||||
factory := objectsync.NewRequestFactory()
|
||||
syncClient := objectsync.NewSyncClient(spaceId, newTestMessagePool(peerId, log), factory)
|
||||
peerManager := newRequestPeerManager(peerId, log)
|
||||
syncClient := NewSyncClient(spaceId, peerManager, peerManager)
|
||||
netTree := &broadcastTree{
|
||||
ObjectTree: objTree,
|
||||
SyncClient: syncClient,
|
||||
}
|
||||
handler := newSyncTreeHandler(spaceId, netTree, syncClient, syncstatus.NewNoOpSyncStatus())
|
||||
return newTestSyncHandler(peerId, handler)
|
||||
return &testSyncHandler{
|
||||
SyncHandler: handler,
|
||||
batcher: mb.New[protocolMsg](0),
|
||||
peerId: peerId,
|
||||
peerManager: peerManager,
|
||||
}
|
||||
}
|
||||
|
||||
// createEmptySyncHandler creates a sync handler when the tree will be provided later (this emulates the situation when we have no tree)
|
||||
func createEmptySyncHandler(peerId, spaceId string, builder objecttree.BuildObjectTreeFunc, aclList list.AclList, log *messageLog) *testSyncHandler {
|
||||
factory := objectsync.NewRequestFactory()
|
||||
syncClient := objectsync.NewSyncClient(spaceId, newTestMessagePool(peerId, log), factory)
|
||||
peerManager := newRequestPeerManager(peerId, log)
|
||||
syncClient := NewSyncClient(spaceId, peerManager, peerManager)
|
||||
|
||||
batcher := mb.New[protocolMsg](0)
|
||||
return &testSyncHandler{
|
||||
batcher: batcher,
|
||||
peerId: peerId,
|
||||
aclList: aclList,
|
||||
log: log,
|
||||
syncClient: syncClient,
|
||||
builder: builder,
|
||||
}
|
||||
}
|
||||
|
||||
func newTestSyncHandler(peerId string, syncHandler synchandler.SyncHandler) *testSyncHandler {
|
||||
batcher := mb.New[protocolMsg](0)
|
||||
return &testSyncHandler{
|
||||
SyncHandler: syncHandler,
|
||||
batcher: batcher,
|
||||
peerId: peerId,
|
||||
aclList: aclList,
|
||||
log: log,
|
||||
syncClient: syncClient,
|
||||
builder: builder,
|
||||
peerManager: peerManager,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,13 +213,8 @@ func (h *testSyncHandler) HandleMessage(ctx context.Context, senderId string, re
|
||||
return
|
||||
}
|
||||
if unmarshalled.Content.GetFullSyncResponse() == nil {
|
||||
newTreeRequest := objectsync.NewRequestFactory().CreateNewTreeRequest()
|
||||
var objMsg *spacesyncproto.ObjectSyncMessage
|
||||
objMsg, err = objectsync.MarshallTreeMessage(newTreeRequest, request.SpaceId, request.ObjectId, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return h.manager().SendPeer(context.Background(), senderId, objMsg)
|
||||
newTreeRequest := NewRequestFactory().CreateNewTreeRequest()
|
||||
return h.syncClient.QueueRequest(senderId, request.ObjectId, newTreeRequest)
|
||||
}
|
||||
fullSyncResponse := unmarshalled.Content.GetFullSyncResponse()
|
||||
treeStorage, _ := treestorage.NewInMemoryTreeStorage(unmarshalled.RootChange, []string{unmarshalled.RootChange.Id}, nil)
|
||||
@@ -166,20 +234,13 @@ func (h *testSyncHandler) HandleMessage(ctx context.Context, senderId string, re
|
||||
return
|
||||
}
|
||||
h.SyncHandler = newSyncTreeHandler(request.SpaceId, netTree, h.syncClient, syncstatus.NewNoOpSyncStatus())
|
||||
var objMsg *spacesyncproto.ObjectSyncMessage
|
||||
newTreeRequest := objectsync.NewRequestFactory().CreateHeadUpdate(netTree, res.Added)
|
||||
objMsg, err = objectsync.MarshallTreeMessage(newTreeRequest, request.SpaceId, request.ObjectId, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return h.manager().Broadcast(context.Background(), objMsg)
|
||||
headUpdate := NewRequestFactory().CreateHeadUpdate(netTree, res.Added)
|
||||
h.syncClient.Broadcast(headUpdate)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *testSyncHandler) manager() *testMessagePool {
|
||||
if h.SyncHandler != nil {
|
||||
return h.SyncHandler.(*syncTreeHandler).syncClient.MessagePool().(*testMessagePool)
|
||||
}
|
||||
return h.syncClient.MessagePool().(*testMessagePool)
|
||||
func (h *testSyncHandler) manager() *requestPeerManager {
|
||||
return h.peerManager
|
||||
}
|
||||
|
||||
func (h *testSyncHandler) tree() *broadcastTree {
|
||||
@@ -211,74 +272,28 @@ func (h *testSyncHandler) run(ctx context.Context, t *testing.T, wg *sync.WaitGr
|
||||
h.tree().Unlock()
|
||||
continue
|
||||
}
|
||||
err = h.HandleMessage(ctx, res.senderId, res.msg)
|
||||
if err != nil {
|
||||
fmt.Println("error handling message", err.Error())
|
||||
continue
|
||||
if res.description().name == "FullSyncRequest" {
|
||||
resp, err := h.HandleRequest(ctx, res.senderId, res.msg)
|
||||
if err != nil {
|
||||
fmt.Println("error handling request", err.Error())
|
||||
continue
|
||||
}
|
||||
h.peerManager.SendPeer(ctx, res.senderId, resp)
|
||||
} else {
|
||||
err = h.HandleMessage(ctx, res.senderId, res.msg)
|
||||
if err != nil {
|
||||
fmt.Println("error handling message", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// testMessagePool captures all other handlers and sends messages to them
|
||||
type testMessagePool struct {
|
||||
peerId string
|
||||
handlers map[string]*testSyncHandler
|
||||
log *messageLog
|
||||
}
|
||||
|
||||
func newTestMessagePool(peerId string, log *messageLog) *testMessagePool {
|
||||
return &testMessagePool{handlers: map[string]*testSyncHandler{}, peerId: peerId, log: log}
|
||||
}
|
||||
|
||||
func (m *testMessagePool) addHandler(peerId string, handler *testSyncHandler) {
|
||||
m.handlers[peerId] = handler
|
||||
}
|
||||
|
||||
func (m *testMessagePool) SendPeer(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
pMsg := protocolMsg{
|
||||
msg: msg,
|
||||
senderId: m.peerId,
|
||||
receiverId: peerId,
|
||||
}
|
||||
m.log.addMessage(pMsg)
|
||||
return m.handlers[peerId].send(context.Background(), pMsg)
|
||||
}
|
||||
|
||||
func (m *testMessagePool) Broadcast(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
for _, handler := range m.handlers {
|
||||
pMsg := protocolMsg{
|
||||
msg: msg,
|
||||
senderId: m.peerId,
|
||||
receiverId: handler.peerId,
|
||||
}
|
||||
m.log.addMessage(pMsg)
|
||||
handler.send(context.Background(), pMsg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *testMessagePool) GetResponsiblePeers(ctx context.Context) (peers []peer.Peer, err error) {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
func (m *testMessagePool) LastUsage() time.Time {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
func (m *testMessagePool) HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
func (m *testMessagePool) SendSync(ctx context.Context, peerId string, message *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
// broadcastTree is the tree that broadcasts changes to everyone when changes are added
|
||||
// it is a simplified version of SyncTree which is easier to use in the test environment
|
||||
type broadcastTree struct {
|
||||
objecttree.ObjectTree
|
||||
objectsync.SyncClient
|
||||
SyncClient
|
||||
}
|
||||
|
||||
func (b *broadcastTree) AddRawChanges(ctx context.Context, changes objecttree.RawChangesPayload) (objecttree.AddResult, error) {
|
||||
@@ -287,7 +302,7 @@ func (b *broadcastTree) AddRawChanges(ctx context.Context, changes objecttree.Ra
|
||||
return objecttree.AddResult{}, err
|
||||
}
|
||||
upd := b.SyncClient.CreateHeadUpdate(b.ObjectTree, res.Added)
|
||||
b.SyncClient.Broadcast(ctx, upd)
|
||||
b.SyncClient.Broadcast(upd)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package treechangeproto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package treechangeproto
|
||||
|
||||
import "github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||
import "github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
|
||||
func WrapHeadUpdate(update *TreeHeadUpdate, rootChange *RawTreeChangeWithId) *TreeSyncMessage {
|
||||
return &TreeSyncMessage{
|
||||
|
||||
@@ -3,8 +3,8 @@ package treestorage
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -13,13 +13,21 @@ type InMemoryTreeStorage struct {
|
||||
root *treechangeproto.RawTreeChangeWithId
|
||||
heads []string
|
||||
Changes map[string]*treechangeproto.RawTreeChangeWithId
|
||||
addErr error
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (t *InMemoryTreeStorage) TransactionAdd(changes []*treechangeproto.RawTreeChangeWithId, heads []string) error {
|
||||
func (t *InMemoryTreeStorage) SetReturnErrorOnAdd(err error) {
|
||||
t.addErr = err
|
||||
}
|
||||
|
||||
func (t *InMemoryTreeStorage) AddRawChangesSetHeads(changes []*treechangeproto.RawTreeChangeWithId, heads []string) error {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
if t.addErr != nil {
|
||||
return t.addErr
|
||||
}
|
||||
|
||||
for _, ch := range changes {
|
||||
t.Changes[ch.Id] = ch
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/treestorage (interfaces: TreeStorage)
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/treestorage (interfaces: TreeStorage)
|
||||
|
||||
// Package mock_treestorage is a generated GoMock package.
|
||||
package mock_treestorage
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
treechangeproto "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
treechangeproto "github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
@@ -49,6 +49,20 @@ func (mr *MockTreeStorageMockRecorder) AddRawChange(arg0 interface{}) *gomock.Ca
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChange", reflect.TypeOf((*MockTreeStorage)(nil).AddRawChange), arg0)
|
||||
}
|
||||
|
||||
// AddRawChangesSetHeads mocks base method.
|
||||
func (m *MockTreeStorage) AddRawChangesSetHeads(arg0 []*treechangeproto.RawTreeChangeWithId, arg1 []string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddRawChangesSetHeads", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddRawChangesSetHeads indicates an expected call of AddRawChangesSetHeads.
|
||||
func (mr *MockTreeStorageMockRecorder) AddRawChangesSetHeads(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChangesSetHeads", reflect.TypeOf((*MockTreeStorage)(nil).AddRawChangesSetHeads), arg0, arg1)
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
func (m *MockTreeStorage) Delete() error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -150,17 +164,3 @@ func (mr *MockTreeStorageMockRecorder) SetHeads(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHeads", reflect.TypeOf((*MockTreeStorage)(nil).SetHeads), arg0)
|
||||
}
|
||||
|
||||
// TransactionAdd mocks base method.
|
||||
func (m *MockTreeStorage) TransactionAdd(arg0 []*treechangeproto.RawTreeChangeWithId, arg1 []string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "TransactionAdd", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// TransactionAdd indicates an expected call of TransactionAdd.
|
||||
func (mr *MockTreeStorageMockRecorder) TransactionAdd(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionAdd", reflect.TypeOf((*MockTreeStorage)(nil).TransactionAdd), arg0, arg1)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
//go:generate mockgen -destination mock_treestorage/mock_treestorage.go github.com/anytypeio/any-sync/commonspace/object/tree/treestorage TreeStorage
|
||||
//go:generate mockgen -destination mock_treestorage/mock_treestorage.go github.com/anyproto/any-sync/commonspace/object/tree/treestorage TreeStorage
|
||||
package treestorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -31,7 +31,7 @@ type TreeStorage interface {
|
||||
Heads() ([]string, error)
|
||||
SetHeads(heads []string) error
|
||||
AddRawChange(change *treechangeproto.RawTreeChangeWithId) error
|
||||
TransactionAdd(changes []*treechangeproto.RawTreeChangeWithId, heads []string) error
|
||||
AddRawChangesSetHeads(changes []*treechangeproto.RawTreeChangeWithId, heads []string) error
|
||||
|
||||
GetRawChange(ctx context.Context, id string) (*treechangeproto.RawTreeChangeWithId, error)
|
||||
HasChange(ctx context.Context, id string) (bool, error)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/treemanager (interfaces: TreeManager)
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/treemanager (interfaces: TreeManager,TreeSyncer)
|
||||
|
||||
// Package mock_treemanager is a generated GoMock package.
|
||||
package mock_treemanager
|
||||
@@ -8,8 +8,9 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
app "github.com/anytypeio/any-sync/app"
|
||||
objecttree "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
treemanager "github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
@@ -93,6 +94,20 @@ func (mr *MockTreeManagerMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockTreeManager)(nil).Init), arg0)
|
||||
}
|
||||
|
||||
// MarkTreeDeleted mocks base method.
|
||||
func (m *MockTreeManager) MarkTreeDeleted(arg0 context.Context, arg1, arg2 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "MarkTreeDeleted", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// MarkTreeDeleted indicates an expected call of MarkTreeDeleted.
|
||||
func (mr *MockTreeManagerMockRecorder) MarkTreeDeleted(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkTreeDeleted", reflect.TypeOf((*MockTreeManager)(nil).MarkTreeDeleted), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// Name mocks base method.
|
||||
func (m *MockTreeManager) Name() string {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -107,6 +122,20 @@ func (mr *MockTreeManagerMockRecorder) Name() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockTreeManager)(nil).Name))
|
||||
}
|
||||
|
||||
// NewTreeSyncer mocks base method.
|
||||
func (m *MockTreeManager) NewTreeSyncer(arg0 string, arg1 treemanager.TreeManager) treemanager.TreeSyncer {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "NewTreeSyncer", arg0, arg1)
|
||||
ret0, _ := ret[0].(treemanager.TreeSyncer)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// NewTreeSyncer indicates an expected call of NewTreeSyncer.
|
||||
func (mr *MockTreeManagerMockRecorder) NewTreeSyncer(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewTreeSyncer", reflect.TypeOf((*MockTreeManager)(nil).NewTreeSyncer), arg0, arg1)
|
||||
}
|
||||
|
||||
// Run mocks base method.
|
||||
func (m *MockTreeManager) Run(arg0 context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -120,3 +149,66 @@ func (mr *MockTreeManagerMockRecorder) Run(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockTreeManager)(nil).Run), arg0)
|
||||
}
|
||||
|
||||
// MockTreeSyncer is a mock of TreeSyncer interface.
|
||||
type MockTreeSyncer struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockTreeSyncerMockRecorder
|
||||
}
|
||||
|
||||
// MockTreeSyncerMockRecorder is the mock recorder for MockTreeSyncer.
|
||||
type MockTreeSyncerMockRecorder struct {
|
||||
mock *MockTreeSyncer
|
||||
}
|
||||
|
||||
// NewMockTreeSyncer creates a new mock instance.
|
||||
func NewMockTreeSyncer(ctrl *gomock.Controller) *MockTreeSyncer {
|
||||
mock := &MockTreeSyncer{ctrl: ctrl}
|
||||
mock.recorder = &MockTreeSyncerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockTreeSyncer) EXPECT() *MockTreeSyncerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockTreeSyncer) Close() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Close")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockTreeSyncerMockRecorder) Close() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockTreeSyncer)(nil).Close))
|
||||
}
|
||||
|
||||
// Init mocks base method.
|
||||
func (m *MockTreeSyncer) Init() {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Init")
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init.
|
||||
func (mr *MockTreeSyncerMockRecorder) Init() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockTreeSyncer)(nil).Init))
|
||||
}
|
||||
|
||||
// SyncAll mocks base method.
|
||||
func (m *MockTreeSyncer) SyncAll(arg0 context.Context, arg1 string, arg2, arg3 []string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SyncAll", arg0, arg1, arg2, arg3)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SyncAll indicates an expected call of SyncAll.
|
||||
func (mr *MockTreeSyncerMockRecorder) SyncAll(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncAll", reflect.TypeOf((*MockTreeSyncer)(nil).SyncAll), arg0, arg1, arg2, arg3)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
//go:generate mockgen -destination mock_treemanager/mock_treemanager.go github.com/anytypeio/any-sync/commonspace/object/treemanager TreeManager
|
||||
//go:generate mockgen -destination mock_treemanager/mock_treemanager.go github.com/anyproto/any-sync/commonspace/object/treemanager TreeManager,TreeSyncer
|
||||
package treemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/app"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
)
|
||||
|
||||
const CName = "common.object.treemanager"
|
||||
@@ -12,5 +12,13 @@ const CName = "common.object.treemanager"
|
||||
type TreeManager interface {
|
||||
app.ComponentRunnable
|
||||
GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error)
|
||||
MarkTreeDeleted(ctx context.Context, spaceId, treeId string) error
|
||||
DeleteTree(ctx context.Context, spaceId, treeId string) error
|
||||
NewTreeSyncer(spaceId string, treeManager TreeManager) TreeSyncer
|
||||
}
|
||||
|
||||
type TreeSyncer interface {
|
||||
Init()
|
||||
SyncAll(ctx context.Context, peerId string, existing, missing []string) error
|
||||
Close() error
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package objectmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
||||
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/settings"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSpaceClosed = errors.New("space is closed")
|
||||
)
|
||||
|
||||
type ObjectManager interface {
|
||||
treemanager.TreeManager
|
||||
AddObject(object syncobjectgetter.SyncObject)
|
||||
GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error)
|
||||
}
|
||||
|
||||
type objectManager struct {
|
||||
treemanager.TreeManager
|
||||
spaceId string
|
||||
reservedObjects []syncobjectgetter.SyncObject
|
||||
spaceIsClosed *atomic.Bool
|
||||
}
|
||||
|
||||
func New(manager treemanager.TreeManager) ObjectManager {
|
||||
return &objectManager{
|
||||
TreeManager: manager,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *objectManager) Init(a *app.App) (err error) {
|
||||
state := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||
o.spaceId = state.SpaceId
|
||||
o.spaceIsClosed = state.SpaceIsClosed
|
||||
settingsObject := a.MustComponent(settings.CName).(settings.Settings).SettingsObject()
|
||||
acl := a.MustComponent(syncacl.CName).(*syncacl.SyncAcl)
|
||||
o.AddObject(settingsObject)
|
||||
o.AddObject(acl)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *objectManager) Run(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *objectManager) Close(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *objectManager) AddObject(object syncobjectgetter.SyncObject) {
|
||||
o.reservedObjects = append(o.reservedObjects, object)
|
||||
}
|
||||
|
||||
func (o *objectManager) Name() string {
|
||||
return treemanager.CName
|
||||
}
|
||||
|
||||
func (o *objectManager) GetTree(ctx context.Context, spaceId, treeId string) (objecttree.ObjectTree, error) {
|
||||
if o.spaceIsClosed.Load() {
|
||||
return nil, ErrSpaceClosed
|
||||
}
|
||||
if obj := o.getReservedObject(treeId); obj != nil {
|
||||
return obj.(objecttree.ObjectTree), nil
|
||||
}
|
||||
return o.TreeManager.GetTree(ctx, spaceId, treeId)
|
||||
}
|
||||
|
||||
func (o *objectManager) getReservedObject(id string) syncobjectgetter.SyncObject {
|
||||
for _, obj := range o.reservedObjects {
|
||||
if obj != nil && obj.Id() == id {
|
||||
return obj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *objectManager) GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error) {
|
||||
if o.spaceIsClosed.Load() {
|
||||
return nil, ErrSpaceClosed
|
||||
}
|
||||
if obj := o.getReservedObject(objectId); obj != nil {
|
||||
return obj, nil
|
||||
}
|
||||
t, err := o.TreeManager.GetTree(ctx, o.spaceId, objectId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
obj = t.(syncobjectgetter.SyncObject)
|
||||
return
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/objectsync (interfaces: SyncClient)
|
||||
// Source: github.com/anyproto/any-sync/commonspace/objectsync (interfaces: ObjectSync)
|
||||
|
||||
// Package mock_objectsync is a generated GoMock package.
|
||||
package mock_objectsync
|
||||
@@ -7,146 +7,146 @@ package mock_objectsync
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
objecttree "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
treechangeproto "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
objectsync "github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
spacesyncproto "github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
objectsync "github.com/anyproto/any-sync/commonspace/objectsync"
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockSyncClient is a mock of SyncClient interface.
|
||||
type MockSyncClient struct {
|
||||
// MockObjectSync is a mock of ObjectSync interface.
|
||||
type MockObjectSync struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockSyncClientMockRecorder
|
||||
recorder *MockObjectSyncMockRecorder
|
||||
}
|
||||
|
||||
// MockSyncClientMockRecorder is the mock recorder for MockSyncClient.
|
||||
type MockSyncClientMockRecorder struct {
|
||||
mock *MockSyncClient
|
||||
// MockObjectSyncMockRecorder is the mock recorder for MockObjectSync.
|
||||
type MockObjectSyncMockRecorder struct {
|
||||
mock *MockObjectSync
|
||||
}
|
||||
|
||||
// NewMockSyncClient creates a new mock instance.
|
||||
func NewMockSyncClient(ctrl *gomock.Controller) *MockSyncClient {
|
||||
mock := &MockSyncClient{ctrl: ctrl}
|
||||
mock.recorder = &MockSyncClientMockRecorder{mock}
|
||||
// NewMockObjectSync creates a new mock instance.
|
||||
func NewMockObjectSync(ctrl *gomock.Controller) *MockObjectSync {
|
||||
mock := &MockObjectSync{ctrl: ctrl}
|
||||
mock.recorder = &MockObjectSyncMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockSyncClient) EXPECT() *MockSyncClientMockRecorder {
|
||||
func (m *MockObjectSync) EXPECT() *MockObjectSyncMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Broadcast mocks base method.
|
||||
func (m *MockSyncClient) Broadcast(arg0 context.Context, arg1 *treechangeproto.TreeSyncMessage) {
|
||||
// Close mocks base method.
|
||||
func (m *MockObjectSync) Close(arg0 context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Broadcast", arg0, arg1)
|
||||
}
|
||||
|
||||
// Broadcast indicates an expected call of Broadcast.
|
||||
func (mr *MockSyncClientMockRecorder) Broadcast(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Broadcast", reflect.TypeOf((*MockSyncClient)(nil).Broadcast), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest mocks base method.
|
||||
func (m *MockSyncClient) CreateFullSyncRequest(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest.
|
||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse mocks base method.
|
||||
func (m *MockSyncClient) CreateFullSyncResponse(arg0 objecttree.ObjectTree, arg1, arg2 []string) (*treechangeproto.TreeSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse.
|
||||
func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// CreateHeadUpdate mocks base method.
|
||||
func (m *MockSyncClient) CreateHeadUpdate(arg0 objecttree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *treechangeproto.TreeSyncMessage {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1)
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
ret := m.ctrl.Call(m, "Close", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateHeadUpdate indicates an expected call of CreateHeadUpdate.
|
||||
func (mr *MockSyncClientMockRecorder) CreateHeadUpdate(arg0, arg1 interface{}) *gomock.Call {
|
||||
// Close indicates an expected call of Close.
|
||||
func (mr *MockObjectSyncMockRecorder) Close(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHeadUpdate", reflect.TypeOf((*MockSyncClient)(nil).CreateHeadUpdate), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockObjectSync)(nil).Close), arg0)
|
||||
}
|
||||
|
||||
// CreateNewTreeRequest mocks base method.
|
||||
func (m *MockSyncClient) CreateNewTreeRequest() *treechangeproto.TreeSyncMessage {
|
||||
// CloseThread mocks base method.
|
||||
func (m *MockObjectSync) CloseThread(arg0 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateNewTreeRequest")
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeSyncMessage)
|
||||
ret := m.ctrl.Call(m, "CloseThread", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest.
|
||||
func (mr *MockSyncClientMockRecorder) CreateNewTreeRequest() *gomock.Call {
|
||||
// CloseThread indicates an expected call of CloseThread.
|
||||
func (mr *MockObjectSyncMockRecorder) CloseThread(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateNewTreeRequest))
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseThread", reflect.TypeOf((*MockObjectSync)(nil).CloseThread), arg0)
|
||||
}
|
||||
|
||||
// MessagePool mocks base method.
|
||||
func (m *MockSyncClient) MessagePool() objectsync.MessagePool {
|
||||
// HandleMessage mocks base method.
|
||||
func (m *MockObjectSync) HandleMessage(arg0 context.Context, arg1 objectsync.HandleMessage) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "MessagePool")
|
||||
ret0, _ := ret[0].(objectsync.MessagePool)
|
||||
ret := m.ctrl.Call(m, "HandleMessage", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// MessagePool indicates an expected call of MessagePool.
|
||||
func (mr *MockSyncClientMockRecorder) MessagePool() *gomock.Call {
|
||||
// HandleMessage indicates an expected call of HandleMessage.
|
||||
func (mr *MockObjectSyncMockRecorder) HandleMessage(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MessagePool", reflect.TypeOf((*MockSyncClient)(nil).MessagePool))
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockObjectSync)(nil).HandleMessage), arg0, arg1)
|
||||
}
|
||||
|
||||
// SendSync mocks base method.
|
||||
func (m *MockSyncClient) SendSync(arg0 context.Context, arg1, arg2 string, arg3 *treechangeproto.TreeSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
||||
// HandleRequest mocks base method.
|
||||
func (m *MockObjectSync) HandleRequest(arg0 context.Context, arg1 *spacesyncproto.ObjectSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SendSync", arg0, arg1, arg2, arg3)
|
||||
ret := m.ctrl.Call(m, "HandleRequest", arg0, arg1)
|
||||
ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SendSync indicates an expected call of SendSync.
|
||||
func (mr *MockSyncClientMockRecorder) SendSync(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||
// HandleRequest indicates an expected call of HandleRequest.
|
||||
func (mr *MockObjectSyncMockRecorder) HandleRequest(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendSync", reflect.TypeOf((*MockSyncClient)(nil).SendSync), arg0, arg1, arg2, arg3)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleRequest", reflect.TypeOf((*MockObjectSync)(nil).HandleRequest), arg0, arg1)
|
||||
}
|
||||
|
||||
// SendWithReply mocks base method.
|
||||
func (m *MockSyncClient) SendWithReply(arg0 context.Context, arg1, arg2 string, arg3 *treechangeproto.TreeSyncMessage, arg4 string) error {
|
||||
// Init mocks base method.
|
||||
func (m *MockObjectSync) Init(arg0 *app.App) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SendWithReply", arg0, arg1, arg2, arg3, arg4)
|
||||
ret := m.ctrl.Call(m, "Init", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SendWithReply indicates an expected call of SendWithReply.
|
||||
func (mr *MockSyncClientMockRecorder) SendWithReply(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
|
||||
// Init indicates an expected call of Init.
|
||||
func (mr *MockObjectSyncMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendWithReply", reflect.TypeOf((*MockSyncClient)(nil).SendWithReply), arg0, arg1, arg2, arg3, arg4)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockObjectSync)(nil).Init), arg0)
|
||||
}
|
||||
|
||||
// LastUsage mocks base method.
|
||||
func (m *MockObjectSync) LastUsage() time.Time {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LastUsage")
|
||||
ret0, _ := ret[0].(time.Time)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// LastUsage indicates an expected call of LastUsage.
|
||||
func (mr *MockObjectSyncMockRecorder) LastUsage() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastUsage", reflect.TypeOf((*MockObjectSync)(nil).LastUsage))
|
||||
}
|
||||
|
||||
// Name mocks base method.
|
||||
func (m *MockObjectSync) Name() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Name")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Name indicates an expected call of Name.
|
||||
func (mr *MockObjectSyncMockRecorder) Name() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockObjectSync)(nil).Name))
|
||||
}
|
||||
|
||||
// Run mocks base method.
|
||||
func (m *MockObjectSync) Run(arg0 context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Run", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Run indicates an expected call of Run.
|
||||
func (mr *MockObjectSyncMockRecorder) Run(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockObjectSync)(nil).Run), arg0)
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
package objectsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anytypeio/any-sync/commonspace/peermanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"go.uber.org/zap"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LastUsage interface {
|
||||
LastUsage() time.Time
|
||||
}
|
||||
|
||||
// MessagePool can be made generic to work with different streams
|
||||
type MessagePool interface {
|
||||
LastUsage
|
||||
synchandler.SyncHandler
|
||||
peermanager.PeerManager
|
||||
SendSync(ctx context.Context, peerId string, message *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
||||
}
|
||||
|
||||
type MessageHandler func(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error)
|
||||
|
||||
type responseWaiter struct {
|
||||
ch chan *spacesyncproto.ObjectSyncMessage
|
||||
}
|
||||
|
||||
type messagePool struct {
|
||||
sync.Mutex
|
||||
peermanager.PeerManager
|
||||
messageHandler MessageHandler
|
||||
waiters map[string]responseWaiter
|
||||
waitersMx sync.Mutex
|
||||
counter atomic.Uint64
|
||||
lastUsage atomic.Int64
|
||||
}
|
||||
|
||||
func newMessagePool(peerManager peermanager.PeerManager, messageHandler MessageHandler) MessagePool {
|
||||
s := &messagePool{
|
||||
PeerManager: peerManager,
|
||||
messageHandler: messageHandler,
|
||||
waiters: make(map[string]responseWaiter),
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *messagePool) SendSync(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
s.updateLastUsage()
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Second*10)
|
||||
defer cancel()
|
||||
newCounter := s.counter.Add(1)
|
||||
msg.RequestId = genReplyKey(peerId, msg.ObjectId, newCounter)
|
||||
log.InfoCtx(ctx, "mpool sendSync", zap.String("requestId", msg.RequestId))
|
||||
s.waitersMx.Lock()
|
||||
waiter := responseWaiter{
|
||||
ch: make(chan *spacesyncproto.ObjectSyncMessage, 1),
|
||||
}
|
||||
s.waiters[msg.RequestId] = waiter
|
||||
s.waitersMx.Unlock()
|
||||
|
||||
err = s.SendPeer(ctx, peerId, msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
s.waitersMx.Lock()
|
||||
delete(s.waiters, msg.RequestId)
|
||||
s.waitersMx.Unlock()
|
||||
|
||||
log.With(zap.String("requestId", msg.RequestId)).DebugCtx(ctx, "time elapsed when waiting")
|
||||
err = fmt.Errorf("sendSync context error: %v", ctx.Err())
|
||||
case reply = <-waiter.ch:
|
||||
// success
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *messagePool) SendPeer(ctx context.Context, peerId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
s.updateLastUsage()
|
||||
return s.PeerManager.SendPeer(ctx, peerId, msg)
|
||||
}
|
||||
|
||||
func (s *messagePool) Broadcast(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
s.updateLastUsage()
|
||||
return s.PeerManager.Broadcast(ctx, msg)
|
||||
}
|
||||
|
||||
func (s *messagePool) HandleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
s.updateLastUsage()
|
||||
if msg.ReplyId != "" {
|
||||
log.InfoCtx(ctx, "mpool receive reply", zap.String("replyId", msg.ReplyId))
|
||||
// we got reply, send it to waiter
|
||||
if s.stopWaiter(msg) {
|
||||
return
|
||||
}
|
||||
log.WarnCtx(ctx, "reply id does not exist", zap.String("replyId", msg.ReplyId))
|
||||
}
|
||||
return s.messageHandler(ctx, senderId, msg)
|
||||
}
|
||||
|
||||
func (s *messagePool) LastUsage() time.Time {
|
||||
return time.Unix(s.lastUsage.Load(), 0)
|
||||
}
|
||||
|
||||
func (s *messagePool) updateLastUsage() {
|
||||
s.lastUsage.Store(time.Now().Unix())
|
||||
}
|
||||
|
||||
func (s *messagePool) stopWaiter(msg *spacesyncproto.ObjectSyncMessage) bool {
|
||||
s.waitersMx.Lock()
|
||||
waiter, exists := s.waiters[msg.ReplyId]
|
||||
if exists {
|
||||
delete(s.waiters, msg.ReplyId)
|
||||
s.waitersMx.Unlock()
|
||||
waiter.ch <- msg
|
||||
return true
|
||||
}
|
||||
s.waitersMx.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
func genReplyKey(peerId, treeId string, counter uint64) string {
|
||||
b := &strings.Builder{}
|
||||
b.WriteString(peerId)
|
||||
b.WriteString(".")
|
||||
b.WriteString(treeId)
|
||||
b.WriteString(".")
|
||||
b.WriteString(strconv.FormatUint(counter, 36))
|
||||
return b.String()
|
||||
}
|
||||
@@ -1,136 +1,233 @@
|
||||
//go:generate mockgen -destination mock_objectsync/mock_objectsync.go github.com/anytypeio/any-sync/commonspace/objectsync SyncClient
|
||||
//go:generate mockgen -destination mock_objectsync/mock_objectsync.go github.com/anyproto/any-sync/commonspace/objectsync ObjectSync
|
||||
package objectsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/treemanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||
"github.com/anyproto/any-sync/metric"
|
||||
"github.com/anyproto/any-sync/net/peer"
|
||||
"github.com/anyproto/any-sync/util/multiqueue"
|
||||
"github.com/cheggaaa/mb/v3"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/syncobjectgetter"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anytypeio/any-sync/commonspace/peermanager"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/nodeconf"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/syncobjectgetter"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed("common.commonspace.objectsync")
|
||||
const CName = "common.commonspace.objectsync"
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
type ObjectSync interface {
|
||||
LastUsage
|
||||
synchandler.SyncHandler
|
||||
SyncClient() SyncClient
|
||||
LastUsage() time.Time
|
||||
HandleMessage(ctx context.Context, hm HandleMessage) (err error)
|
||||
HandleRequest(ctx context.Context, req *spacesyncproto.ObjectSyncMessage) (resp *spacesyncproto.ObjectSyncMessage, err error)
|
||||
CloseThread(id string) (err error)
|
||||
app.ComponentRunnable
|
||||
}
|
||||
|
||||
Close() (err error)
|
||||
type HandleMessage struct {
|
||||
Id uint64
|
||||
ReceiveTime time.Time
|
||||
StartHandlingTime time.Time
|
||||
Deadline time.Time
|
||||
SenderId string
|
||||
Message *spacesyncproto.ObjectSyncMessage
|
||||
PeerCtx context.Context
|
||||
}
|
||||
|
||||
func (m HandleMessage) LogFields(fields ...zap.Field) []zap.Field {
|
||||
return append(fields,
|
||||
metric.SpaceId(m.Message.SpaceId),
|
||||
metric.ObjectId(m.Message.ObjectId),
|
||||
metric.QueueDur(m.StartHandlingTime.Sub(m.ReceiveTime)),
|
||||
metric.TotalDur(time.Since(m.ReceiveTime)),
|
||||
)
|
||||
}
|
||||
|
||||
type objectSync struct {
|
||||
spaceId string
|
||||
|
||||
messagePool MessagePool
|
||||
syncClient SyncClient
|
||||
objectGetter syncobjectgetter.SyncObjectGetter
|
||||
configuration nodeconf.NodeConf
|
||||
spaceStorage spacestorage.SpaceStorage
|
||||
metric metric.Metric
|
||||
|
||||
syncCtx context.Context
|
||||
cancelSync context.CancelFunc
|
||||
spaceIsDeleted *atomic.Bool
|
||||
handleQueue multiqueue.MultiQueue[HandleMessage]
|
||||
}
|
||||
|
||||
func NewObjectSync(
|
||||
spaceId string,
|
||||
spaceIsDeleted *atomic.Bool,
|
||||
configuration nodeconf.NodeConf,
|
||||
peerManager peermanager.PeerManager,
|
||||
objectGetter syncobjectgetter.SyncObjectGetter,
|
||||
storage spacestorage.SpaceStorage) ObjectSync {
|
||||
syncCtx, cancel := context.WithCancel(context.Background())
|
||||
os := &objectSync{
|
||||
objectGetter: objectGetter,
|
||||
spaceStorage: storage,
|
||||
spaceId: spaceId,
|
||||
syncCtx: syncCtx,
|
||||
cancelSync: cancel,
|
||||
spaceIsDeleted: spaceIsDeleted,
|
||||
configuration: configuration,
|
||||
func (s *objectSync) Init(a *app.App) (err error) {
|
||||
s.spaceStorage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
||||
s.objectGetter = a.MustComponent(treemanager.CName).(syncobjectgetter.SyncObjectGetter)
|
||||
s.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
||||
sharedData := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||
mc := a.Component(metric.CName)
|
||||
if mc != nil {
|
||||
s.metric = mc.(metric.Metric)
|
||||
}
|
||||
os.messagePool = newMessagePool(peerManager, os.handleMessage)
|
||||
os.syncClient = NewSyncClient(spaceId, os.messagePool, NewRequestFactory())
|
||||
return os
|
||||
s.spaceIsDeleted = sharedData.SpaceIsDeleted
|
||||
s.spaceId = sharedData.SpaceId
|
||||
s.handleQueue = multiqueue.New[HandleMessage](s.processHandleMessage, 100)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *objectSync) Close() (err error) {
|
||||
s.cancelSync()
|
||||
return
|
||||
func (s *objectSync) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *objectSync) Run(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *objectSync) Close(ctx context.Context) (err error) {
|
||||
return s.handleQueue.Close()
|
||||
}
|
||||
|
||||
func New() ObjectSync {
|
||||
return &objectSync{}
|
||||
}
|
||||
|
||||
func (s *objectSync) LastUsage() time.Time {
|
||||
return s.messagePool.LastUsage()
|
||||
// TODO: add time
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (s *objectSync) HandleMessage(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
return s.messagePool.HandleMessage(ctx, senderId, message)
|
||||
func (s *objectSync) HandleRequest(ctx context.Context, req *spacesyncproto.ObjectSyncMessage) (resp *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
peerId, err := peer.CtxPeerId(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.handleRequest(ctx, peerId, req)
|
||||
}
|
||||
|
||||
func (s *objectSync) handleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
log := log.With(
|
||||
zap.String("objectId", msg.ObjectId),
|
||||
zap.String("requestId", msg.RequestId),
|
||||
zap.String("replyId", msg.ReplyId))
|
||||
func (s *objectSync) HandleMessage(ctx context.Context, hm HandleMessage) (err error) {
|
||||
threadId := hm.Message.ObjectId
|
||||
hm.ReceiveTime = time.Now()
|
||||
if hm.PeerCtx == nil {
|
||||
hm.PeerCtx = ctx
|
||||
}
|
||||
err = s.handleQueue.Add(ctx, threadId, hm)
|
||||
if err == mb.ErrOverflowed {
|
||||
log.InfoCtx(ctx, "queue overflowed", zap.String("spaceId", s.spaceId), zap.String("objectId", threadId))
|
||||
// skip overflowed error
|
||||
return nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *objectSync) processHandleMessage(msg HandleMessage) {
|
||||
var err error
|
||||
msg.StartHandlingTime = time.Now()
|
||||
ctx := peer.CtxWithPeerId(context.Background(), msg.SenderId)
|
||||
ctx = logger.CtxWithFields(ctx, zap.Uint64("msgId", msg.Id), zap.String("senderId", msg.SenderId))
|
||||
defer func() {
|
||||
if s.metric == nil {
|
||||
return
|
||||
}
|
||||
s.metric.RequestLog(msg.PeerCtx, "space.streamOp", msg.LogFields(
|
||||
zap.Error(err),
|
||||
)...)
|
||||
}()
|
||||
|
||||
if !msg.Deadline.IsZero() {
|
||||
now := time.Now()
|
||||
if now.After(msg.Deadline) {
|
||||
log.InfoCtx(ctx, "skip message: deadline exceed")
|
||||
err = context.DeadlineExceeded
|
||||
return
|
||||
}
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithDeadline(ctx, msg.Deadline)
|
||||
defer cancel()
|
||||
}
|
||||
if err = s.handleMessage(ctx, msg.SenderId, msg.Message); err != nil {
|
||||
if msg.Message.ObjectId != "" {
|
||||
// cleanup thread on error
|
||||
_ = s.handleQueue.CloseThread(msg.Message.ObjectId)
|
||||
}
|
||||
log.InfoCtx(ctx, "handleMessage error", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *objectSync) handleRequest(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
log := log.With(zap.String("objectId", msg.ObjectId))
|
||||
if s.spaceIsDeleted.Load() {
|
||||
log = log.With(zap.Bool("isDeleted", true))
|
||||
// preventing sync with other clients if they are not just syncing the settings tree
|
||||
if !slices.Contains(s.configuration.NodeIds(s.spaceId), senderId) && msg.ObjectId != s.spaceStorage.SpaceSettingsId() {
|
||||
return s.unmarshallSendError(ctx, msg, spacesyncproto.ErrSpaceIsDeleted, senderId, msg.ObjectId)
|
||||
return nil, spacesyncproto.ErrSpaceIsDeleted
|
||||
}
|
||||
}
|
||||
log.DebugCtx(ctx, "handling message")
|
||||
err = s.checkEmptyFullSync(log, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj, err := s.objectGetter.GetObject(ctx, msg.ObjectId)
|
||||
if err != nil {
|
||||
return nil, treechangeproto.ErrGetTree
|
||||
}
|
||||
return obj.HandleRequest(ctx, senderId, msg)
|
||||
}
|
||||
|
||||
func (s *objectSync) handleMessage(ctx context.Context, senderId string, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
log := log.With(zap.String("objectId", msg.ObjectId))
|
||||
if s.spaceIsDeleted.Load() {
|
||||
log = log.With(zap.Bool("isDeleted", true))
|
||||
// preventing sync with other clients if they are not just syncing the settings tree
|
||||
if !slices.Contains(s.configuration.NodeIds(s.spaceId), senderId) && msg.ObjectId != s.spaceStorage.SpaceSettingsId() {
|
||||
return spacesyncproto.ErrSpaceIsDeleted
|
||||
}
|
||||
}
|
||||
err = s.checkEmptyFullSync(log, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj, err := s.objectGetter.GetObject(ctx, msg.ObjectId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get object from cache: %w", err)
|
||||
}
|
||||
err = obj.HandleMessage(ctx, senderId, msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to handle message: %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *objectSync) CloseThread(id string) (err error) {
|
||||
return s.handleQueue.CloseThread(id)
|
||||
}
|
||||
|
||||
func (s *objectSync) checkEmptyFullSync(log logger.CtxLogger, msg *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
hasTree, err := s.spaceStorage.HasTree(msg.ObjectId)
|
||||
if err != nil {
|
||||
return s.unmarshallSendError(ctx, msg, spacesyncproto.ErrUnexpected, senderId, msg.ObjectId)
|
||||
log.Warn("failed to execute get operation on storage has tree", zap.Error(err))
|
||||
return spacesyncproto.ErrUnexpected
|
||||
}
|
||||
// in this case we will try to get it from remote, unless the sender also sent us the same request :-)
|
||||
if !hasTree {
|
||||
treeMsg := &treechangeproto.TreeSyncMessage{}
|
||||
err = proto.Unmarshal(msg.Payload, treeMsg)
|
||||
if err != nil {
|
||||
return s.sendError(ctx, nil, spacesyncproto.ErrUnexpected, senderId, msg.ObjectId, msg.RequestId)
|
||||
return nil
|
||||
}
|
||||
// this means that we don't have the tree locally and therefore can't return it
|
||||
if s.isEmptyFullSyncRequest(treeMsg) {
|
||||
return s.sendError(ctx, nil, treechangeproto.ErrGetTree, senderId, msg.ObjectId, msg.RequestId)
|
||||
return treechangeproto.ErrGetTree
|
||||
}
|
||||
}
|
||||
obj, err := s.objectGetter.GetObject(ctx, msg.ObjectId)
|
||||
if err != nil {
|
||||
log.DebugCtx(ctx, "failed to get object")
|
||||
return s.unmarshallSendError(ctx, msg, err, msg.ObjectId, senderId)
|
||||
}
|
||||
return obj.HandleMessage(ctx, senderId, msg)
|
||||
}
|
||||
|
||||
func (s *objectSync) SyncClient() SyncClient {
|
||||
return s.syncClient
|
||||
}
|
||||
|
||||
func (s *objectSync) unmarshallSendError(ctx context.Context, msg *spacesyncproto.ObjectSyncMessage, respErr error, senderId, objectId string) (err error) {
|
||||
unmarshalled := &treechangeproto.TreeSyncMessage{}
|
||||
err = proto.Unmarshal(msg.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.sendError(ctx, unmarshalled.RootChange, respErr, senderId, objectId, msg.RequestId)
|
||||
}
|
||||
|
||||
func (s *objectSync) sendError(ctx context.Context, root *treechangeproto.RawTreeChangeWithId, respErr error, senderId, objectId, replyId string) (err error) {
|
||||
resp := treechangeproto.WrapError(respErr, root)
|
||||
return s.syncClient.SendWithReply(ctx, senderId, objectId, resp, replyId)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *objectSync) isEmptyFullSyncRequest(msg *treechangeproto.TreeSyncMessage) bool {
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
package objectsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type SyncClient interface {
|
||||
RequestFactory
|
||||
Broadcast(ctx context.Context, msg *treechangeproto.TreeSyncMessage)
|
||||
SendWithReply(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage, replyId string) (err error)
|
||||
SendSync(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error)
|
||||
MessagePool() MessagePool
|
||||
}
|
||||
|
||||
type syncClient struct {
|
||||
RequestFactory
|
||||
spaceId string
|
||||
messagePool MessagePool
|
||||
}
|
||||
|
||||
func NewSyncClient(
|
||||
spaceId string,
|
||||
messagePool MessagePool,
|
||||
factory RequestFactory) SyncClient {
|
||||
return &syncClient{
|
||||
messagePool: messagePool,
|
||||
RequestFactory: factory,
|
||||
spaceId: spaceId,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncClient) Broadcast(ctx context.Context, msg *treechangeproto.TreeSyncMessage) {
|
||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, msg.RootChange.Id, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = s.messagePool.Broadcast(ctx, objMsg)
|
||||
if err != nil {
|
||||
log.DebugCtx(ctx, "broadcast error", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *syncClient) SendSync(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.messagePool.SendSync(ctx, peerId, objMsg)
|
||||
}
|
||||
|
||||
func (s *syncClient) SendWithReply(ctx context.Context, peerId, objectId string, msg *treechangeproto.TreeSyncMessage, replyId string) (err error) {
|
||||
objMsg, err := MarshallTreeMessage(msg, s.spaceId, objectId, replyId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.messagePool.SendPeer(ctx, peerId, objMsg)
|
||||
}
|
||||
|
||||
func (s *syncClient) MessagePool() MessagePool {
|
||||
return s.messagePool
|
||||
}
|
||||
|
||||
func MarshallTreeMessage(message *treechangeproto.TreeSyncMessage, spaceId, objectId, replyId string) (objMsg *spacesyncproto.ObjectSyncMessage, err error) {
|
||||
payload, err := message.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
objMsg = &spacesyncproto.ObjectSyncMessage{
|
||||
ReplyId: replyId,
|
||||
Payload: payload,
|
||||
ObjectId: objectId,
|
||||
SpaceId: spaceId,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -2,9 +2,10 @@ package synchandler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
)
|
||||
|
||||
type SyncHandler interface {
|
||||
HandleMessage(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (err error)
|
||||
HandleMessage(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error)
|
||||
HandleRequest(ctx context.Context, senderId string, request *spacesyncproto.ObjectSyncMessage) (response *spacesyncproto.ObjectSyncMessage, err error)
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/objecttreebuilder (interfaces: TreeBuilder)
|
||||
|
||||
// Package mock_objecttreebuilder is a generated GoMock package.
|
||||
package mock_objecttreebuilder
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
objecttree "github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
updatelistener "github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
treestorage "github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
objecttreebuilder "github.com/anyproto/any-sync/commonspace/objecttreebuilder"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockTreeBuilder is a mock of TreeBuilder interface.
|
||||
type MockTreeBuilder struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockTreeBuilderMockRecorder
|
||||
}
|
||||
|
||||
// MockTreeBuilderMockRecorder is the mock recorder for MockTreeBuilder.
|
||||
type MockTreeBuilderMockRecorder struct {
|
||||
mock *MockTreeBuilder
|
||||
}
|
||||
|
||||
// NewMockTreeBuilder creates a new mock instance.
|
||||
func NewMockTreeBuilder(ctrl *gomock.Controller) *MockTreeBuilder {
|
||||
mock := &MockTreeBuilder{ctrl: ctrl}
|
||||
mock.recorder = &MockTreeBuilderMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockTreeBuilder) EXPECT() *MockTreeBuilderMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// BuildHistoryTree mocks base method.
|
||||
func (m *MockTreeBuilder) BuildHistoryTree(arg0 context.Context, arg1 string, arg2 objecttreebuilder.HistoryTreeOpts) (objecttree.HistoryTree, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BuildHistoryTree", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(objecttree.HistoryTree)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// BuildHistoryTree indicates an expected call of BuildHistoryTree.
|
||||
func (mr *MockTreeBuilderMockRecorder) BuildHistoryTree(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildHistoryTree", reflect.TypeOf((*MockTreeBuilder)(nil).BuildHistoryTree), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// BuildTree mocks base method.
|
||||
func (m *MockTreeBuilder) BuildTree(arg0 context.Context, arg1 string, arg2 objecttreebuilder.BuildTreeOpts) (objecttree.ObjectTree, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BuildTree", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(objecttree.ObjectTree)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// BuildTree indicates an expected call of BuildTree.
|
||||
func (mr *MockTreeBuilderMockRecorder) BuildTree(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildTree", reflect.TypeOf((*MockTreeBuilder)(nil).BuildTree), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// CreateTree mocks base method.
|
||||
func (m *MockTreeBuilder) CreateTree(arg0 context.Context, arg1 objecttree.ObjectTreeCreatePayload) (treestorage.TreeStorageCreatePayload, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateTree", arg0, arg1)
|
||||
ret0, _ := ret[0].(treestorage.TreeStorageCreatePayload)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateTree indicates an expected call of CreateTree.
|
||||
func (mr *MockTreeBuilderMockRecorder) CreateTree(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTree", reflect.TypeOf((*MockTreeBuilder)(nil).CreateTree), arg0, arg1)
|
||||
}
|
||||
|
||||
// PutTree mocks base method.
|
||||
func (m *MockTreeBuilder) PutTree(arg0 context.Context, arg1 treestorage.TreeStorageCreatePayload, arg2 updatelistener.UpdateListener) (objecttree.ObjectTree, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "PutTree", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(objecttree.ObjectTree)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// PutTree indicates an expected call of PutTree.
|
||||
func (mr *MockTreeBuilderMockRecorder) PutTree(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutTree", reflect.TypeOf((*MockTreeBuilder)(nil).PutTree), arg0, arg1, arg2)
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
//go:generate mockgen -destination mock_objecttreebuilder/mock_objecttreebuilder.go github.com/anyproto/any-sync/commonspace/objecttreebuilder TreeBuilder
|
||||
package objecttreebuilder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/headsync"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/syncacl"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync"
|
||||
"github.com/anyproto/any-sync/commonspace/peermanager"
|
||||
"github.com/anyproto/any-sync/commonspace/requestmanager"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestate"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
||||
"github.com/anyproto/any-sync/nodeconf"
|
||||
"go.uber.org/zap"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type BuildTreeOpts struct {
|
||||
Listener updatelistener.UpdateListener
|
||||
WaitTreeRemoteSync bool
|
||||
TreeBuilder objecttree.BuildObjectTreeFunc
|
||||
}
|
||||
|
||||
const CName = "common.commonspace.objecttreebuilder"
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
var ErrSpaceClosed = errors.New("space is closed")
|
||||
|
||||
type HistoryTreeOpts struct {
|
||||
BeforeId string
|
||||
Include bool
|
||||
BuildFullTree bool
|
||||
}
|
||||
|
||||
type TreeBuilder interface {
|
||||
BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (t objecttree.ObjectTree, err error)
|
||||
BuildHistoryTree(ctx context.Context, id string, opts HistoryTreeOpts) (t objecttree.HistoryTree, err error)
|
||||
CreateTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error)
|
||||
PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, listener updatelistener.UpdateListener) (t objecttree.ObjectTree, err error)
|
||||
}
|
||||
|
||||
type TreeBuilderComponent interface {
|
||||
app.Component
|
||||
TreeBuilder
|
||||
}
|
||||
|
||||
func New() TreeBuilderComponent {
|
||||
return &treeBuilder{}
|
||||
}
|
||||
|
||||
type treeBuilder struct {
|
||||
syncClient synctree.SyncClient
|
||||
configuration nodeconf.NodeConf
|
||||
headsNotifiable synctree.HeadNotifiable
|
||||
peerManager peermanager.PeerManager
|
||||
requestManager requestmanager.RequestManager
|
||||
spaceStorage spacestorage.SpaceStorage
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
objectSync objectsync.ObjectSync
|
||||
|
||||
log logger.CtxLogger
|
||||
builder objecttree.BuildObjectTreeFunc
|
||||
spaceId string
|
||||
aclList list.AclList
|
||||
treesUsed *atomic.Int32
|
||||
isClosed *atomic.Bool
|
||||
}
|
||||
|
||||
func (t *treeBuilder) Init(a *app.App) (err error) {
|
||||
state := a.MustComponent(spacestate.CName).(*spacestate.SpaceState)
|
||||
t.spaceId = state.SpaceId
|
||||
t.isClosed = state.SpaceIsClosed
|
||||
t.treesUsed = state.TreesUsed
|
||||
t.builder = state.TreeBuilderFunc
|
||||
t.aclList = a.MustComponent(syncacl.CName).(*syncacl.SyncAcl)
|
||||
t.spaceStorage = a.MustComponent(spacestorage.CName).(spacestorage.SpaceStorage)
|
||||
t.configuration = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf)
|
||||
t.headsNotifiable = a.MustComponent(headsync.CName).(headsync.HeadSync)
|
||||
t.syncStatus = a.MustComponent(syncstatus.CName).(syncstatus.StatusUpdater)
|
||||
t.peerManager = a.MustComponent(peermanager.CName).(peermanager.PeerManager)
|
||||
t.requestManager = a.MustComponent(requestmanager.CName).(requestmanager.RequestManager)
|
||||
t.objectSync = a.MustComponent(objectsync.CName).(objectsync.ObjectSync)
|
||||
t.log = log.With(zap.String("spaceId", t.spaceId))
|
||||
t.syncClient = synctree.NewSyncClient(t.spaceId, t.requestManager, t.peerManager)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *treeBuilder) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (t *treeBuilder) BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (ot objecttree.ObjectTree, err error) {
|
||||
if t.isClosed.Load() {
|
||||
// TODO: change to real error
|
||||
err = ErrSpaceClosed
|
||||
return
|
||||
}
|
||||
treeBuilder := opts.TreeBuilder
|
||||
if treeBuilder == nil {
|
||||
treeBuilder = t.builder
|
||||
}
|
||||
deps := synctree.BuildDeps{
|
||||
SpaceId: t.spaceId,
|
||||
SyncClient: t.syncClient,
|
||||
Configuration: t.configuration,
|
||||
HeadNotifiable: t.headsNotifiable,
|
||||
Listener: opts.Listener,
|
||||
AclList: t.aclList,
|
||||
SpaceStorage: t.spaceStorage,
|
||||
OnClose: t.onClose,
|
||||
SyncStatus: t.syncStatus,
|
||||
WaitTreeRemoteSync: opts.WaitTreeRemoteSync,
|
||||
PeerGetter: t.peerManager,
|
||||
BuildObjectTree: treeBuilder,
|
||||
}
|
||||
t.treesUsed.Add(1)
|
||||
t.log.Debug("incrementing counter", zap.String("id", id), zap.Int32("trees", t.treesUsed.Load()))
|
||||
if ot, err = synctree.BuildSyncTreeOrGetRemote(ctx, id, deps); err != nil {
|
||||
t.treesUsed.Add(-1)
|
||||
t.log.Debug("decrementing counter, load failed", zap.String("id", id), zap.Int32("trees", t.treesUsed.Load()), zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeBuilder) BuildHistoryTree(ctx context.Context, id string, opts HistoryTreeOpts) (ot objecttree.HistoryTree, err error) {
|
||||
if t.isClosed.Load() {
|
||||
// TODO: change to real error
|
||||
err = ErrSpaceClosed
|
||||
return
|
||||
}
|
||||
|
||||
params := objecttree.HistoryTreeParams{
|
||||
AclList: t.aclList,
|
||||
BeforeId: opts.BeforeId,
|
||||
IncludeBeforeId: opts.Include,
|
||||
BuildFullTree: opts.BuildFullTree,
|
||||
}
|
||||
params.TreeStorage, err = t.spaceStorage.TreeStorage(id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return objecttree.BuildHistoryTree(params)
|
||||
}
|
||||
|
||||
func (t *treeBuilder) CreateTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error) {
|
||||
if t.isClosed.Load() {
|
||||
err = ErrSpaceClosed
|
||||
return
|
||||
}
|
||||
root, err := objecttree.CreateObjectTreeRoot(payload, t.aclList)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = treestorage.TreeStorageCreatePayload{
|
||||
RootRawChange: root,
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{root},
|
||||
Heads: []string{root.Id},
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeBuilder) PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, listener updatelistener.UpdateListener) (ot objecttree.ObjectTree, err error) {
|
||||
if t.isClosed.Load() {
|
||||
err = ErrSpaceClosed
|
||||
return
|
||||
}
|
||||
deps := synctree.BuildDeps{
|
||||
SpaceId: t.spaceId,
|
||||
SyncClient: t.syncClient,
|
||||
Configuration: t.configuration,
|
||||
HeadNotifiable: t.headsNotifiable,
|
||||
Listener: listener,
|
||||
AclList: t.aclList,
|
||||
SpaceStorage: t.spaceStorage,
|
||||
OnClose: t.onClose,
|
||||
SyncStatus: t.syncStatus,
|
||||
PeerGetter: t.peerManager,
|
||||
BuildObjectTree: t.builder,
|
||||
}
|
||||
ot, err = synctree.PutSyncTree(ctx, payload, deps)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t.treesUsed.Add(1)
|
||||
t.log.Debug("incrementing counter", zap.String("id", payload.RootRawChange.Id), zap.Int32("trees", t.treesUsed.Load()))
|
||||
return
|
||||
}
|
||||
|
||||
func (t *treeBuilder) onClose(id string) {
|
||||
t.treesUsed.Add(-1)
|
||||
log.Debug("decrementing counter", zap.String("id", id), zap.Int32("trees", t.treesUsed.Load()), zap.String("spaceId", t.spaceId))
|
||||
_ = t.objectSync.CloseThread(id)
|
||||
}
|
||||
+16
-18
@@ -2,14 +2,14 @@ package commonspace
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/util/cidutil"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"hash/fnv"
|
||||
"math/rand"
|
||||
@@ -214,14 +214,15 @@ func validateSpaceStorageCreatePayload(payload spacestorage.SpaceStorageCreatePa
|
||||
}
|
||||
|
||||
func ValidateSpaceHeader(rawHeaderWithId *spacesyncproto.RawSpaceHeaderWithId, identity crypto.PubKey) (err error) {
|
||||
if rawHeaderWithId == nil {
|
||||
return spacestorage.ErrIncorrectSpaceHeader
|
||||
}
|
||||
sepIdx := strings.Index(rawHeaderWithId.Id, ".")
|
||||
if sepIdx == -1 {
|
||||
err = spacestorage.ErrIncorrectSpaceHeader
|
||||
return
|
||||
return spacestorage.ErrIncorrectSpaceHeader
|
||||
}
|
||||
if !cidutil.VerifyCid(rawHeaderWithId.RawHeader, rawHeaderWithId.Id[:sepIdx]) {
|
||||
err = objecttree.ErrIncorrectCid
|
||||
return
|
||||
return objecttree.ErrIncorrectCid
|
||||
}
|
||||
var rawSpaceHeader spacesyncproto.RawSpaceHeader
|
||||
err = proto.Unmarshal(rawHeaderWithId.RawHeader, &rawSpaceHeader)
|
||||
@@ -239,19 +240,16 @@ func ValidateSpaceHeader(rawHeaderWithId *spacesyncproto.RawSpaceHeaderWithId, i
|
||||
}
|
||||
res, err := payloadIdentity.Verify(rawSpaceHeader.SpaceHeader, rawSpaceHeader.Signature)
|
||||
if err != nil || !res {
|
||||
err = spacestorage.ErrIncorrectSpaceHeader
|
||||
return
|
||||
return spacestorage.ErrIncorrectSpaceHeader
|
||||
}
|
||||
if rawHeaderWithId.Id[sepIdx+1:] != strconv.FormatUint(header.ReplicationKey, 36) {
|
||||
err = spacestorage.ErrIncorrectSpaceHeader
|
||||
return
|
||||
return spacestorage.ErrIncorrectSpaceHeader
|
||||
}
|
||||
if identity == nil {
|
||||
return
|
||||
}
|
||||
if !payloadIdentity.Equals(identity) {
|
||||
err = ErrIncorrectIdentity
|
||||
return
|
||||
return ErrIncorrectIdentity
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ package commonspace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/util/cidutil"
|
||||
"github.com/anytypeio/any-sync/util/crypto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/util/cidutil"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"math/rand"
|
||||
|
||||
Loaded 100 of 217 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user