Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bded394761 | ||
|
|
ef1155743e | ||
|
|
4ff4c5950d | ||
|
|
8e1053a116 | ||
|
|
576cef2d53 | ||
|
|
5e23680b36 | ||
|
|
4e7c3840ac | ||
|
|
265b64b6f0 | ||
|
|
3bd8c92f51 | ||
|
|
a51f26c785 | ||
|
|
af753ec392 | ||
|
|
8de24af4b9 | ||
|
|
790be1b02b | ||
|
|
a093e1c9f0 | ||
|
|
3381a84dec | ||
|
|
42913d3d18 | ||
|
|
b84dfc595f | ||
|
|
b6be4e8975 | ||
|
|
25a52f41b8 | ||
|
|
18bc3314fe | ||
|
|
4c4abc9266 | ||
|
|
8cf6ad560e | ||
|
|
059f0ca636 | ||
|
|
4cb79eeab9 | ||
|
|
9f0c7f4711 | ||
|
|
cfbae15999 | ||
|
|
a7a54bcc8b | ||
|
|
1eed7e5050 | ||
|
|
d21dd91a77 | ||
|
|
c792ff1fcb | ||
|
|
ec96ecafcc | ||
|
|
b875c504a0 | ||
|
|
1bb707f0a2 | ||
|
|
28dd15e07e | ||
|
|
0fb8ec2b7c | ||
|
|
ca1ebaaf14 | ||
|
|
7f165dd6f8 | ||
|
|
abe4603639 | ||
|
|
da61fec0a0 | ||
|
|
39bffdab9c | ||
|
|
c12bb0f081 | ||
|
|
96896d3060 | ||
|
|
d158b98b35 | ||
|
|
57eb691b53 | ||
|
|
fe8f8c43f5 | ||
|
|
39f79e03ce | ||
|
|
a9c564777a | ||
|
|
876f94ef4e | ||
|
|
3bc23edfd3 | ||
|
|
88cc804d01 | ||
|
|
993403bf76 | ||
|
|
35e58ae20f | ||
|
|
27b6f754b5 | ||
|
|
4186e30912 | ||
|
|
9205f414cc | ||
|
|
d986290db8 | ||
|
|
dcae5da0ec | ||
|
|
bcc0aa8fd6 | ||
|
|
8c36953790 | ||
|
|
18bc40a531 | ||
|
|
da74f45d46 | ||
|
|
527d104ba7 | ||
|
|
7e41284cc8 | ||
|
|
071d85cd22 | ||
|
|
d0ac577686 | ||
|
|
0dd992b7a6 | ||
|
|
2f95c66360 | ||
|
|
c7254ed8be | ||
|
|
bb25dd0ea1 | ||
|
|
c565071dad | ||
|
|
b96654dd14 | ||
|
|
048406edfd | ||
|
|
62c4e80c36 | ||
|
|
b3194da23c | ||
|
|
207e2dce52 | ||
|
|
dfd2b0e8b9 | ||
|
|
248a112236 | ||
|
|
dbfc62eef5 | ||
|
|
b8b4f937d4 | ||
|
|
f79d7f8eb4 | ||
|
|
c88b31ca2f | ||
|
|
4c5cb61802 | ||
|
|
90f0d0bb37 | ||
|
|
71cfefe6fa | ||
|
|
4aa8c9ac09 | ||
|
|
6bb542ca09 | ||
|
|
f9c65bf8e1 | ||
|
|
411a67c31f | ||
|
|
02e92a0db0 | ||
|
|
9b59e6029d | ||
|
|
ac84febce9 | ||
|
|
e57ef72be1 | ||
|
|
2d605ca694 | ||
|
|
c64f732878 | ||
|
|
40f8fb503a | ||
|
|
987bd0154f | ||
|
|
8680c24329 | ||
|
|
1a2ceb6e2b | ||
|
|
8d8aff2680 | ||
|
|
397252d97a | ||
|
|
58389432d7 | ||
|
|
a55306aa27 | ||
|
|
e44bcfedaf | ||
|
|
33a2b5cc84 | ||
|
|
a855f02f63 | ||
|
|
8eafaa266d | ||
|
|
b5dc56d72c | ||
|
|
a103c960a4 | ||
|
|
98b8584efc | ||
|
|
fa076ee83f | ||
|
|
d4b90e5202 | ||
|
|
84b21c62c5 | ||
|
|
caec31dd7f | ||
|
|
f86337ffc8 | ||
|
|
70163c3aaf | ||
|
|
5b98550349 | ||
|
|
0a24886ce6 | ||
|
|
9da4e0e7e9 | ||
|
|
d35d5c9437 | ||
|
|
ac018be8b1 | ||
|
|
6fb720e0ea | ||
|
|
e736ff313f | ||
|
|
f696d774bb | ||
|
|
50dcc735c7 | ||
|
|
3c30f72cd5 | ||
|
|
874d5bbca0 | ||
|
|
acf21c7d54 | ||
|
|
b7d4c27920 | ||
|
|
555ff804fe | ||
|
|
ec900d6e9d | ||
|
|
0b27fe7642 | ||
|
|
ad8055635d | ||
|
|
f4a27fb1d3 | ||
|
|
b0b75ac8ea | ||
|
|
108900e88a | ||
|
|
439182cdfa | ||
|
|
9b0aba1339 | ||
|
|
b3024740d0 | ||
|
|
cfd2ea8ece | ||
|
|
d0adbf5026 | ||
|
|
9d6e0886f4 | ||
|
|
f1d1a26b64 | ||
|
|
15ccf29f0b | ||
|
|
ad87d5e545 | ||
|
|
322a8d144a | ||
|
|
4aa9ce1a27 | ||
|
|
364ec32a39 | ||
|
|
6444042a02 | ||
|
|
5df4f13938 | ||
|
|
4eba413631 | ||
|
|
060c14792c | ||
|
|
a43d25841d | ||
|
|
da0398c103 | ||
|
|
3b320087f3 | ||
|
|
0c85336e82 | ||
|
|
367bd1978d | ||
|
|
8a6a5a6bc1 | ||
|
|
c064f89988 | ||
|
|
c36fe14272 | ||
|
|
8a19ea8dcb | ||
|
|
d0a4b05706 | ||
|
|
38e055ed12 | ||
|
|
0924413056 | ||
|
|
09acec0d11 | ||
|
|
fa8ac48c2a | ||
|
|
da70f7b78c | ||
|
|
e0c1e6f19d | ||
|
|
b71360ec01 | ||
|
|
3807d8e6ef | ||
|
|
d7cce3c9fb | ||
|
|
5eaa3bfe44 | ||
|
|
eb078d311a | ||
|
|
462eb1cc7a | ||
|
|
8b703efb55 | ||
|
|
a08c3873b4 | ||
|
|
2ffd735e25 | ||
|
|
761772f09a | ||
|
|
de12b35216 | ||
|
|
6f8a21e547 | ||
|
|
50e3d70f70 | ||
|
|
f9a064e9aa | ||
|
|
a8507d8120 | ||
|
|
acf5b95646 | ||
|
|
cd197dc7d7 | ||
|
|
016543db40 | ||
|
|
6ed6142129 | ||
|
|
cdfee1232e | ||
|
|
25191a2443 | ||
|
|
339dd2b006 | ||
|
|
eb7a6fb9dc | ||
|
|
8b86ad2918 | ||
|
|
3d624adc09 | ||
|
|
4062a76e21 | ||
|
|
9ae5710854 | ||
|
|
494b7552ce | ||
|
|
aaecd2ae6d | ||
|
|
1e4a25c15d | ||
|
|
ea9a51ec02 | ||
|
|
4593637e23 | ||
|
|
bdae3a76d0 | ||
|
|
a370efda87 | ||
|
|
bab107ebf6 | ||
|
|
ea6dd0dc99 | ||
|
|
20b9a36604 | ||
|
|
b3b50aa8ab | ||
|
|
f749e967f1 | ||
|
|
75b5193dc4 | ||
|
|
6bc8124413 | ||
|
|
06d68e25ac | ||
|
|
19bc9a0c93 | ||
|
|
4a3f20b236 | ||
|
|
82400407b9 | ||
|
|
d49384b98b | ||
|
|
3c1072db4a | ||
|
|
41fde6f1ad | ||
|
|
620731b464 | ||
|
|
558cecea76 | ||
|
|
ff81bf1fac | ||
|
|
71717a5dbb | ||
|
|
9155a3cbf2 | ||
|
|
f98d629a78 | ||
|
|
55e04cd2cd | ||
|
|
f6d8183001 | ||
|
|
b9f4ddd480 | ||
|
|
0d9360490d | ||
|
|
8854bbbe95 | ||
|
|
b617cdc690 | ||
|
|
62d26fbafd | ||
|
|
6c38474c40 | ||
|
|
fed41ba3aa | ||
|
|
181d74a139 | ||
|
|
72d83ca171 | ||
|
|
020b1f1d71 | ||
|
|
11e57a43f7 | ||
|
|
f9647c0691 | ||
|
|
92bdf7d57b | ||
|
|
d5ef4f9313 | ||
|
|
925005a9de | ||
|
|
354ee3b6c7 | ||
|
|
9f080b4533 | ||
|
|
16b7181ebc | ||
|
|
7c1d449743 | ||
|
|
9bbb1a02e5 | ||
|
|
37035bfacd | ||
|
|
434f149e04 | ||
|
|
25f30cacc2 | ||
|
|
8f862fd1a1 | ||
|
|
5904815908 | ||
|
|
36b80c25d4 | ||
|
|
994ffe3d5f | ||
|
|
08b8ecba32 | ||
|
|
675885d875 | ||
|
|
f0b8e5ab64 | ||
|
|
9a202207b4 | ||
|
|
f4027b433d | ||
|
|
ab4fee8229 | ||
|
|
1920a190bc | ||
|
|
b9db1881ca | ||
|
|
9c1c1e609d | ||
|
|
7a2d366bdd | ||
|
|
6497e461bd | ||
|
|
6a6b750b4e | ||
|
|
7b5c51a198 | ||
|
|
600ae38c0f | ||
|
|
f5b9275192 | ||
|
|
c33e944ec4 | ||
|
|
8263bbfde2 | ||
|
|
305cd2d795 | ||
|
|
93bf6d4dc4 | ||
|
|
3546fabdfa | ||
|
|
f507e4cabd | ||
|
|
9992b1fa7e | ||
|
|
e419e59f1d | ||
|
|
a3d5a56077 | ||
|
|
7bd593f0e5 | ||
|
|
63d353a078 | ||
|
|
bf7a8b0854 | ||
|
|
09f28cf829 | ||
|
|
58f0cf2916 | ||
|
|
d6a45cced0 | ||
|
|
8cc44c4a79 | ||
|
|
5b93c78ebd | ||
|
|
2085e3ae40 | ||
|
|
3886436841 | ||
|
|
40ce73db68 | ||
|
|
27b3213bac | ||
|
|
829cfb3054 | ||
|
|
3a3d21cd01 | ||
|
|
aa47d8ecf8 | ||
|
|
e8353897eb | ||
|
|
58f9e623a4 | ||
|
|
d871c9b061 | ||
|
|
4da28bcd86 | ||
|
|
f21f05585c | ||
|
|
a606e5f79e | ||
|
|
15bf4c7018 | ||
|
|
efa3acc368 | ||
|
|
64e0ba3b77 | ||
|
|
c9ded8956c | ||
|
|
6cb6b91bd4 | ||
|
|
6470454221 | ||
|
|
a3814f644c | ||
|
|
a89d6dc468 | ||
|
|
ad8f90d12e | ||
|
|
95cb91ffad | ||
|
|
04fd072ae9 | ||
|
|
6201951e3b | ||
|
|
9f1735110c | ||
|
|
440d63ade0 | ||
|
|
09e3eb3e54 | ||
|
|
22074a5fbd | ||
|
|
5a17320c44 | ||
|
|
7548082355 | ||
|
|
36181b0ef8 | ||
|
|
d478d973fd | ||
|
|
d80986e6d0 | ||
|
|
f929bf280f | ||
|
|
0013f64c61 | ||
|
|
a2d2c61f1e | ||
|
|
700e04cc14 | ||
|
|
baa275b05b | ||
|
|
e6d6cae35c | ||
|
|
3facea9a06 | ||
|
|
a1aacbffed | ||
|
|
401402021a | ||
|
|
e7602a164a | ||
|
|
980357509d | ||
|
|
ad021e5758 | ||
|
|
5e2a6778f0 | ||
|
|
296d9a86d0 | ||
|
|
85d83ed9ba | ||
|
|
d2b64fb272 | ||
|
|
8970730ec9 | ||
|
|
bc9ffc1a79 | ||
|
|
46f562e91d | ||
|
|
3695a29b32 | ||
|
|
03eb8834c1 | ||
|
|
79f5fc6908 | ||
|
|
526ddf12d5 | ||
|
|
d3acc71d95 | ||
|
|
3438770f30 | ||
|
|
46d5d464b6 | ||
|
|
01438d630d | ||
|
|
c3d8273caa | ||
|
|
2999f2dcdd | ||
|
|
1676d02d80 | ||
|
|
65e7d0a2cd | ||
|
|
afc9141a7c | ||
|
|
fd8ba951dc | ||
|
|
6374ef8d1b | ||
|
|
9f16e7c272 | ||
|
|
b59cc28dc5 | ||
|
|
9930db8dc7 | ||
|
|
9f902bfbe9 | ||
|
|
5729a32959 | ||
|
|
86616b6936 | ||
|
|
d7a2a12129 | ||
|
|
274573df28 | ||
|
|
20234269e9 | ||
|
|
edc9a14ef5 | ||
|
|
7e6c72a198 | ||
|
|
48ae45550d | ||
|
|
aaaab9deca | ||
|
|
53d10dc597 | ||
|
|
86ef6d2dd6 | ||
|
|
beccba6d95 | ||
|
|
c01441460b | ||
|
|
68ed132e61 | ||
|
|
b9c9424103 | ||
|
|
82eda9a092 | ||
|
|
8e6237e885 | ||
|
|
e99650615d | ||
|
|
58b4dc760e | ||
|
|
131f4a6968 | ||
|
|
533880e9f1 | ||
|
|
9be23b4ff7 | ||
|
|
53af9c6c1e | ||
|
|
51af8c223d | ||
|
|
dbc951a9d0 | ||
|
|
ac3a50a31c | ||
|
|
c111257f63 | ||
|
|
67f0b82ae4 | ||
|
|
0b97aaa302 | ||
|
|
227e7d47ab | ||
|
|
9d73e1d5c1 | ||
|
|
632d73698f | ||
|
|
674e784318 | ||
|
|
d77d8dbdd1 | ||
|
|
58812a568f | ||
|
|
e768e83507 | ||
|
|
dc69e93064 | ||
|
|
832d0d5bfa | ||
|
|
04184b1582 | ||
|
|
70dabf70dd | ||
|
|
f420ac144c | ||
|
|
1bc9242644 | ||
|
|
eb36eed4c0 | ||
|
|
91aacdd955 | ||
|
|
a48e8f357a | ||
|
|
34ef43786c | ||
|
|
fbdf820734 | ||
|
|
aecb95bc92 | ||
|
|
f900cbff0b | ||
|
|
206b695e62 | ||
|
|
c94a0c94f3 | ||
|
|
5599277bf2 | ||
|
|
7a62b348cd | ||
|
|
a907cde399 | ||
|
|
77bbb64feb | ||
|
|
b5aa0b135f | ||
|
|
9976a84d42 | ||
|
|
018406d25c | ||
|
|
639cc302d2 | ||
|
|
3cbdf798a2 | ||
|
|
74ebcc5616 | ||
|
|
0685f401f3 | ||
|
|
235c179d60 | ||
|
|
caa725dc20 | ||
|
|
feff4ad890 | ||
|
|
bee067ad76 | ||
|
|
2e47b23827 | ||
|
|
88bf93a2cc | ||
|
|
3f284889ac | ||
|
|
698acad3bd | ||
|
|
20f462e603 | ||
|
|
11cb1e1f31 | ||
|
|
5b1228e14d | ||
|
|
de1a5baaca | ||
|
|
3ca8814e46 | ||
|
|
7afde45ac6 | ||
|
|
357e889dad | ||
|
|
024f316b3a | ||
|
|
2e1c2cbc6e | ||
|
|
88832596da | ||
|
|
1375854237 | ||
|
|
c7fdd81691 | ||
|
|
276d935cee | ||
|
|
9905b2608b | ||
|
|
06f80f0646 | ||
|
|
cde907e15a | ||
|
|
94c95b44ec | ||
|
|
b8e3ec8b7b | ||
|
|
9a1dc89a99 | ||
|
|
90cee00319 | ||
|
|
1ad4eba17e | ||
|
|
b87c38e232 | ||
|
|
b602c08603 | ||
|
|
b18514d9fe | ||
|
|
e2dffad962 | ||
|
|
3957fa3be6 | ||
|
|
535358b057 | ||
|
|
9f4dbf0002 | ||
|
|
10a5677241 | ||
|
|
f2cedfd08e | ||
|
|
13236d8ebc | ||
|
|
d19f0534e4 | ||
|
|
f5d79a5469 | ||
|
|
88c58237c1 | ||
|
|
03d8c187ed | ||
|
|
d3ed644b9c | ||
|
|
6bfda6743e | ||
|
|
8cf5acd183 | ||
|
|
b0432487cc | ||
|
|
e5fe1b5a92 | ||
|
|
1f403291cc | ||
|
|
edaad85990 | ||
|
|
f4aeecae84 | ||
|
|
9142f28d55 | ||
|
|
23deea0c37 | ||
|
|
cc2d6194a9 | ||
|
|
340528f5af | ||
|
|
66067a3506 | ||
|
|
eed3c518cd | ||
|
|
88b0e8cd7e | ||
|
|
028cb99586 | ||
|
|
37eacb5d61 | ||
|
|
e7956901de | ||
|
|
0e3d7cc8ca | ||
|
|
103c8a4aef | ||
|
|
b7190b6a74 | ||
|
|
ddf976e240 | ||
|
|
d23cc0a239 | ||
|
|
ff5a0d6a45 | ||
|
|
6a1e7d9a20 | ||
|
|
7eb965977a | ||
|
|
48627c0901 | ||
|
|
fbaf96eb38 | ||
|
|
31517db9bf | ||
|
|
d0ecf74836 | ||
|
|
89fb94420a | ||
|
|
3e81762f27 | ||
|
|
06a362225f | ||
|
|
62c5d8e3b9 | ||
|
|
4724fab97e | ||
|
|
b7e94f2b52 | ||
|
|
ad99bbb7e8 | ||
|
|
7a1ddf9c83 | ||
|
|
cc134da1ed | ||
|
|
e6be9ea84e | ||
|
|
718df194fe | ||
|
|
26eb52adf1 | ||
|
|
f5ec49b375 | ||
|
|
9aaa80dcc1 | ||
|
|
3463b8d374 | ||
|
|
16b3787258 | ||
|
|
269f907d1d | ||
|
|
5f2144db0f | ||
|
|
693c159360 | ||
|
|
7a6c42df62 | ||
|
|
9f984054b6 | ||
|
|
1400e57cf7 | ||
|
|
7f4b18104f | ||
|
|
d9452509b5 | ||
|
|
d120da8000 | ||
|
|
bc5ffe1828 | ||
|
|
5211267cd2 | ||
|
|
67e0956042 | ||
|
|
0467933ae9 | ||
|
|
48af2866d2 | ||
|
|
d3922d812a | ||
|
|
2893c4b4ad | ||
|
|
b8dcac03cc | ||
|
|
5f8dc32ed6 | ||
|
|
293ada96c8 | ||
|
|
6c4c4f1e9d | ||
|
|
cbfaf7fd91 | ||
|
|
abc6f68062 | ||
|
|
1331123fb4 | ||
|
|
5f92bc3d8d | ||
|
|
c8c6b63cd2 | ||
|
|
8552b676cb | ||
|
|
fe90278b8e | ||
|
|
37e6722ffb | ||
|
|
7b27fca2ad | ||
|
|
f9919bd72c | ||
|
|
3a72587a9e | ||
|
|
71d88cdcba | ||
|
|
d439b804fe | ||
|
|
84c2ee1e1c | ||
|
|
fecae2228d | ||
|
|
2580b7693c | ||
|
|
a62e713219 | ||
|
|
410780d051 | ||
|
|
bb910e20ac | ||
|
|
31a735ca25 | ||
|
|
3d069dbb1c | ||
|
|
5b3f594b2b | ||
|
|
3643719161 | ||
|
|
d743317069 | ||
|
|
f274181270 | ||
|
|
c90af47f02 | ||
|
|
7496c79ae5 | ||
|
|
11d3d93693 | ||
|
|
277d40761b | ||
|
|
27ec72cdb9 | ||
|
|
e17e8e15c7 | ||
|
|
4b6ec6f310 | ||
|
|
04c55cd602 | ||
|
|
c242ef49a6 | ||
|
|
4502ca7bf4 | ||
|
|
9c72f05fbd | ||
|
|
1fb82a6d7f | ||
|
|
9242f78b88 | ||
|
|
5b9d403b7d | ||
|
|
cc802ebc69 | ||
|
|
0129c97c83 | ||
|
|
fc01b705a5 | ||
|
|
126d080c63 | ||
|
|
d5614eb5ee | ||
|
|
c07b26d568 | ||
|
|
11fcb088e5 | ||
|
|
f889aa1b71 | ||
|
|
ee123efd7d | ||
|
|
94f53aabe1 | ||
|
|
9fe502057e | ||
|
|
17c556607a | ||
|
|
a18938d0b1 | ||
|
|
d08fb78357 | ||
|
|
906d2b6614 | ||
|
|
a09e7ca1cb | ||
|
|
3ad690d624 | ||
|
|
db06096169 | ||
|
|
99d67d531b | ||
|
|
4e8585bf9a | ||
|
|
376e5ba119 | ||
|
|
3395f35b59 | ||
|
|
102b33c3dc | ||
|
|
aa4222588a | ||
|
|
15deb66dff | ||
|
|
4a5da29cfe | ||
|
|
18963546ef | ||
|
|
3404626dd6 | ||
|
|
e970fa1d4d | ||
|
|
e5b3b7a3c0 | ||
|
|
64eb6581e3 | ||
|
|
dc9f6f3917 | ||
|
|
3c2cd5d936 | ||
|
|
6e98744aab | ||
|
|
4beb0d2902 | ||
|
|
9c62ec1d2c |
No files matched your search
@@ -1,14 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
ignore:
|
||||
- dependency-name: "github.com/anyproto/go-chash"
|
||||
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GOPRIVATE: github.com/anyproto
|
||||
GOPRIVATE: github.com/anytypeio
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
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/anyproto
|
||||
export GOPRIVATE=github.com/anytypeio
|
||||
export PATH:=deps:$(PATH)
|
||||
|
||||
proto:
|
||||
@@ -7,19 +7,14 @@ 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/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))
|
||||
@$(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))
|
||||
|
||||
protoc --gogofaster_out=:. $(P_ACL_RECORDS_PATH_PB)/protos/*.proto
|
||||
protoc --gogofaster_out=:. $(P_TREE_CHANGES_PATH_PB)/protos/*.proto
|
||||
protoc --gogofaster_out=:. $(P_CRYPTO_PATH_PB)/protos/*.proto
|
||||
$(eval PKGMAP := $$(P_TREE_CHANGES),$$(P_ACL_RECORDS))
|
||||
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. commonspace/spacesyncproto/protos/*.proto
|
||||
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. commonfile/fileproto/protos/*.proto
|
||||
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. net/streampool/testservice/protos/*.proto
|
||||
protoc --gogofaster_out=:. net/secureservice/handshake/handshakeproto/protos/*.proto
|
||||
protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. coordinator/coordinatorproto/protos/*.proto
|
||||
|
||||
deps:
|
||||
go mod download
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
//go:generate mockgen -destination mock_accountservice/mock_accountservice.go github.com/anyproto/any-sync/accountservice Service
|
||||
//go:generate mockgen -destination mock_accountservice/mock_accountservice.go github.com/anytypeio/any-sync/accountservice Service
|
||||
package accountservice
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/app"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
)
|
||||
|
||||
const CName = "common.accountservice"
|
||||
|
||||
type Service interface {
|
||||
app.Component
|
||||
Account() *accountdata.AccountKeys
|
||||
Account() *accountdata.AccountData
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
PeerId string `yaml:"peerId"`
|
||||
PeerKey string `yaml:"peerKey"`
|
||||
SigningKey string `yaml:"signingKey"`
|
||||
PeerId string `yaml:"peerId"`
|
||||
PeerKey string `yaml:"peerKey"`
|
||||
SigningKey string `yaml:"signingKey"`
|
||||
EncryptionKey string `yaml:"encryptionKey"`
|
||||
}
|
||||
|
||||
type ConfigGetter interface {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package mock_accountservice
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/accountservice"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/accountservice"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
func NewAccountServiceWithAccount(ctrl *gomock.Controller, acc *accountdata.AccountKeys) *MockService {
|
||||
func NewAccountServiceWithAccount(ctrl *gomock.Controller, acc *accountdata.AccountData) *MockService {
|
||||
mock := NewMockService(ctrl)
|
||||
mock.EXPECT().Name().Return(accountservice.CName).AnyTimes()
|
||||
mock.EXPECT().Init(gomock.Any()).AnyTimes()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/accountservice (interfaces: Service)
|
||||
// Source: github.com/anytypeio/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/anyproto/any-sync/app"
|
||||
accountdata "github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
app "github.com/anytypeio/any-sync/app"
|
||||
accountdata "github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
@@ -36,10 +36,10 @@ func (m *MockService) EXPECT() *MockServiceMockRecorder {
|
||||
}
|
||||
|
||||
// Account mocks base method.
|
||||
func (m *MockService) Account() *accountdata.AccountKeys {
|
||||
func (m *MockService) Account() *accountdata.AccountData {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Account")
|
||||
ret0, _ := ret[0].(*accountdata.AccountKeys)
|
||||
ret0, _ := ret[0].(*accountdata.AccountData)
|
||||
return ret0
|
||||
}
|
||||
|
||||
|
||||
+26
-133
@@ -4,11 +4,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"go.uber.org/zap"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -16,15 +15,12 @@ import (
|
||||
|
||||
var (
|
||||
// values of this vars will be defined while compilation
|
||||
AppName, GitCommit, GitBranch, GitState, GitSummary, BuildDate string
|
||||
name string
|
||||
GitCommit, GitBranch, GitState, GitSummary, BuildDate string
|
||||
name string
|
||||
)
|
||||
|
||||
var (
|
||||
log = logger.NewNamed("app")
|
||||
StopDeadline = time.Minute
|
||||
StopWarningAfter = time.Second * 10
|
||||
StartWarningAfter = time.Second * 10
|
||||
log = logger.NewNamed("app")
|
||||
)
|
||||
|
||||
// Component is a minimal interface for a common app.Component
|
||||
@@ -55,13 +51,10 @@ type ComponentStatable interface {
|
||||
// App is the central part of the application
|
||||
// It contains and manages all components
|
||||
type App struct {
|
||||
parent *App
|
||||
components []Component
|
||||
mu sync.RWMutex
|
||||
startStat Stat
|
||||
stopStat Stat
|
||||
deviceState int
|
||||
anySyncVersion string
|
||||
components []Component
|
||||
mu sync.RWMutex
|
||||
startStat StartStat
|
||||
deviceState int
|
||||
}
|
||||
|
||||
// Name returns app name
|
||||
@@ -69,34 +62,23 @@ 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
|
||||
}
|
||||
|
||||
type Stat struct {
|
||||
type StartStat struct {
|
||||
SpentMsPerComp map[string]int64
|
||||
SpentMsTotal int64
|
||||
}
|
||||
|
||||
// StartStat returns total time spent per comp for the last Start
|
||||
func (app *App) StartStat() Stat {
|
||||
// StartStat returns total time spent per comp
|
||||
func (app *App) StartStat() StartStat {
|
||||
app.mu.Lock()
|
||||
defer app.mu.Unlock()
|
||||
return app.startStat
|
||||
}
|
||||
|
||||
// StopStat returns total time spent per comp for the last Close
|
||||
func (app *App) StopStat() Stat {
|
||||
app.mu.Lock()
|
||||
defer app.mu.Unlock()
|
||||
return app.stopStat
|
||||
}
|
||||
|
||||
// VersionDescription return the full info about the build
|
||||
func (app *App) VersionDescription() string {
|
||||
return VersionDescription()
|
||||
@@ -110,16 +92,6 @@ 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 {
|
||||
@@ -139,14 +111,10 @@ func (app *App) Register(s Component) *App {
|
||||
func (app *App) Component(name string) Component {
|
||||
app.mu.RLock()
|
||||
defer app.mu.RUnlock()
|
||||
current := app
|
||||
for current != nil {
|
||||
for _, s := range current.components {
|
||||
if s.Name() == name {
|
||||
return s
|
||||
}
|
||||
for _, s := range app.components {
|
||||
if s.Name() == name {
|
||||
return s
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -164,14 +132,10 @@ func (app *App) MustComponent(name string) Component {
|
||||
func MustComponent[i any](app *App) i {
|
||||
app.mu.RLock()
|
||||
defer app.mu.RUnlock()
|
||||
current := app
|
||||
for current != nil {
|
||||
for _, s := range current.components {
|
||||
if v, ok := s.(i); ok {
|
||||
return v
|
||||
}
|
||||
for _, s := range app.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))
|
||||
@@ -181,13 +145,9 @@ func MustComponent[i any](app *App) i {
|
||||
func (app *App) ComponentNames() (names []string) {
|
||||
app.mu.RLock()
|
||||
defer app.mu.RUnlock()
|
||||
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
|
||||
names = make([]string, len(app.components))
|
||||
for i, c := range app.components {
|
||||
names[i] = c.Name()
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -198,17 +158,7 @@ func (app *App) Start(ctx context.Context) (err error) {
|
||||
app.mu.RLock()
|
||||
defer app.mu.RUnlock()
|
||||
app.startStat.SpentMsPerComp = make(map[string]int64)
|
||||
var currentComponentStarting string
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-time.After(StartWarningAfter):
|
||||
l := statLogger(app.stopStat, log).With(zap.String("in_progress", currentComponentStarting))
|
||||
l.Warn("components start in progress")
|
||||
}
|
||||
}()
|
||||
|
||||
closeServices := func(idx int) {
|
||||
for i := idx; i >= 0; i-- {
|
||||
if serviceClose, ok := app.components[i].(ComponentRunnable); ok {
|
||||
@@ -222,7 +172,7 @@ func (app *App) Start(ctx context.Context) (err error) {
|
||||
for i, s := range app.components {
|
||||
if err = s.Init(app); err != nil {
|
||||
closeServices(i)
|
||||
return fmt.Errorf("can't init service '%s': %w", s.Name(), err)
|
||||
return fmt.Errorf("can't init service '%s': %v", s.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,20 +181,14 @@ func (app *App) Start(ctx context.Context) (err error) {
|
||||
start := time.Now()
|
||||
if err = serviceRun.Run(ctx); err != nil {
|
||||
closeServices(i)
|
||||
return fmt.Errorf("can't run service '%s': %w", serviceRun.Name(), err)
|
||||
return fmt.Errorf("can't run service '%s': %v", serviceRun.Name(), err)
|
||||
}
|
||||
spent := time.Since(start).Milliseconds()
|
||||
app.startStat.SpentMsTotal += spent
|
||||
app.startStat.SpentMsPerComp[s.Name()] = spent
|
||||
}
|
||||
}
|
||||
|
||||
close(done)
|
||||
l := statLogger(app.stopStat, log)
|
||||
if app.startStat.SpentMsTotal > StartWarningAfter.Milliseconds() {
|
||||
l.Warn("all components started")
|
||||
}
|
||||
l.Debug("all components started")
|
||||
log.Debug("all components started")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -259,41 +203,18 @@ func stackAllGoroutines() []byte {
|
||||
}
|
||||
}
|
||||
|
||||
func statLogger(stat Stat, ctxLogger logger.CtxLogger) logger.CtxLogger {
|
||||
l := ctxLogger
|
||||
for k, v := range stat.SpentMsPerComp {
|
||||
l = l.With(zap.Int64(k, v))
|
||||
}
|
||||
l = l.With(zap.Int64("total", stat.SpentMsTotal))
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// Close stops the application
|
||||
// All components with ComponentRunnable implementation will be closed in the reversed order
|
||||
func (app *App) Close(ctx context.Context) error {
|
||||
log.Debug("close components...")
|
||||
app.mu.RLock()
|
||||
defer app.mu.RUnlock()
|
||||
app.stopStat.SpentMsPerComp = make(map[string]int64)
|
||||
var currentComponentStopping string
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-time.After(StopWarningAfter):
|
||||
statLogger(app.stopStat, log).
|
||||
With(zap.String("in_progress", currentComponentStopping)).
|
||||
Warn("components close in progress")
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-time.After(StopDeadline):
|
||||
case <-time.After(time.Minute):
|
||||
_, _ = os.Stderr.Write([]byte("app.Close timeout\n"))
|
||||
_, _ = os.Stderr.Write(stackAllGoroutines())
|
||||
panic("app.Close timeout")
|
||||
@@ -303,27 +224,16 @@ func (app *App) Close(ctx context.Context) error {
|
||||
var errs []string
|
||||
for i := len(app.components) - 1; i >= 0; i-- {
|
||||
if serviceClose, ok := app.components[i].(ComponentRunnable); ok {
|
||||
start := time.Now()
|
||||
currentComponentStopping = app.components[i].Name()
|
||||
if e := serviceClose.Close(ctx); e != nil {
|
||||
errs = append(errs, fmt.Sprintf("Component '%s' close error: %v", serviceClose.Name(), e))
|
||||
}
|
||||
spent := time.Since(start).Milliseconds()
|
||||
app.stopStat.SpentMsTotal += spent
|
||||
app.stopStat.SpentMsPerComp[app.components[i].Name()] = spent
|
||||
}
|
||||
}
|
||||
close(done)
|
||||
if len(errs) > 0 {
|
||||
return errors.New(strings.Join(errs, "\n"))
|
||||
}
|
||||
|
||||
l := statLogger(app.stopStat, log)
|
||||
if app.stopStat.SpentMsTotal > StopWarningAfter.Milliseconds() {
|
||||
l.Warn("all components have been closed")
|
||||
}
|
||||
|
||||
l.Debug("all components have been closed")
|
||||
log.Debug("all components have been closed")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -340,20 +250,3 @@ 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,25 +34,6 @@ 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) {
|
||||
|
||||
+2
-25
@@ -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/anyproto/any-sync/app/ldiff Diff,Remote
|
||||
//go:generate mockgen -destination mock_ldiff/mock_ldiff.go github.com/anytypeio/any-sync/app/ldiff Diff,Remote
|
||||
package ldiff
|
||||
|
||||
import (
|
||||
@@ -51,7 +51,7 @@ var hashersPool = &sync.Pool{
|
||||
},
|
||||
}
|
||||
|
||||
var ErrElementNotFound = errors.New("ldiff: element not found")
|
||||
var ErrElementNotFound = errors.New("element not found")
|
||||
|
||||
// Element of data
|
||||
type Element struct {
|
||||
@@ -88,14 +88,10 @@ type Diff interface {
|
||||
Diff(ctx context.Context, dl Remote) (newIds, changedIds, removedIds []string, err error)
|
||||
// Elements retrieves all elements in the Diff
|
||||
Elements() []Element
|
||||
// Element returns an element by id
|
||||
Element(id string) (Element, error)
|
||||
// Ids retrieves ids of all elements in the Diff
|
||||
Ids() []string
|
||||
// Hash returns hash of all elements in the diff
|
||||
Hash() string
|
||||
// Len returns count of elements in the diff
|
||||
Len() int
|
||||
}
|
||||
|
||||
// Remote interface for using in the Diff
|
||||
@@ -161,12 +157,6 @@ func (d *diff) Ids() (ids []string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d *diff) Len() int {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
return d.sl.Len()
|
||||
}
|
||||
|
||||
func (d *diff) Elements() (elements []Element) {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
@@ -182,19 +172,6 @@ func (d *diff) Elements() (elements []Element) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d *diff) Element(id string) (Element, error) {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
el := d.sl.Get(&element{Element: Element{Id: id}, hash: xxhash.Sum64([]byte(id))})
|
||||
if el == nil {
|
||||
return Element{}, ErrElementNotFound
|
||||
}
|
||||
if e, ok := el.Key().(*element); ok {
|
||||
return e.Element, nil
|
||||
}
|
||||
return Element{}, ErrElementNotFound
|
||||
}
|
||||
|
||||
func (d *diff) Hash() string {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
+5
-54
@@ -3,11 +3,10 @@ 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 +43,7 @@ func TestDiff_Diff(t *testing.T) {
|
||||
d2 := New(16, 16)
|
||||
for i := 0; i < 1000; i++ {
|
||||
id := fmt.Sprint(i)
|
||||
head := uuid.NewString()
|
||||
head := bson.NewObjectId().Hex()
|
||||
d1.Set(Element{
|
||||
Id: id,
|
||||
Head: head,
|
||||
@@ -92,7 +91,7 @@ func TestDiff_Diff(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
d2.Set(Element{
|
||||
Id: fmt.Sprint(i),
|
||||
Head: uuid.NewString(),
|
||||
Head: bson.NewObjectId().Hex(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -108,7 +107,7 @@ func TestDiff_Diff(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
d2.Set(Element{
|
||||
Id: fmt.Sprint(i),
|
||||
Head: uuid.NewString(),
|
||||
Head: bson.NewObjectId().Hex(),
|
||||
})
|
||||
}
|
||||
var cancel func()
|
||||
@@ -123,7 +122,7 @@ func BenchmarkDiff_Ranges(b *testing.B) {
|
||||
d := New(16, 16)
|
||||
for i := 0; i < 10000; i++ {
|
||||
id := fmt.Sprint(i)
|
||||
head := uuid.NewString()
|
||||
head := bson.NewObjectId().Hex()
|
||||
d.Set(Element{
|
||||
Id: id,
|
||||
Head: head,
|
||||
@@ -149,51 +148,3 @@ func TestDiff_Hash(t *testing.T) {
|
||||
assert.NotEmpty(t, h2)
|
||||
assert.NotEqual(t, h1, h2)
|
||||
}
|
||||
|
||||
func TestDiff_Element(t *testing.T) {
|
||||
d := New(16, 16)
|
||||
for i := 0; i < 10; i++ {
|
||||
d.Set(Element{Id: fmt.Sprint("id", i), Head: fmt.Sprint("head", i)})
|
||||
}
|
||||
_, err := d.Element("not found")
|
||||
assert.Equal(t, ErrElementNotFound, err)
|
||||
|
||||
el, err := d.Element("id5")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "head5", el.Head)
|
||||
|
||||
d.Set(Element{"id5", "otherHead"})
|
||||
el, err = d.Element("id5")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "otherHead", el.Head)
|
||||
}
|
||||
|
||||
func TestDiff_Ids(t *testing.T) {
|
||||
d := New(16, 16)
|
||||
var ids []string
|
||||
for i := 0; i < 10; i++ {
|
||||
id := fmt.Sprint("id", i)
|
||||
d.Set(Element{Id: id, Head: fmt.Sprint("head", i)})
|
||||
ids = append(ids, id)
|
||||
}
|
||||
gotIds := d.Ids()
|
||||
sort.Strings(gotIds)
|
||||
assert.Equal(t, ids, gotIds)
|
||||
assert.Equal(t, len(ids), d.Len())
|
||||
}
|
||||
|
||||
func TestDiff_Elements(t *testing.T) {
|
||||
d := New(16, 16)
|
||||
var els []Element
|
||||
for i := 0; i < 10; i++ {
|
||||
id := fmt.Sprint("id", i)
|
||||
el := Element{Id: id, Head: fmt.Sprint("head", i)}
|
||||
d.Set(el)
|
||||
els = append(els, el)
|
||||
}
|
||||
gotEls := d.Elements()
|
||||
sort.Slice(gotEls, func(i, j int) bool {
|
||||
return gotEls[i].Id < gotEls[j].Id
|
||||
})
|
||||
assert.Equal(t, els, gotEls)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/app/ldiff (interfaces: Diff,Remote)
|
||||
// Source: github.com/anytypeio/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/anyproto/any-sync/app/ldiff"
|
||||
ldiff "github.com/anytypeio/any-sync/app/ldiff"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
@@ -52,21 +52,6 @@ func (mr *MockDiffMockRecorder) Diff(arg0, arg1 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Diff", reflect.TypeOf((*MockDiff)(nil).Diff), arg0, arg1)
|
||||
}
|
||||
|
||||
// Element mocks base method.
|
||||
func (m *MockDiff) Element(arg0 string) (ldiff.Element, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Element", arg0)
|
||||
ret0, _ := ret[0].(ldiff.Element)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Element indicates an expected call of Element.
|
||||
func (mr *MockDiffMockRecorder) Element(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Element", reflect.TypeOf((*MockDiff)(nil).Element), arg0)
|
||||
}
|
||||
|
||||
// Elements mocks base method.
|
||||
func (m *MockDiff) Elements() []ldiff.Element {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -109,20 +94,6 @@ func (mr *MockDiffMockRecorder) Ids() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ids", reflect.TypeOf((*MockDiff)(nil).Ids))
|
||||
}
|
||||
|
||||
// Len mocks base method.
|
||||
func (m *MockDiff) Len() int {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Len")
|
||||
ret0, _ := ret[0].(int)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Len indicates an expected call of Len.
|
||||
func (mr *MockDiffMockRecorder) Len() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Len", reflect.TypeOf((*MockDiff)(nil).Len))
|
||||
}
|
||||
|
||||
// Ranges mocks base method.
|
||||
func (m *MockDiff) Ranges(arg0 context.Context, arg1 []ldiff.Range, arg2 []ldiff.RangeResult) ([]ldiff.RangeResult, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
+18
-109
@@ -1,124 +1,33 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
)
|
||||
|
||||
type LogFormat int
|
||||
|
||||
const (
|
||||
ColorizedOutput LogFormat = iota
|
||||
PlaintextOutput
|
||||
JSONOutput
|
||||
)
|
||||
|
||||
type NamedLevel struct {
|
||||
Name string `yaml:"name"`
|
||||
Level string `yaml:"level"`
|
||||
}
|
||||
import "go.uber.org/zap"
|
||||
|
||||
type Config struct {
|
||||
Production bool `yaml:"production"`
|
||||
DefaultLevel string `yaml:"defaultLevel"`
|
||||
Levels []NamedLevel `yaml:"levels"` // first match will be used
|
||||
AddOutputPaths []string `yaml:"outputPaths"`
|
||||
DisableStdErr bool `yaml:"disableStdErr"`
|
||||
Format LogFormat `yaml:"format"`
|
||||
ZapConfig *zap.Config `yaml:"-"` // optional, if set it will be used instead of other config options
|
||||
Production bool `yaml:"production"`
|
||||
DefaultLevel string `yaml:"defaultLevel"`
|
||||
NamedLevels map[string]string `yaml:"namedLevels"`
|
||||
}
|
||||
|
||||
func (l Config) ApplyGlobal() {
|
||||
var conf zap.Config
|
||||
if l.ZapConfig != nil {
|
||||
conf = *l.ZapConfig
|
||||
if l.Production {
|
||||
conf = zap.NewProductionConfig()
|
||||
} else {
|
||||
if l.Production {
|
||||
conf = zap.NewProductionConfig()
|
||||
} else {
|
||||
conf = zap.NewDevelopmentConfig()
|
||||
}
|
||||
encConfig := conf.EncoderConfig
|
||||
switch l.Format {
|
||||
case PlaintextOutput:
|
||||
encConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
conf.Encoding = "console"
|
||||
case JSONOutput:
|
||||
encConfig.MessageKey = "msg"
|
||||
encConfig.TimeKey = "ts"
|
||||
encConfig.LevelKey = "level"
|
||||
encConfig.NameKey = "logger"
|
||||
encConfig.CallerKey = "caller"
|
||||
encConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
conf.Encoding = "json"
|
||||
default:
|
||||
// default is ColorizedOutput
|
||||
conf.Encoding = "console"
|
||||
encConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
}
|
||||
|
||||
conf.EncoderConfig = encConfig
|
||||
if len(l.AddOutputPaths) > 0 {
|
||||
conf.OutputPaths = append(conf.OutputPaths, l.AddOutputPaths...)
|
||||
}
|
||||
if l.DisableStdErr {
|
||||
conf.OutputPaths = slice.Filter(conf.OutputPaths, func(path string) bool {
|
||||
return path != "stderr"
|
||||
})
|
||||
}
|
||||
|
||||
if defaultLevel, err := zap.ParseAtomicLevel(l.DefaultLevel); err == nil {
|
||||
conf.Level = defaultLevel
|
||||
conf = zap.NewDevelopmentConfig()
|
||||
}
|
||||
if defaultLevel, err := zap.ParseAtomicLevel(l.DefaultLevel); err == nil {
|
||||
conf.Level = defaultLevel
|
||||
}
|
||||
var levels = make(map[string]zap.AtomicLevel)
|
||||
for k, v := range l.NamedLevels {
|
||||
if lev, err := zap.ParseAtomicLevel(v); err != nil {
|
||||
levels[k] = lev
|
||||
}
|
||||
}
|
||||
for _, v := range l.Levels {
|
||||
if lev, err := zap.ParseAtomicLevel(v.Level); err == nil {
|
||||
// we need to have a minimum level of all named loggers for the main logger
|
||||
if lev.Level() < conf.Level.Level() {
|
||||
conf.Level.SetLevel(lev.Level())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lg, err := conf.Build()
|
||||
defaultLogger, err := conf.Build()
|
||||
if err != nil {
|
||||
Default().Fatal("can't build logger", zap.Error(err))
|
||||
}
|
||||
SetDefault(lg)
|
||||
SetNamedLevels(l.Levels)
|
||||
}
|
||||
|
||||
// LevelsFromStr parses a string of the form "name1=DEBUG;prefix*=WARN;*=ERROR" into a slice of NamedLevel
|
||||
// it may be useful to parse the log level from the OS env var
|
||||
func LevelsFromStr(s string) (levels []NamedLevel) {
|
||||
for _, kv := range strings.Split(s, ";") {
|
||||
strings.TrimSpace(kv)
|
||||
parts := strings.Split(kv, "=")
|
||||
var key, value string
|
||||
if len(parts) == 1 {
|
||||
key = "*"
|
||||
value = parts[0]
|
||||
_, err := zap.ParseAtomicLevel(value)
|
||||
if err != nil {
|
||||
fmt.Printf("Can't parse log level %s: %s\n", parts[0], err.Error())
|
||||
continue
|
||||
}
|
||||
levels = append(levels, NamedLevel{Name: key, Level: value})
|
||||
} else if len(parts) == 2 {
|
||||
key = parts[0]
|
||||
value = parts[1]
|
||||
}
|
||||
_, err := zap.ParseAtomicLevel(value)
|
||||
if err != nil {
|
||||
fmt.Printf("Can't parse log level %s: %s\n", parts[0], err.Error())
|
||||
continue
|
||||
}
|
||||
levels = append(levels, NamedLevel{Name: key, Level: value})
|
||||
}
|
||||
return levels
|
||||
SetDefault(defaultLogger)
|
||||
SetNamedLevels(levels)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ctxKey uint
|
||||
|
||||
const (
|
||||
ctxKeyFields ctxKey = iota
|
||||
)
|
||||
|
||||
func WithCtx(ctx context.Context, l *zap.Logger) *zap.Logger {
|
||||
return l.With(CtxGetFields(ctx)...)
|
||||
}
|
||||
|
||||
func CtxWithFields(ctx context.Context, fields ...zap.Field) context.Context {
|
||||
existingFields := CtxGetFields(ctx)
|
||||
if existingFields != nil {
|
||||
existingFields = append(existingFields, fields...)
|
||||
}
|
||||
return context.WithValue(ctx, ctxKeyFields, fields)
|
||||
}
|
||||
|
||||
func CtxGetFields(ctx context.Context) (fields []zap.Field) {
|
||||
if v := ctx.Value(ctxKeyFields); v != nil {
|
||||
return v.([]zap.Field)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type CtxLogger struct {
|
||||
*zap.Logger
|
||||
name string
|
||||
}
|
||||
|
||||
func (cl CtxLogger) DebugCtx(ctx context.Context, msg string, fields ...zap.Field) {
|
||||
cl.Logger.Debug(msg, append(CtxGetFields(ctx), fields...)...)
|
||||
}
|
||||
|
||||
func (cl CtxLogger) InfoCtx(ctx context.Context, msg string, fields ...zap.Field) {
|
||||
cl.Logger.Info(msg, append(CtxGetFields(ctx), fields...)...)
|
||||
}
|
||||
|
||||
func (cl CtxLogger) WarnCtx(ctx context.Context, msg string, fields ...zap.Field) {
|
||||
cl.Logger.Warn(msg, append(CtxGetFields(ctx), fields...)...)
|
||||
}
|
||||
|
||||
func (cl CtxLogger) ErrorCtx(ctx context.Context, msg string, fields ...zap.Field) {
|
||||
cl.Logger.Error(msg, append(CtxGetFields(ctx), fields...)...)
|
||||
}
|
||||
|
||||
func (cl CtxLogger) With(fields ...zap.Field) CtxLogger {
|
||||
return CtxLogger{cl.Logger.With(fields...), cl.name}
|
||||
}
|
||||
|
||||
func (cl CtxLogger) Sugar() *zap.SugaredLogger {
|
||||
return NewNamedSugared(cl.name)
|
||||
}
|
||||
+19
-106
@@ -1,137 +1,50 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
logger *zap.Logger
|
||||
loggerConfig zap.Config
|
||||
namedLevels []namedLevel
|
||||
namedGlobs = make(map[string]glob.Glob)
|
||||
namedLoggers = make(map[string]CtxLogger)
|
||||
namedSugarLoggers = make(map[string]*zap.SugaredLogger)
|
||||
mu sync.Mutex
|
||||
defaultLogger *zap.Logger
|
||||
levels = make(map[string]zap.AtomicLevel)
|
||||
loggers = make(map[string]*zap.Logger)
|
||||
)
|
||||
|
||||
type namedLevel struct {
|
||||
name string
|
||||
level zap.AtomicLevel
|
||||
}
|
||||
|
||||
func init() {
|
||||
loggerConfig = zap.NewDevelopmentConfig()
|
||||
logger, _ = loggerConfig.Build()
|
||||
defaultLogger, _ = zap.NewDevelopment()
|
||||
zap.NewProduction()
|
||||
}
|
||||
|
||||
// SetDefault replaces the default logger
|
||||
// you need to call SetNamedLevels after in case you have named loggers,
|
||||
// otherwise they will use the old logger
|
||||
func SetDefault(l *zap.Logger) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
*logger = *l
|
||||
*defaultLogger = *l
|
||||
for name, l := range loggers {
|
||||
*l = *defaultLogger.Named(name)
|
||||
}
|
||||
}
|
||||
|
||||
// SetNamedLevels sets the namedLevels for named loggers
|
||||
// it also supports glob patterns for names, like "app*"
|
||||
// can be racy in case there are existing named loggers
|
||||
// so consider to call only once at the beginning
|
||||
func SetNamedLevels(nls []NamedLevel) {
|
||||
func SetNamedLevels(l map[string]zap.AtomicLevel) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
namedLevels = namedLevels[:0]
|
||||
|
||||
var minLevel = logger.Level()
|
||||
for _, nl := range nls {
|
||||
l, err := zap.ParseAtomicLevel(nl.Level)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
namedLevels = append(namedLevels, namedLevel{name: nl.Name, level: l})
|
||||
g, err := glob.Compile(nl.Name)
|
||||
if err == nil {
|
||||
namedGlobs[nl.Name] = g
|
||||
}
|
||||
|
||||
if l.Level() < minLevel {
|
||||
minLevel = l.Level()
|
||||
}
|
||||
}
|
||||
|
||||
if minLevel < logger.Level() {
|
||||
// recreate logger if the min level is lower than the current min one
|
||||
loggerConfig.Level = zap.NewAtomicLevelAt(minLevel)
|
||||
logger, _ = loggerConfig.Build()
|
||||
}
|
||||
|
||||
for name, nl := range namedLoggers {
|
||||
level := getLevel(name)
|
||||
newCore := zap.New(logger.Core()).Named(name).WithOptions(
|
||||
zap.IncreaseLevel(level),
|
||||
)
|
||||
*(nl.Logger) = *newCore
|
||||
}
|
||||
|
||||
for name, nl := range namedSugarLoggers {
|
||||
level := getLevel(name)
|
||||
newCore := zap.New(logger.Core()).Named(name).WithOptions(
|
||||
zap.IncreaseLevel(level),
|
||||
).Sugar()
|
||||
*(nl) = *newCore
|
||||
}
|
||||
levels = l
|
||||
}
|
||||
|
||||
func Default() *zap.Logger {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return logger
|
||||
return defaultLogger
|
||||
}
|
||||
|
||||
// getLevel returns the level for the given name
|
||||
// it return the first matching name or glob pattern whatever comes first
|
||||
func getLevel(name string) zap.AtomicLevel {
|
||||
for _, nl := range namedLevels {
|
||||
if nl.name == name {
|
||||
return nl.level
|
||||
}
|
||||
if g, ok := namedGlobs[nl.name]; ok && g.Match(name) {
|
||||
return nl.level
|
||||
}
|
||||
}
|
||||
return zap.NewAtomicLevelAt(logger.Level())
|
||||
}
|
||||
|
||||
func NewNamed(name string, fields ...zap.Field) CtxLogger {
|
||||
func NewNamed(name string, fields ...zap.Field) *zap.Logger {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if l, nameExists := namedLoggers[name]; nameExists {
|
||||
return l
|
||||
l := defaultLogger.Named(name)
|
||||
if len(fields) > 0 {
|
||||
l = l.With(fields...)
|
||||
}
|
||||
|
||||
level := getLevel(name)
|
||||
l := zap.New(logger.Core()).Named(name).WithOptions(zap.IncreaseLevel(level),
|
||||
zap.Fields(fields...))
|
||||
|
||||
ctxL := CtxLogger{Logger: l, name: name}
|
||||
namedLoggers[name] = ctxL
|
||||
return ctxL
|
||||
}
|
||||
|
||||
func NewNamedSugared(name string) *zap.SugaredLogger {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if l, nameExists := namedSugarLoggers[name]; nameExists {
|
||||
return l
|
||||
}
|
||||
|
||||
level := getLevel(name)
|
||||
l := zap.New(logger.Core()).Named(name).Sugar().WithOptions(zap.IncreaseLevel(level))
|
||||
namedSugarLoggers[name] = l
|
||||
loggers[name] = l
|
||||
return l
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Test_getLevel1(t *testing.T) {
|
||||
SetNamedLevels([]NamedLevel{
|
||||
{Name: "app", Level: "debug"},
|
||||
{Name: "app*", Level: "info"},
|
||||
{Name: "app.sub", Level: "warn"},
|
||||
{Name: "*", Level: "fatal"},
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
want zap.AtomicLevel
|
||||
}{
|
||||
{
|
||||
name: "app",
|
||||
want: zap.NewAtomicLevelAt(zap.DebugLevel),
|
||||
},
|
||||
{
|
||||
name: "app.aaa",
|
||||
want: zap.NewAtomicLevelAt(zap.InfoLevel),
|
||||
},
|
||||
{
|
||||
name: "app.sub",
|
||||
want: zap.NewAtomicLevelAt(zap.InfoLevel),
|
||||
},
|
||||
{
|
||||
name: "random",
|
||||
want: zap.NewAtomicLevelAt(zap.FatalLevel),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getLevel() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getLevel2(t *testing.T) {
|
||||
SetNamedLevels([]NamedLevel{
|
||||
{Name: "*", Level: "ERROR"},
|
||||
{Name: "app", Level: "info"},
|
||||
{Name: "app.sub", Level: "warn"},
|
||||
{Name: "*", Level: "fatal"},
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
want zap.AtomicLevel
|
||||
}{
|
||||
{
|
||||
name: "app",
|
||||
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
|
||||
},
|
||||
{
|
||||
name: "app.aaa",
|
||||
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
|
||||
},
|
||||
{
|
||||
name: "app.sub",
|
||||
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
|
||||
},
|
||||
{
|
||||
name: "random",
|
||||
want: zap.NewAtomicLevelAt(zap.ErrorLevel),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getLevel() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getLevel3(t *testing.T) {
|
||||
SetNamedLevels([]NamedLevel{
|
||||
{Name: "app", Level: "info"},
|
||||
{Name: "*.sub", Level: "warn"},
|
||||
{Name: "*", Level: "fatal"},
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
want zap.AtomicLevel
|
||||
}{
|
||||
{
|
||||
name: "app",
|
||||
want: zap.NewAtomicLevelAt(zap.InfoLevel),
|
||||
},
|
||||
{
|
||||
name: "app.sub",
|
||||
want: zap.NewAtomicLevelAt(zap.WarnLevel),
|
||||
},
|
||||
{
|
||||
name: "random",
|
||||
want: zap.NewAtomicLevelAt(zap.FatalLevel),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getLevel() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getLevel4(t *testing.T) {
|
||||
SetNamedLevels([]NamedLevel{
|
||||
{Name: "*", Level: "invalid"},
|
||||
{Name: "app", Level: "info"},
|
||||
{Name: "b", Level: "invalid"},
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
want zap.AtomicLevel
|
||||
}{
|
||||
{
|
||||
name: "app",
|
||||
want: zap.NewAtomicLevelAt(zap.InfoLevel),
|
||||
},
|
||||
{
|
||||
name: "app.sub",
|
||||
want: zap.NewAtomicLevelAt(logger.Level()),
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
want: zap.NewAtomicLevelAt(logger.Level()),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getLevel(tt.name); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getLevel() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package ocache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type entryState int
|
||||
|
||||
const (
|
||||
entryStateLoading = iota
|
||||
entryStateActive
|
||||
entryStateClosing
|
||||
entryStateClosed
|
||||
)
|
||||
|
||||
type entry struct {
|
||||
id string
|
||||
state entryState
|
||||
lastUsage time.Time
|
||||
load chan struct{}
|
||||
loadErr error
|
||||
value Object
|
||||
close chan struct{}
|
||||
mx sync.Mutex
|
||||
}
|
||||
|
||||
func newEntry(id string, value Object, state entryState) *entry {
|
||||
return &entry{
|
||||
id: id,
|
||||
load: make(chan struct{}),
|
||||
lastUsage: time.Now(),
|
||||
state: state,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *entry) isActive() bool {
|
||||
e.mx.Lock()
|
||||
defer e.mx.Unlock()
|
||||
return e.state == entryStateActive
|
||||
}
|
||||
|
||||
func (e *entry) isClosing() bool {
|
||||
e.mx.Lock()
|
||||
defer e.mx.Unlock()
|
||||
return e.state == entryStateClosed || e.state == entryStateClosing
|
||||
}
|
||||
|
||||
func (e *entry) waitLoad(ctx context.Context, id string) (value Object, err error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.DebugCtx(ctx, "ctx done while waiting on object load", zap.String("id", id))
|
||||
return nil, ctx.Err()
|
||||
case <-e.load:
|
||||
return e.value, e.loadErr
|
||||
}
|
||||
}
|
||||
|
||||
func (e *entry) waitClose(ctx context.Context, id string) (res bool, err error) {
|
||||
e.mx.Lock()
|
||||
switch e.state {
|
||||
case entryStateClosing:
|
||||
waitCh := e.close
|
||||
e.mx.Unlock()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.DebugCtx(ctx, "ctx done while waiting on object close", zap.String("id", id))
|
||||
return false, ctx.Err()
|
||||
case <-waitCh:
|
||||
return true, nil
|
||||
}
|
||||
case entryStateClosed:
|
||||
e.mx.Unlock()
|
||||
return true, nil
|
||||
default:
|
||||
e.mx.Unlock()
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *entry) setClosing(wait bool) (prevState, curState entryState) {
|
||||
e.mx.Lock()
|
||||
prevState = e.state
|
||||
curState = e.state
|
||||
if e.state == entryStateClosing {
|
||||
waitCh := e.close
|
||||
e.mx.Unlock()
|
||||
if !wait {
|
||||
return
|
||||
}
|
||||
<-waitCh
|
||||
e.mx.Lock()
|
||||
}
|
||||
if e.state != entryStateClosed {
|
||||
e.state = entryStateClosing
|
||||
e.close = make(chan struct{})
|
||||
}
|
||||
curState = e.state
|
||||
e.mx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (e *entry) setActive(chClose bool) {
|
||||
e.mx.Lock()
|
||||
defer e.mx.Unlock()
|
||||
if chClose {
|
||||
close(e.close)
|
||||
}
|
||||
e.state = entryStateActive
|
||||
}
|
||||
|
||||
func (e *entry) setClosed() {
|
||||
e.mx.Lock()
|
||||
defer e.mx.Unlock()
|
||||
close(e.close)
|
||||
e.state = entryStateClosed
|
||||
}
|
||||
+4
-12
@@ -1,22 +1,14 @@
|
||||
package ocache
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"strings"
|
||||
)
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
func WithPrometheus(reg *prometheus.Registry, namespace, subsystem string) Option {
|
||||
if reg == nil {
|
||||
return nil
|
||||
}
|
||||
if subsystem == "" {
|
||||
subsystem = "cache"
|
||||
}
|
||||
nameSplit := strings.Split(namespace, ".")
|
||||
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{
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package ocache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWithPrometheus_MetricsConvertsDots(t *testing.T) {
|
||||
opt := WithPrometheus(prometheus.NewRegistry(), "some.name", "some.system")
|
||||
cache := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
return &testObject{}, nil
|
||||
}, opt).(*oCache)
|
||||
_, err := cache.Get(context.Background(), "id")
|
||||
require.NoError(t, err)
|
||||
require.True(t, strings.Contains(cache.metrics.hit.Desc().String(), "some_name_some_system_hit"))
|
||||
}
|
||||
+178
-88
@@ -3,7 +3,7 @@ package ocache
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"go.uber.org/zap"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -44,6 +44,12 @@ var WithGCPeriod = func(gcPeriod time.Duration) Option {
|
||||
}
|
||||
}
|
||||
|
||||
var WithRefCounter = func(enable bool) Option {
|
||||
return func(cache *oCache) {
|
||||
cache.refCounter = enable
|
||||
}
|
||||
}
|
||||
|
||||
func New(loadFunc LoadFunc, opts ...Option) OCache {
|
||||
c := &oCache{
|
||||
data: make(map[string]*entry),
|
||||
@@ -67,7 +73,33 @@ func New(loadFunc LoadFunc, opts ...Option) OCache {
|
||||
|
||||
type Object interface {
|
||||
Close() (err error)
|
||||
TryClose(objectTTL time.Duration) (res bool, err error)
|
||||
}
|
||||
|
||||
type ObjectLocker interface {
|
||||
Object
|
||||
Locked() bool
|
||||
}
|
||||
|
||||
type ObjectLastUsage interface {
|
||||
LastUsage() time.Time
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
id string
|
||||
lastUsage time.Time
|
||||
refCount uint32
|
||||
isClosing bool
|
||||
load chan struct{}
|
||||
loadErr error
|
||||
value Object
|
||||
close chan struct{}
|
||||
}
|
||||
|
||||
func (e *entry) locked() bool {
|
||||
if locker, ok := e.value.(ObjectLocker); ok {
|
||||
return locker.Locked()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type OCache interface {
|
||||
@@ -84,8 +116,12 @@ type OCache interface {
|
||||
// Add adds new object to cache
|
||||
// Returns error when object exists
|
||||
Add(id string, value Object) (err error)
|
||||
// Release decreases the refs counter
|
||||
Release(id string) bool
|
||||
// Reset sets refs counter to 0
|
||||
Reset(id string) bool
|
||||
// Remove closes and removes object
|
||||
Remove(ctx context.Context, id string) (ok bool, err error)
|
||||
Remove(id string) (ok bool, err error)
|
||||
// ForEach iterates over all loaded objects, breaks when callback returns false
|
||||
ForEach(f func(v Object) (isContinue bool))
|
||||
// GC frees not used and expired objects
|
||||
@@ -98,16 +134,17 @@ type OCache interface {
|
||||
}
|
||||
|
||||
type oCache struct {
|
||||
mu sync.Mutex
|
||||
data map[string]*entry
|
||||
loadFunc LoadFunc
|
||||
timeNow func() time.Time
|
||||
ttl time.Duration
|
||||
gc time.Duration
|
||||
closed bool
|
||||
closeCh chan struct{}
|
||||
log *zap.SugaredLogger
|
||||
metrics *metrics
|
||||
mu sync.Mutex
|
||||
data map[string]*entry
|
||||
loadFunc LoadFunc
|
||||
timeNow func() time.Time
|
||||
ttl time.Duration
|
||||
gc time.Duration
|
||||
closed bool
|
||||
closeCh chan struct{}
|
||||
log *zap.SugaredLogger
|
||||
metrics *metrics
|
||||
refCounter bool
|
||||
}
|
||||
|
||||
func (c *oCache) Get(ctx context.Context, id string) (value Object, err error) {
|
||||
@@ -123,36 +160,63 @@ Load:
|
||||
return nil, ErrClosed
|
||||
}
|
||||
if e, ok = c.data[id]; !ok {
|
||||
e = newEntry(id, nil, entryStateLoading)
|
||||
load = true
|
||||
e = &entry{
|
||||
id: id,
|
||||
load: make(chan struct{}),
|
||||
}
|
||||
c.data[id] = e
|
||||
}
|
||||
e.lastUsage = time.Now()
|
||||
c.mu.Unlock()
|
||||
reload, err := e.waitClose(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
closing := e.isClosing
|
||||
if !e.isClosing {
|
||||
e.lastUsage = c.timeNow()
|
||||
if c.refCounter {
|
||||
e.refCount++
|
||||
}
|
||||
}
|
||||
if reload {
|
||||
c.mu.Unlock()
|
||||
if closing {
|
||||
<-e.close
|
||||
goto Load
|
||||
}
|
||||
|
||||
if load {
|
||||
go c.load(ctx, id, e)
|
||||
}
|
||||
c.metricsGet(!load)
|
||||
return e.waitLoad(ctx, id)
|
||||
if c.metrics != nil {
|
||||
if load {
|
||||
c.metrics.miss.Inc()
|
||||
} else {
|
||||
c.metrics.hit.Inc()
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-e.load:
|
||||
}
|
||||
return e.value, e.loadErr
|
||||
}
|
||||
|
||||
func (c *oCache) Pick(ctx context.Context, id string) (value Object, err error) {
|
||||
c.mu.Lock()
|
||||
val, ok := c.data[id]
|
||||
if !ok || val.isClosing() {
|
||||
if !ok || val.isClosing {
|
||||
c.mu.Unlock()
|
||||
return nil, ErrNotExists
|
||||
}
|
||||
c.mu.Unlock()
|
||||
c.metricsGet(true)
|
||||
return val.waitLoad(ctx, id)
|
||||
|
||||
if c.metrics != nil {
|
||||
c.metrics.hit.Inc()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-val.load:
|
||||
return val.value, val.loadErr
|
||||
}
|
||||
}
|
||||
|
||||
func (c *oCache) load(ctx context.Context, id string, e *entry) {
|
||||
@@ -166,39 +230,63 @@ func (c *oCache) load(ctx context.Context, id string, e *entry) {
|
||||
delete(c.data, id)
|
||||
} else {
|
||||
e.value = value
|
||||
e.setActive(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *oCache) Remove(ctx context.Context, id string) (ok bool, err error) {
|
||||
func (c *oCache) Release(id string) bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.closed {
|
||||
return false
|
||||
}
|
||||
if e, ok := c.data[id]; ok {
|
||||
if c.refCounter && e.refCount > 0 {
|
||||
e.refCount--
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *oCache) Reset(id string) bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.closed {
|
||||
return false
|
||||
}
|
||||
if e, ok := c.data[id]; ok {
|
||||
e.refCount = 0
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *oCache) Remove(id string) (ok bool, err error) {
|
||||
c.mu.Lock()
|
||||
if c.closed {
|
||||
c.mu.Unlock()
|
||||
err = ErrClosed
|
||||
return
|
||||
}
|
||||
e, ok := c.data[id]
|
||||
if !ok {
|
||||
var e *entry
|
||||
e, ok = c.data[id]
|
||||
if !ok || e.isClosing {
|
||||
c.mu.Unlock()
|
||||
return false, ErrNotExists
|
||||
return
|
||||
}
|
||||
e.isClosing = true
|
||||
e.close = make(chan struct{})
|
||||
c.mu.Unlock()
|
||||
return c.remove(ctx, e)
|
||||
}
|
||||
|
||||
func (c *oCache) remove(ctx context.Context, e *entry) (ok bool, err error) {
|
||||
if _, err = e.waitLoad(ctx, e.id); err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, curState := e.setClosing(true)
|
||||
if curState == entryStateClosing {
|
||||
ok = true
|
||||
<-e.load
|
||||
if e.value != nil {
|
||||
err = e.value.Close()
|
||||
c.mu.Lock()
|
||||
e.setClosed()
|
||||
delete(c.data, e.id)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
c.mu.Lock()
|
||||
close(e.close)
|
||||
delete(c.data, e.id)
|
||||
c.mu.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -220,7 +308,13 @@ func (c *oCache) Add(id string, value Object) (err error) {
|
||||
if _, ok := c.data[id]; ok {
|
||||
return ErrExists
|
||||
}
|
||||
e := newEntry(id, value, entryStateActive)
|
||||
e := &entry{
|
||||
id: id,
|
||||
lastUsage: time.Now(),
|
||||
refCount: 0,
|
||||
load: make(chan struct{}),
|
||||
value: value,
|
||||
}
|
||||
close(e.load)
|
||||
c.data[id] = e
|
||||
return
|
||||
@@ -232,7 +326,7 @@ func (c *oCache) ForEach(f func(obj Object) (isContinue bool)) {
|
||||
for _, v := range c.data {
|
||||
select {
|
||||
case <-v.load:
|
||||
if v.value != nil && !v.isClosing() {
|
||||
if v.value != nil && !v.isClosing {
|
||||
objects = append(objects, v.value)
|
||||
}
|
||||
default:
|
||||
@@ -268,35 +362,40 @@ func (c *oCache) GC() {
|
||||
deadline := c.timeNow().Add(-c.ttl)
|
||||
var toClose []*entry
|
||||
for _, e := range c.data {
|
||||
if e.isActive() && e.lastUsage.Before(deadline) {
|
||||
if e.isClosing {
|
||||
continue
|
||||
}
|
||||
lu := e.lastUsage
|
||||
if lug, ok := e.value.(ObjectLastUsage); ok {
|
||||
lu = lug.LastUsage()
|
||||
}
|
||||
if !e.locked() && e.refCount <= 0 && lu.Before(deadline) {
|
||||
e.isClosing = true
|
||||
e.close = make(chan struct{})
|
||||
toClose = append(toClose, e)
|
||||
}
|
||||
}
|
||||
size := len(c.data)
|
||||
c.mu.Unlock()
|
||||
closedNum := 0
|
||||
|
||||
for _, e := range toClose {
|
||||
prevState, _ := e.setClosing(false)
|
||||
if prevState == entryStateClosing || prevState == entryStateClosed {
|
||||
continue
|
||||
}
|
||||
closed, err := e.value.TryClose(c.ttl)
|
||||
if err != nil {
|
||||
c.log.With("object_id", e.id).Warnf("GC: object close error: %v", err)
|
||||
}
|
||||
if !closed {
|
||||
e.setActive(true)
|
||||
continue
|
||||
} else {
|
||||
closedNum++
|
||||
c.mu.Lock()
|
||||
e.setClosed()
|
||||
delete(c.data, e.id)
|
||||
c.mu.Unlock()
|
||||
<-e.load
|
||||
if e.value != nil {
|
||||
if err := e.value.Close(); err != nil {
|
||||
c.log.With("object_id", e.id).Warnf("GC: object close error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
c.metricsClosed(closedNum, size)
|
||||
c.log.Infof("GC: removed %d; cache size: %d", len(toClose), size)
|
||||
if len(toClose) > 0 && c.metrics != nil {
|
||||
c.metrics.gc.Add(float64(len(toClose)))
|
||||
}
|
||||
c.mu.Lock()
|
||||
for _, e := range toClose {
|
||||
close(e.close)
|
||||
delete(c.data, e.id)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *oCache) Len() int {
|
||||
@@ -313,34 +412,25 @@ func (c *oCache) Close() (err error) {
|
||||
}
|
||||
c.closed = true
|
||||
close(c.closeCh)
|
||||
var toClose []*entry
|
||||
var toClose, alreadyClosing []*entry
|
||||
for _, e := range c.data {
|
||||
toClose = append(toClose, e)
|
||||
if e.isClosing {
|
||||
alreadyClosing = append(alreadyClosing, e)
|
||||
} else {
|
||||
toClose = append(toClose, e)
|
||||
}
|
||||
}
|
||||
c.mu.Unlock()
|
||||
for _, e := range toClose {
|
||||
if _, err := c.remove(context.Background(), e); err != nil && err != ErrNotExists {
|
||||
c.log.With("object_id", e.id).Warnf("cache close: object close error: %v", err)
|
||||
<-e.load
|
||||
if e.value != nil {
|
||||
if clErr := e.value.Close(); clErr != nil {
|
||||
c.log.With("object_id", e.id).Warnf("cache close: object close error: %v", clErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, e := range alreadyClosing {
|
||||
<-e.close
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *oCache) metricsGet(hit bool) {
|
||||
if c.metrics == nil {
|
||||
return
|
||||
}
|
||||
if hit {
|
||||
c.metrics.hit.Inc()
|
||||
} else {
|
||||
c.metrics.miss.Inc()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *oCache) metricsClosed(closedLen, size int) {
|
||||
c.log.Infof("GC: removed %d; cache size: %d", closedLen, size)
|
||||
if c.metrics == nil || closedLen == 0 {
|
||||
return
|
||||
}
|
||||
c.metrics.gc.Add(float64(closedLen))
|
||||
}
|
||||
+50
-335
@@ -3,8 +3,6 @@ package ocache
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -13,48 +11,26 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
type testObject struct {
|
||||
name string
|
||||
closeErr error
|
||||
closeCh chan struct{}
|
||||
tryReturn bool
|
||||
closeCalled bool
|
||||
tryCloseCalled bool
|
||||
name string
|
||||
closeErr error
|
||||
closeCh chan struct{}
|
||||
}
|
||||
|
||||
func NewTestObject(name string, tryReturn bool, closeCh chan struct{}) *testObject {
|
||||
func NewTestObject(name string, closeCh chan struct{}) *testObject {
|
||||
return &testObject{
|
||||
name: name,
|
||||
closeCh: closeCh,
|
||||
tryReturn: tryReturn,
|
||||
name: name,
|
||||
closeCh: closeCh,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *testObject) Close() (err error) {
|
||||
if t.closeCalled || (t.tryCloseCalled && t.tryReturn) {
|
||||
panic("close called twice")
|
||||
}
|
||||
t.closeCalled = true
|
||||
if t.closeCh != nil {
|
||||
<-t.closeCh
|
||||
}
|
||||
return t.closeErr
|
||||
}
|
||||
|
||||
func (t *testObject) TryClose(objectTTL time.Duration) (res bool, err error) {
|
||||
if t.closeCalled || (t.tryCloseCalled && t.tryReturn) {
|
||||
panic("close called twice")
|
||||
}
|
||||
t.tryCloseCalled = true
|
||||
if t.closeCh != nil {
|
||||
<-t.closeCh
|
||||
return t.tryReturn, t.closeErr
|
||||
}
|
||||
return t.tryReturn, nil
|
||||
}
|
||||
|
||||
func TestOCache_Get(t *testing.T) {
|
||||
t.Run("successful", func(t *testing.T) {
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
@@ -140,37 +116,42 @@ func TestOCache_Get(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOCache_GC(t *testing.T) {
|
||||
t.Run("test gc expired object", func(t *testing.T) {
|
||||
t.Run("test without close wait", func(t *testing.T) {
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
return NewTestObject(id, true, nil), nil
|
||||
}, WithTTL(time.Millisecond*10))
|
||||
return &testObject{name: id}, nil
|
||||
}, WithTTL(time.Millisecond*10), WithRefCounter(true))
|
||||
val, err := c.Get(context.TODO(), "id")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
assert.Equal(t, 1, c.Len())
|
||||
c.GC()
|
||||
assert.Equal(t, 1, c.Len())
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
c.GC()
|
||||
assert.Equal(t, 1, c.Len())
|
||||
assert.True(t, c.Release("id"))
|
||||
c.GC()
|
||||
assert.Equal(t, 0, c.Len())
|
||||
assert.False(t, c.Release("id"))
|
||||
})
|
||||
t.Run("test gc tryClose true, close before get", func(t *testing.T) {
|
||||
t.Run("test with close wait", func(t *testing.T) {
|
||||
closeCh := make(chan struct{})
|
||||
getCh := make(chan struct{})
|
||||
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
return NewTestObject(id, true, closeCh), nil
|
||||
}, WithTTL(time.Millisecond*10))
|
||||
return NewTestObject(id, closeCh), nil
|
||||
}, WithTTL(time.Millisecond*10), WithRefCounter(true))
|
||||
val, err := c.Get(context.TODO(), "id")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
assert.Equal(t, 1, c.Len())
|
||||
assert.True(t, c.Release("id"))
|
||||
// making ttl pass
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
time.Sleep(time.Millisecond * 40)
|
||||
// first gc will be run after 20 secs, so calling it manually
|
||||
go c.GC()
|
||||
// waiting until all objects are marked as closing
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
time.Sleep(time.Millisecond * 40)
|
||||
var events []string
|
||||
go func() {
|
||||
_, err := c.Get(context.TODO(), "id")
|
||||
@@ -179,313 +160,47 @@ func TestOCache_GC(t *testing.T) {
|
||||
events = append(events, "get")
|
||||
close(getCh)
|
||||
}()
|
||||
// sleeping to make sure that Get is called
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
events = append(events, "close")
|
||||
// sleeping to make sure that Get is called
|
||||
time.Sleep(time.Millisecond * 40)
|
||||
close(closeCh)
|
||||
|
||||
<-getCh
|
||||
require.Equal(t, []string{"close", "get"}, events)
|
||||
})
|
||||
t.Run("test gc tryClose false, many parallel get", func(t *testing.T) {
|
||||
timesCalled := &atomic.Int32{}
|
||||
obj := NewTestObject("id", false, nil)
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
timesCalled.Add(1)
|
||||
return obj, nil
|
||||
}, WithTTL(0))
|
||||
|
||||
val, err := c.Get(context.TODO(), "id")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
assert.Equal(t, 1, c.Len())
|
||||
begin := make(chan struct{})
|
||||
wg := sync.WaitGroup{}
|
||||
once := sync.Once{}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
<-begin
|
||||
c.GC()
|
||||
wg.Done()
|
||||
}()
|
||||
for i := 0; i < 50; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
once.Do(func() {
|
||||
close(begin)
|
||||
})
|
||||
if i%2 != 0 {
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
_, err := c.Get(context.TODO(), "id")
|
||||
require.NoError(t, err)
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
wg.Wait()
|
||||
require.Equal(t, timesCalled.Load(), int32(1))
|
||||
require.True(t, obj.tryCloseCalled)
|
||||
})
|
||||
t.Run("test gc tryClose different, many objects", func(t *testing.T) {
|
||||
tryCloseIds := make(map[string]bool)
|
||||
called := make(map[string]int)
|
||||
max := 1000
|
||||
getId := func(i int) string {
|
||||
return fmt.Sprintf("id%d", i)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
if i%2 == 1 {
|
||||
tryCloseIds[getId(i)] = true
|
||||
} else {
|
||||
tryCloseIds[getId(i)] = false
|
||||
}
|
||||
}
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
called[id] = called[id] + 1
|
||||
return NewTestObject(id, tryCloseIds[id], nil), nil
|
||||
}, WithTTL(time.Millisecond*10))
|
||||
|
||||
for i := 0; i < max; i++ {
|
||||
val, err := c.Get(context.TODO(), getId(i))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
}
|
||||
assert.Equal(t, max, c.Len())
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
c.GC()
|
||||
for i := 0; i < max; i++ {
|
||||
val, err := c.Get(context.TODO(), getId(i))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
val, err := c.Get(context.TODO(), getId(i))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
require.Equal(t, called[getId(i)], i%2+1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_OCache_Remove(t *testing.T) {
|
||||
t.Run("remove simple", func(t *testing.T) {
|
||||
closeCh := make(chan struct{})
|
||||
getCh := make(chan struct{})
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
return NewTestObject(id, false, closeCh), nil
|
||||
}, WithTTL(time.Millisecond*10))
|
||||
closeCh := make(chan struct{})
|
||||
getCh := make(chan struct{})
|
||||
|
||||
val, err := c.Get(context.TODO(), "id")
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
return NewTestObject(id, closeCh), nil
|
||||
}, WithTTL(time.Millisecond*10))
|
||||
val, err := c.Get(context.TODO(), "id")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
assert.Equal(t, 1, c.Len())
|
||||
// removing the object, so we will wait on closing
|
||||
go func() {
|
||||
_, err := c.Remove("id")
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
time.Sleep(time.Millisecond * 40)
|
||||
|
||||
var events []string
|
||||
go func() {
|
||||
_, err := c.Get(context.TODO(), "id")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
assert.Equal(t, 1, c.Len())
|
||||
// removing the object, so we will wait on closing
|
||||
go func() {
|
||||
_, err := c.Remove(ctx, "id")
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
events = append(events, "get")
|
||||
close(getCh)
|
||||
}()
|
||||
events = append(events, "close")
|
||||
// sleeping to make sure that Get is called
|
||||
time.Sleep(time.Millisecond * 40)
|
||||
close(closeCh)
|
||||
|
||||
var events []string
|
||||
go func() {
|
||||
_, err := c.Get(context.TODO(), "id")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
events = append(events, "get")
|
||||
close(getCh)
|
||||
}()
|
||||
// sleeping to make sure that Get is called
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
events = append(events, "close")
|
||||
close(closeCh)
|
||||
|
||||
<-getCh
|
||||
require.Equal(t, []string{"close", "get"}, events)
|
||||
})
|
||||
t.Run("test remove while gc, tryClose false", func(t *testing.T) {
|
||||
closeCh := make(chan struct{})
|
||||
removeCh := make(chan struct{})
|
||||
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
return NewTestObject(id, false, closeCh), nil
|
||||
}, WithTTL(time.Millisecond*10))
|
||||
val, err := c.Get(context.TODO(), "id")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
assert.Equal(t, 1, c.Len())
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
go c.GC()
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
var events []string
|
||||
go func() {
|
||||
ok, err := c.Remove(ctx, "id")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
events = append(events, "remove")
|
||||
close(removeCh)
|
||||
}()
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
events = append(events, "close")
|
||||
close(closeCh)
|
||||
|
||||
<-removeCh
|
||||
require.Equal(t, []string{"close", "remove"}, events)
|
||||
})
|
||||
t.Run("test remove while gc, tryClose true", func(t *testing.T) {
|
||||
closeCh := make(chan struct{})
|
||||
removeCh := make(chan struct{})
|
||||
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
return NewTestObject(id, true, closeCh), nil
|
||||
}, WithTTL(time.Millisecond*10))
|
||||
val, err := c.Get(context.TODO(), "id")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
assert.Equal(t, 1, c.Len())
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
go c.GC()
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
var events []string
|
||||
go func() {
|
||||
ok, err := c.Remove(ctx, "id")
|
||||
require.NoError(t, err)
|
||||
require.False(t, ok)
|
||||
events = append(events, "remove")
|
||||
close(removeCh)
|
||||
}()
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
events = append(events, "close")
|
||||
close(closeCh)
|
||||
|
||||
<-removeCh
|
||||
require.Equal(t, []string{"close", "remove"}, events)
|
||||
})
|
||||
t.Run("test gc while remove, tryClose true", func(t *testing.T) {
|
||||
closeCh := make(chan struct{})
|
||||
removeCh := make(chan struct{})
|
||||
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
return NewTestObject(id, true, closeCh), nil
|
||||
}, WithTTL(time.Millisecond*10))
|
||||
val, err := c.Get(context.TODO(), "id")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
assert.Equal(t, 1, c.Len())
|
||||
go func() {
|
||||
ok, err := c.Remove(ctx, "id")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
close(removeCh)
|
||||
}()
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
c.GC()
|
||||
close(closeCh)
|
||||
<-removeCh
|
||||
})
|
||||
}
|
||||
|
||||
func TestOCacheFuzzy(t *testing.T) {
|
||||
t.Run("test many objects gc, get and remove simultaneously, close after", func(t *testing.T) {
|
||||
tryCloseIds := make(map[string]bool)
|
||||
max := 2000
|
||||
getId := func(i int) string {
|
||||
return fmt.Sprintf("id%d", i)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
if i%2 == 1 {
|
||||
tryCloseIds[getId(i)] = true
|
||||
} else {
|
||||
tryCloseIds[getId(i)] = false
|
||||
}
|
||||
}
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
return NewTestObject(id, tryCloseIds[id], nil), nil
|
||||
}, WithTTL(time.Nanosecond))
|
||||
|
||||
stopGC := make(chan struct{})
|
||||
wg := sync.WaitGroup{}
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-stopGC:
|
||||
return
|
||||
default:
|
||||
c.GC()
|
||||
}
|
||||
}
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 10; j++ {
|
||||
for i := 0; i < max; i++ {
|
||||
val, err := c.Get(context.TODO(), getId(i))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
}
|
||||
}
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 10; j++ {
|
||||
for i := 0; i < max; i++ {
|
||||
c.Remove(ctx, getId(i))
|
||||
}
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
close(stopGC)
|
||||
err := c.Close()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, c.Len())
|
||||
})
|
||||
t.Run("test many objects gc, get, remove and close simultaneously", func(t *testing.T) {
|
||||
tryCloseIds := make(map[string]bool)
|
||||
max := 2000
|
||||
getId := func(i int) string {
|
||||
return fmt.Sprintf("id%d", i)
|
||||
}
|
||||
for i := 0; i < max; i++ {
|
||||
if i%2 == 1 {
|
||||
tryCloseIds[getId(i)] = true
|
||||
} else {
|
||||
tryCloseIds[getId(i)] = false
|
||||
}
|
||||
}
|
||||
c := New(func(ctx context.Context, id string) (value Object, err error) {
|
||||
return NewTestObject(id, tryCloseIds[id], nil), nil
|
||||
}, WithTTL(time.Nanosecond))
|
||||
|
||||
go func() {
|
||||
for {
|
||||
c.GC()
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for j := 0; j < 10; j++ {
|
||||
for i := 0; i < max; i++ {
|
||||
val, err := c.Get(context.TODO(), getId(i))
|
||||
if err == ErrClosed {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, val)
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for j := 0; j < 10; j++ {
|
||||
for i := 0; i < max; i++ {
|
||||
c.Remove(ctx, getId(i))
|
||||
}
|
||||
}
|
||||
}()
|
||||
time.Sleep(time.Millisecond)
|
||||
err := c.Close()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, c.Len())
|
||||
})
|
||||
<-getCh
|
||||
require.Equal(t, []string{"close", "get"}, events)
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package fileblockstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonfile/fileproto/fileprotoerr"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonfile/fileproto/fileprotoerr"
|
||||
blocks "github.com/ipfs/go-block-format"
|
||||
"github.com/ipfs/go-cid"
|
||||
)
|
||||
@@ -21,7 +21,6 @@ type ctxKey uint
|
||||
|
||||
const (
|
||||
ctxKeySpaceId ctxKey = iota
|
||||
ctxKeyFileId
|
||||
)
|
||||
|
||||
type BlockStore interface {
|
||||
@@ -49,12 +48,3 @@ func CtxGetSpaceId(ctx context.Context) (spaceId string) {
|
||||
spaceId, _ = ctx.Value(ctxKeySpaceId).(string)
|
||||
return
|
||||
}
|
||||
|
||||
func CtxWithFileId(ctx context.Context, spaceId string) context.Context {
|
||||
return context.WithValue(ctx, ctxKeyFileId, spaceId)
|
||||
}
|
||||
|
||||
func CtxGetFileId(ctx context.Context) (spaceId string) {
|
||||
spaceId, _ = ctx.Value(ctxKeyFileId).(string)
|
||||
return
|
||||
}
|
||||
+369
-2543
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,5 @@
|
||||
// Code generated by protoc-gen-go-drpc. DO NOT EDIT.
|
||||
// protoc-gen-go-drpc version: v0.0.33
|
||||
// protoc-gen-go-drpc version: v0.0.32
|
||||
// source: commonfile/fileproto/protos/file.proto
|
||||
|
||||
package fileproto
|
||||
@@ -40,14 +40,10 @@ func (drpcEncoding_File_commonfile_fileproto_protos_file_proto) JSONUnmarshal(bu
|
||||
type DRPCFileClient interface {
|
||||
DRPCConn() drpc.Conn
|
||||
|
||||
BlockGet(ctx context.Context, in *BlockGetRequest) (*BlockGetResponse, error)
|
||||
BlockPush(ctx context.Context, in *BlockPushRequest) (*BlockPushResponse, error)
|
||||
BlocksCheck(ctx context.Context, in *BlocksCheckRequest) (*BlocksCheckResponse, error)
|
||||
BlocksBind(ctx context.Context, in *BlocksBindRequest) (*BlocksBindResponse, error)
|
||||
FilesDelete(ctx context.Context, in *FilesDeleteRequest) (*FilesDeleteResponse, error)
|
||||
FilesInfo(ctx context.Context, in *FilesInfoRequest) (*FilesInfoResponse, error)
|
||||
GetBlocks(ctx context.Context) (DRPCFile_GetBlocksClient, error)
|
||||
PushBlock(ctx context.Context, in *PushBlockRequest) (*PushBlockResponse, error)
|
||||
DeleteBlocks(ctx context.Context, in *DeleteBlocksRequest) (*DeleteBlocksResponse, error)
|
||||
Check(ctx context.Context, in *CheckRequest) (*CheckResponse, error)
|
||||
SpaceInfo(ctx context.Context, in *SpaceInfoRequest) (*SpaceInfoResponse, error)
|
||||
}
|
||||
|
||||
type drpcFileClient struct {
|
||||
@@ -60,54 +56,53 @@ func NewDRPCFileClient(cc drpc.Conn) DRPCFileClient {
|
||||
|
||||
func (c *drpcFileClient) DRPCConn() drpc.Conn { return c.cc }
|
||||
|
||||
func (c *drpcFileClient) BlockGet(ctx context.Context, in *BlockGetRequest) (*BlockGetResponse, error) {
|
||||
out := new(BlockGetResponse)
|
||||
err := c.cc.Invoke(ctx, "/filesync.File/BlockGet", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
|
||||
func (c *drpcFileClient) GetBlocks(ctx context.Context) (DRPCFile_GetBlocksClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, "/anyFile.File/GetBlocks", drpcEncoding_File_commonfile_fileproto_protos_file_proto{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &drpcFile_GetBlocksClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type DRPCFile_GetBlocksClient interface {
|
||||
drpc.Stream
|
||||
Send(*GetBlockRequest) error
|
||||
Recv() (*GetBlockResponse, error)
|
||||
}
|
||||
|
||||
type drpcFile_GetBlocksClient struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcFile_GetBlocksClient) Send(m *GetBlockRequest) error {
|
||||
return x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{})
|
||||
}
|
||||
|
||||
func (x *drpcFile_GetBlocksClient) Recv() (*GetBlockResponse, error) {
|
||||
m := new(GetBlockResponse)
|
||||
if err := x.MsgRecv(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (x *drpcFile_GetBlocksClient) RecvMsg(m *GetBlockResponse) error {
|
||||
return x.MsgRecv(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{})
|
||||
}
|
||||
|
||||
func (c *drpcFileClient) PushBlock(ctx context.Context, in *PushBlockRequest) (*PushBlockResponse, error) {
|
||||
out := new(PushBlockResponse)
|
||||
err := c.cc.Invoke(ctx, "/anyFile.File/PushBlock", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcFileClient) BlockPush(ctx context.Context, in *BlockPushRequest) (*BlockPushResponse, error) {
|
||||
out := new(BlockPushResponse)
|
||||
err := c.cc.Invoke(ctx, "/filesync.File/BlockPush", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcFileClient) BlocksCheck(ctx context.Context, in *BlocksCheckRequest) (*BlocksCheckResponse, error) {
|
||||
out := new(BlocksCheckResponse)
|
||||
err := c.cc.Invoke(ctx, "/filesync.File/BlocksCheck", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcFileClient) BlocksBind(ctx context.Context, in *BlocksBindRequest) (*BlocksBindResponse, error) {
|
||||
out := new(BlocksBindResponse)
|
||||
err := c.cc.Invoke(ctx, "/filesync.File/BlocksBind", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcFileClient) FilesDelete(ctx context.Context, in *FilesDeleteRequest) (*FilesDeleteResponse, error) {
|
||||
out := new(FilesDeleteResponse)
|
||||
err := c.cc.Invoke(ctx, "/filesync.File/FilesDelete", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcFileClient) FilesInfo(ctx context.Context, in *FilesInfoRequest) (*FilesInfoResponse, error) {
|
||||
out := new(FilesInfoResponse)
|
||||
err := c.cc.Invoke(ctx, "/filesync.File/FilesInfo", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
|
||||
func (c *drpcFileClient) DeleteBlocks(ctx context.Context, in *DeleteBlocksRequest) (*DeleteBlocksResponse, error) {
|
||||
out := new(DeleteBlocksResponse)
|
||||
err := c.cc.Invoke(ctx, "/anyFile.File/DeleteBlocks", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -116,16 +111,7 @@ func (c *drpcFileClient) FilesInfo(ctx context.Context, in *FilesInfoRequest) (*
|
||||
|
||||
func (c *drpcFileClient) Check(ctx context.Context, in *CheckRequest) (*CheckResponse, error) {
|
||||
out := new(CheckResponse)
|
||||
err := c.cc.Invoke(ctx, "/filesync.File/Check", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcFileClient) SpaceInfo(ctx context.Context, in *SpaceInfoRequest) (*SpaceInfoResponse, error) {
|
||||
out := new(SpaceInfoResponse)
|
||||
err := c.cc.Invoke(ctx, "/filesync.File/SpaceInfo", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
|
||||
err := c.cc.Invoke(ctx, "/anyFile.File/Check", drpcEncoding_File_commonfile_fileproto_protos_file_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,39 +119,23 @@ func (c *drpcFileClient) SpaceInfo(ctx context.Context, in *SpaceInfoRequest) (*
|
||||
}
|
||||
|
||||
type DRPCFileServer interface {
|
||||
BlockGet(context.Context, *BlockGetRequest) (*BlockGetResponse, error)
|
||||
BlockPush(context.Context, *BlockPushRequest) (*BlockPushResponse, error)
|
||||
BlocksCheck(context.Context, *BlocksCheckRequest) (*BlocksCheckResponse, error)
|
||||
BlocksBind(context.Context, *BlocksBindRequest) (*BlocksBindResponse, error)
|
||||
FilesDelete(context.Context, *FilesDeleteRequest) (*FilesDeleteResponse, error)
|
||||
FilesInfo(context.Context, *FilesInfoRequest) (*FilesInfoResponse, error)
|
||||
GetBlocks(DRPCFile_GetBlocksStream) error
|
||||
PushBlock(context.Context, *PushBlockRequest) (*PushBlockResponse, error)
|
||||
DeleteBlocks(context.Context, *DeleteBlocksRequest) (*DeleteBlocksResponse, error)
|
||||
Check(context.Context, *CheckRequest) (*CheckResponse, error)
|
||||
SpaceInfo(context.Context, *SpaceInfoRequest) (*SpaceInfoResponse, error)
|
||||
}
|
||||
|
||||
type DRPCFileUnimplementedServer struct{}
|
||||
|
||||
func (s *DRPCFileUnimplementedServer) BlockGet(context.Context, *BlockGetRequest) (*BlockGetResponse, error) {
|
||||
func (s *DRPCFileUnimplementedServer) GetBlocks(DRPCFile_GetBlocksStream) error {
|
||||
return drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
func (s *DRPCFileUnimplementedServer) PushBlock(context.Context, *PushBlockRequest) (*PushBlockResponse, error) {
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
func (s *DRPCFileUnimplementedServer) BlockPush(context.Context, *BlockPushRequest) (*BlockPushResponse, error) {
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
func (s *DRPCFileUnimplementedServer) BlocksCheck(context.Context, *BlocksCheckRequest) (*BlocksCheckResponse, error) {
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
func (s *DRPCFileUnimplementedServer) BlocksBind(context.Context, *BlocksBindRequest) (*BlocksBindResponse, error) {
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
func (s *DRPCFileUnimplementedServer) FilesDelete(context.Context, *FilesDeleteRequest) (*FilesDeleteResponse, error) {
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
func (s *DRPCFileUnimplementedServer) FilesInfo(context.Context, *FilesInfoRequest) (*FilesInfoResponse, error) {
|
||||
func (s *DRPCFileUnimplementedServer) DeleteBlocks(context.Context, *DeleteBlocksRequest) (*DeleteBlocksResponse, error) {
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
@@ -173,72 +143,40 @@ func (s *DRPCFileUnimplementedServer) Check(context.Context, *CheckRequest) (*Ch
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
func (s *DRPCFileUnimplementedServer) SpaceInfo(context.Context, *SpaceInfoRequest) (*SpaceInfoResponse, error) {
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
type DRPCFileDescription struct{}
|
||||
|
||||
func (DRPCFileDescription) NumMethods() int { return 8 }
|
||||
func (DRPCFileDescription) NumMethods() int { return 4 }
|
||||
|
||||
func (DRPCFileDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) {
|
||||
switch n {
|
||||
case 0:
|
||||
return "/filesync.File/BlockGet", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
|
||||
return "/anyFile.File/GetBlocks", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCFileServer).
|
||||
BlockGet(
|
||||
ctx,
|
||||
in1.(*BlockGetRequest),
|
||||
return nil, srv.(DRPCFileServer).
|
||||
GetBlocks(
|
||||
&drpcFile_GetBlocksStream{in1.(drpc.Stream)},
|
||||
)
|
||||
}, DRPCFileServer.BlockGet, true
|
||||
}, DRPCFileServer.GetBlocks, true
|
||||
case 1:
|
||||
return "/filesync.File/BlockPush", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
|
||||
return "/anyFile.File/PushBlock", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCFileServer).
|
||||
BlockPush(
|
||||
PushBlock(
|
||||
ctx,
|
||||
in1.(*BlockPushRequest),
|
||||
in1.(*PushBlockRequest),
|
||||
)
|
||||
}, DRPCFileServer.BlockPush, true
|
||||
}, DRPCFileServer.PushBlock, true
|
||||
case 2:
|
||||
return "/filesync.File/BlocksCheck", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
|
||||
return "/anyFile.File/DeleteBlocks", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCFileServer).
|
||||
BlocksCheck(
|
||||
DeleteBlocks(
|
||||
ctx,
|
||||
in1.(*BlocksCheckRequest),
|
||||
in1.(*DeleteBlocksRequest),
|
||||
)
|
||||
}, DRPCFileServer.BlocksCheck, true
|
||||
}, DRPCFileServer.DeleteBlocks, true
|
||||
case 3:
|
||||
return "/filesync.File/BlocksBind", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCFileServer).
|
||||
BlocksBind(
|
||||
ctx,
|
||||
in1.(*BlocksBindRequest),
|
||||
)
|
||||
}, DRPCFileServer.BlocksBind, true
|
||||
case 4:
|
||||
return "/filesync.File/FilesDelete", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCFileServer).
|
||||
FilesDelete(
|
||||
ctx,
|
||||
in1.(*FilesDeleteRequest),
|
||||
)
|
||||
}, DRPCFileServer.FilesDelete, true
|
||||
case 5:
|
||||
return "/filesync.File/FilesInfo", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCFileServer).
|
||||
FilesInfo(
|
||||
ctx,
|
||||
in1.(*FilesInfoRequest),
|
||||
)
|
||||
}, DRPCFileServer.FilesInfo, true
|
||||
case 6:
|
||||
return "/filesync.File/Check", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
|
||||
return "/anyFile.File/Check", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCFileServer).
|
||||
Check(
|
||||
@@ -246,15 +184,6 @@ func (DRPCFileDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver,
|
||||
in1.(*CheckRequest),
|
||||
)
|
||||
}, DRPCFileServer.Check, true
|
||||
case 7:
|
||||
return "/filesync.File/SpaceInfo", drpcEncoding_File_commonfile_fileproto_protos_file_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCFileServer).
|
||||
SpaceInfo(
|
||||
ctx,
|
||||
in1.(*SpaceInfoRequest),
|
||||
)
|
||||
}, DRPCFileServer.SpaceInfo, true
|
||||
default:
|
||||
return "", nil, nil, nil, false
|
||||
}
|
||||
@@ -264,96 +193,58 @@ func DRPCRegisterFile(mux drpc.Mux, impl DRPCFileServer) error {
|
||||
return mux.Register(impl, DRPCFileDescription{})
|
||||
}
|
||||
|
||||
type DRPCFile_BlockGetStream interface {
|
||||
type DRPCFile_GetBlocksStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*BlockGetResponse) error
|
||||
Send(*GetBlockResponse) error
|
||||
Recv() (*GetBlockRequest, error)
|
||||
}
|
||||
|
||||
type drpcFile_BlockGetStream struct {
|
||||
type drpcFile_GetBlocksStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcFile_BlockGetStream) SendAndClose(m *BlockGetResponse) error {
|
||||
func (x *drpcFile_GetBlocksStream) Send(m *GetBlockResponse) error {
|
||||
return x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{})
|
||||
}
|
||||
|
||||
func (x *drpcFile_GetBlocksStream) Recv() (*GetBlockRequest, error) {
|
||||
m := new(GetBlockRequest)
|
||||
if err := x.MsgRecv(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (x *drpcFile_GetBlocksStream) RecvMsg(m *GetBlockRequest) error {
|
||||
return x.MsgRecv(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{})
|
||||
}
|
||||
|
||||
type DRPCFile_PushBlockStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*PushBlockResponse) error
|
||||
}
|
||||
|
||||
type drpcFile_PushBlockStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcFile_PushBlockStream) SendAndClose(m *PushBlockResponse) error {
|
||||
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCFile_BlockPushStream interface {
|
||||
type DRPCFile_DeleteBlocksStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*BlockPushResponse) error
|
||||
SendAndClose(*DeleteBlocksResponse) error
|
||||
}
|
||||
|
||||
type drpcFile_BlockPushStream struct {
|
||||
type drpcFile_DeleteBlocksStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcFile_BlockPushStream) SendAndClose(m *BlockPushResponse) error {
|
||||
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCFile_BlocksCheckStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*BlocksCheckResponse) error
|
||||
}
|
||||
|
||||
type drpcFile_BlocksCheckStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcFile_BlocksCheckStream) SendAndClose(m *BlocksCheckResponse) error {
|
||||
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCFile_BlocksBindStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*BlocksBindResponse) error
|
||||
}
|
||||
|
||||
type drpcFile_BlocksBindStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcFile_BlocksBindStream) SendAndClose(m *BlocksBindResponse) error {
|
||||
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCFile_FilesDeleteStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*FilesDeleteResponse) error
|
||||
}
|
||||
|
||||
type drpcFile_FilesDeleteStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcFile_FilesDeleteStream) SendAndClose(m *FilesDeleteResponse) error {
|
||||
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCFile_FilesInfoStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*FilesInfoResponse) error
|
||||
}
|
||||
|
||||
type drpcFile_FilesInfoStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcFile_FilesInfoStream) SendAndClose(m *FilesInfoResponse) error {
|
||||
func (x *drpcFile_DeleteBlocksStream) SendAndClose(m *DeleteBlocksResponse) error {
|
||||
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -375,19 +266,3 @@ func (x *drpcFile_CheckStream) SendAndClose(m *CheckResponse) error {
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCFile_SpaceInfoStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*SpaceInfoResponse) error
|
||||
}
|
||||
|
||||
type drpcFile_SpaceInfoStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcFile_SpaceInfoStream) SendAndClose(m *SpaceInfoResponse) error {
|
||||
if err := x.MsgSend(m, drpcEncoding_File_commonfile_fileproto_protos_file_proto{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
@@ -2,16 +2,12 @@ package fileprotoerr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonfile/fileproto"
|
||||
"github.com/anyproto/any-sync/net/rpc/rpcerr"
|
||||
"github.com/anytypeio/any-sync/commonfile/fileproto"
|
||||
"github.com/anytypeio/any-sync/net/rpc/rpcerr"
|
||||
)
|
||||
|
||||
var (
|
||||
errGroup = rpcerr.ErrGroup(fileproto.ErrCodes_ErrorOffset)
|
||||
ErrUnexpected = errGroup.Register(fmt.Errorf("unexpected fileproto error"), uint64(fileproto.ErrCodes_Unexpected))
|
||||
ErrCIDNotFound = errGroup.Register(fmt.Errorf("CID not found"), uint64(fileproto.ErrCodes_CIDNotFound))
|
||||
ErrForbidden = errGroup.Register(fmt.Errorf("forbidden"), uint64(fileproto.ErrCodes_Forbidden))
|
||||
ErrSpaceLimitExceeded = errGroup.Register(fmt.Errorf("space limit exceeded"), uint64(fileproto.ErrCodes_SpaceLimitExceeded))
|
||||
ErrQuerySizeExceeded = errGroup.Register(fmt.Errorf("query size exceeded"), uint64(fileproto.ErrCodes_QuerySizeExceeded))
|
||||
ErrWrongHash = errGroup.Register(fmt.Errorf("wrong block hash"), uint64(fileproto.ErrCodes_WrongHash))
|
||||
errGroup = rpcerr.ErrGroup(fileproto.ErrCodes_ErrorOffset)
|
||||
ErrUnexpected = errGroup.Register(fmt.Errorf("unexpected fileproto error"), uint64(fileproto.ErrCodes_Unexpected))
|
||||
ErrCIDNotFound = errGroup.Register(fmt.Errorf("CID not found"), uint64(fileproto.ErrCodes_CIDNotFound))
|
||||
)
|
||||
@@ -1,123 +1,60 @@
|
||||
syntax = "proto3";
|
||||
package filesync;
|
||||
package anyFile;
|
||||
|
||||
option go_package = "commonfile/fileproto";
|
||||
|
||||
enum ErrCodes {
|
||||
Unexpected = 0;
|
||||
CIDNotFound = 1;
|
||||
Forbidden = 2;
|
||||
SpaceLimitExceeded = 3;
|
||||
QuerySizeExceeded = 4;
|
||||
WrongHash = 5;
|
||||
ErrorOffset = 200;
|
||||
}
|
||||
|
||||
service File {
|
||||
// BlockGet gets one block from a server
|
||||
rpc BlockGet(BlockGetRequest) returns (BlockGetResponse);
|
||||
// BlockPush pushes one block to a server
|
||||
rpc BlockPush(BlockPushRequest) returns (BlockPushResponse);
|
||||
// BlocksCheck checks is CIDs exists
|
||||
rpc BlocksCheck(BlocksCheckRequest) returns (BlocksCheckResponse);
|
||||
// BlocksBind binds CIDs to space
|
||||
rpc BlocksBind(BlocksBindRequest) returns (BlocksBindResponse);
|
||||
// FilesDelete deletes files by id
|
||||
rpc FilesDelete(FilesDeleteRequest) returns (FilesDeleteResponse);
|
||||
// FilesInfo return info by given files id
|
||||
rpc FilesInfo(FilesInfoRequest) returns (FilesInfoResponse);
|
||||
// Check checks the connection and credentials
|
||||
// GetBlocks streams ipfs blocks from server to client
|
||||
rpc GetBlocks(stream GetBlockRequest) returns (stream GetBlockResponse);
|
||||
// PushBlock pushes one block to server
|
||||
rpc PushBlock(PushBlockRequest) returns (PushBlockResponse);
|
||||
// DeleteBlock deletes block from space
|
||||
rpc DeleteBlocks(DeleteBlocksRequest) returns (DeleteBlocksResponse);
|
||||
// Ping checks the connection
|
||||
rpc Check(CheckRequest) returns (CheckResponse);
|
||||
// SpaceInfo returns usage, limit, etc about space
|
||||
rpc SpaceInfo(SpaceInfoRequest) returns (SpaceInfoResponse);
|
||||
}
|
||||
|
||||
message BlockGetRequest {
|
||||
message GetBlockRequest {
|
||||
string spaceId = 1;
|
||||
bytes cid = 2;
|
||||
}
|
||||
|
||||
message BlockGetResponse {
|
||||
message GetBlockResponse {
|
||||
bytes cid = 1;
|
||||
bytes data = 2;
|
||||
CIDError code = 3;
|
||||
}
|
||||
|
||||
message BlockPushRequest {
|
||||
message PushBlockRequest {
|
||||
string spaceId = 1;
|
||||
string fileId = 2;
|
||||
bytes cid = 3;
|
||||
bytes data = 4;
|
||||
bytes cid = 2;
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
message BlockPushResponse {}
|
||||
message PushBlockResponse {}
|
||||
|
||||
|
||||
message BlocksCheckRequest {
|
||||
message DeleteBlocksRequest {
|
||||
string spaceId = 1;
|
||||
repeated bytes cids = 2;
|
||||
repeated bytes cid = 2;
|
||||
}
|
||||
|
||||
message BlocksCheckResponse {
|
||||
repeated BlockAvailability blocksAvailability = 1;
|
||||
}
|
||||
|
||||
message BlockAvailability {
|
||||
bytes cid = 1;
|
||||
AvailabilityStatus status = 2;
|
||||
}
|
||||
|
||||
enum AvailabilityStatus {
|
||||
NotExists = 0;
|
||||
Exists = 1;
|
||||
ExistsInSpace = 2;
|
||||
}
|
||||
|
||||
message BlocksBindRequest {
|
||||
string spaceId = 1;
|
||||
string fileId = 2;
|
||||
repeated bytes cids = 3;
|
||||
}
|
||||
|
||||
message BlocksBindResponse {}
|
||||
|
||||
|
||||
message FilesDeleteRequest {
|
||||
string spaceId = 1;
|
||||
repeated string fileIds = 2;
|
||||
}
|
||||
|
||||
message FilesDeleteResponse {}
|
||||
|
||||
message FilesInfoRequest {
|
||||
string spaceId = 1;
|
||||
repeated string fileIds = 2;
|
||||
}
|
||||
|
||||
message FilesInfoResponse {
|
||||
repeated FileInfo filesInfo = 1;
|
||||
}
|
||||
|
||||
message FileInfo {
|
||||
string fileId = 1;
|
||||
uint64 usageBytes = 2;
|
||||
uint32 cidsCount = 3;
|
||||
}
|
||||
message DeleteBlocksResponse {}
|
||||
|
||||
message CheckRequest {}
|
||||
|
||||
message CheckResponse {
|
||||
repeated string spaceIds = 1;
|
||||
bool allowWrite = 2;
|
||||
}
|
||||
|
||||
message SpaceInfoRequest {
|
||||
string spaceId = 1;
|
||||
}
|
||||
|
||||
message SpaceInfoResponse {
|
||||
uint64 limitBytes = 1;
|
||||
uint64 usageBytes = 2;
|
||||
uint64 cidsCount = 3;
|
||||
uint64 filesCount = 4;
|
||||
}
|
||||
|
||||
enum CIDError {
|
||||
CIDErrorOk = 0;
|
||||
CIDErrorNotFound = 1;
|
||||
CIDErrorUnexpected = 2;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package fileservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/commonfile/fileblockstore"
|
||||
"github.com/anytypeio/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/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonfile/fileblockstore"
|
||||
"github.com/anytypeio/any-sync/app"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonfile/fileblockstore"
|
||||
"github.com/ipfs/go-cid"
|
||||
chunker "github.com/ipfs/go-ipfs-chunker"
|
||||
ipld "github.com/ipfs/go-ipld-format"
|
||||
@@ -22,10 +22,6 @@ const CName = "common.commonfile.fileservice"
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
const (
|
||||
ChunkSize = 1 << 20
|
||||
)
|
||||
|
||||
func New() FileService {
|
||||
return &fileService{}
|
||||
}
|
||||
@@ -78,7 +74,7 @@ func (fs *fileService) AddFile(ctx context.Context, r io.Reader) (ipld.Node, err
|
||||
Maxlinks: helpers.DefaultLinksPerBlock,
|
||||
CidBuilder: &fs.prefix,
|
||||
}
|
||||
dbh, err := dbp.New(chunker.NewSizeSplitter(r, ChunkSize))
|
||||
dbh, err := dbp.New(chunker.DefaultSplitter(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package commonspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/syncacl"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/syncobjectgetter"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/treegetter"
|
||||
"github.com/anytypeio/any-sync/commonspace/settings"
|
||||
)
|
||||
|
||||
type commonSpaceGetter struct {
|
||||
spaceId string
|
||||
aclList *syncacl.SyncAcl
|
||||
treeGetter treegetter.TreeGetter
|
||||
settings settings.SettingsObject
|
||||
}
|
||||
|
||||
func newCommonSpaceGetter(spaceId string, aclList *syncacl.SyncAcl, treeGetter treegetter.TreeGetter, settings settings.SettingsObject) syncobjectgetter.SyncObjectGetter {
|
||||
return &commonSpaceGetter{
|
||||
spaceId: spaceId,
|
||||
aclList: aclList,
|
||||
treeGetter: treeGetter,
|
||||
settings: settings,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *commonSpaceGetter) GetObject(ctx context.Context, objectId string) (obj syncobjectgetter.SyncObject, err error) {
|
||||
if c.aclList.Id() == objectId {
|
||||
obj = c.aclList
|
||||
return
|
||||
}
|
||||
if c.settings.Id() == objectId {
|
||||
obj = c.settings.(syncobjectgetter.SyncObject)
|
||||
return
|
||||
}
|
||||
t, err := c.treeGetter.GetTree(ctx, c.spaceId, objectId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
obj = t.(syncobjectgetter.SyncObject)
|
||||
return
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package commonspace
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage"
|
||||
)
|
||||
|
||||
type commonStorage struct {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
//go:generate mockgen -destination mock_confconnector/mock_confconnector.go github.com/anytypeio/any-sync/commonspace/confconnector ConfConnector
|
||||
package confconnector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anytypeio/any-sync/net/peer"
|
||||
"github.com/anytypeio/any-sync/net/pool"
|
||||
"github.com/anytypeio/any-sync/nodeconf"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type ConfConnector interface {
|
||||
Configuration() nodeconf.Configuration
|
||||
Pool() pool.Pool
|
||||
GetResponsiblePeers(ctx context.Context, spaceId string) ([]peer.Peer, error)
|
||||
DialInactiveResponsiblePeers(ctx context.Context, spaceId string, activeNodeIds []string) ([]peer.Peer, error)
|
||||
}
|
||||
|
||||
type confConnector struct {
|
||||
conf nodeconf.Configuration
|
||||
pool pool.Pool
|
||||
}
|
||||
|
||||
func NewConfConnector(conf nodeconf.Configuration, pool pool.Pool) ConfConnector {
|
||||
return &confConnector{conf: conf, pool: pool}
|
||||
}
|
||||
|
||||
func (s *confConnector) Configuration() nodeconf.Configuration {
|
||||
return s.conf
|
||||
}
|
||||
|
||||
func (s *confConnector) Pool() pool.Pool {
|
||||
return s.pool
|
||||
}
|
||||
|
||||
func (s *confConnector) GetResponsiblePeers(ctx context.Context, spaceId string) ([]peer.Peer, error) {
|
||||
return s.connectOneOrMany(ctx, spaceId, nil, s.pool.Get)
|
||||
}
|
||||
|
||||
func (s *confConnector) DialInactiveResponsiblePeers(ctx context.Context, spaceId string, activeNodeIds []string) ([]peer.Peer, error) {
|
||||
return s.connectOneOrMany(ctx, spaceId, activeNodeIds, s.pool.Dial)
|
||||
}
|
||||
|
||||
func (s *confConnector) connectOneOrMany(
|
||||
ctx context.Context,
|
||||
spaceId string,
|
||||
activeNodeIds []string,
|
||||
connectOne func(context.Context, string) (peer.Peer, error)) (peers []peer.Peer, err error) {
|
||||
var (
|
||||
inactiveNodeIds []string
|
||||
allNodes = s.conf.NodeIds(spaceId)
|
||||
)
|
||||
for _, id := range allNodes {
|
||||
if !slices.Contains(activeNodeIds, id) {
|
||||
inactiveNodeIds = append(inactiveNodeIds, id)
|
||||
}
|
||||
}
|
||||
|
||||
if s.conf.IsResponsible(spaceId) {
|
||||
for _, id := range inactiveNodeIds {
|
||||
var p peer.Peer
|
||||
p, err = connectOne(ctx, id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
peers = append(peers, p)
|
||||
}
|
||||
} else if len(activeNodeIds) == 0 {
|
||||
// that means that all connected ids
|
||||
var p peer.Peer
|
||||
p, err = s.pool.GetOneOf(ctx, allNodes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// if we are dialling someone, we want to dial to the same peer which we cached
|
||||
// thus communication through streams and through diff will go to the same node
|
||||
p, err = connectOne(ctx, p.Id())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
peers = []peer.Peer{p}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/confconnector (interfaces: ConfConnector)
|
||||
|
||||
// Package mock_confconnector is a generated GoMock package.
|
||||
package mock_confconnector
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
peer "github.com/anytypeio/any-sync/net/peer"
|
||||
pool "github.com/anytypeio/any-sync/net/pool"
|
||||
nodeconf "github.com/anytypeio/any-sync/nodeconf"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockConfConnector is a mock of ConfConnector interface.
|
||||
type MockConfConnector struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockConfConnectorMockRecorder
|
||||
}
|
||||
|
||||
// MockConfConnectorMockRecorder is the mock recorder for MockConfConnector.
|
||||
type MockConfConnectorMockRecorder struct {
|
||||
mock *MockConfConnector
|
||||
}
|
||||
|
||||
// NewMockConfConnector creates a new mock instance.
|
||||
func NewMockConfConnector(ctrl *gomock.Controller) *MockConfConnector {
|
||||
mock := &MockConfConnector{ctrl: ctrl}
|
||||
mock.recorder = &MockConfConnectorMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockConfConnector) EXPECT() *MockConfConnectorMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Configuration mocks base method.
|
||||
func (m *MockConfConnector) Configuration() nodeconf.Configuration {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Configuration")
|
||||
ret0, _ := ret[0].(nodeconf.Configuration)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Configuration indicates an expected call of Configuration.
|
||||
func (mr *MockConfConnectorMockRecorder) Configuration() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Configuration", reflect.TypeOf((*MockConfConnector)(nil).Configuration))
|
||||
}
|
||||
|
||||
// DialInactiveResponsiblePeers mocks base method.
|
||||
func (m *MockConfConnector) DialInactiveResponsiblePeers(arg0 context.Context, arg1 string, arg2 []string) ([]peer.Peer, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DialInactiveResponsiblePeers", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].([]peer.Peer)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DialInactiveResponsiblePeers indicates an expected call of DialInactiveResponsiblePeers.
|
||||
func (mr *MockConfConnectorMockRecorder) DialInactiveResponsiblePeers(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DialInactiveResponsiblePeers", reflect.TypeOf((*MockConfConnector)(nil).DialInactiveResponsiblePeers), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// GetResponsiblePeers mocks base method.
|
||||
func (m *MockConfConnector) GetResponsiblePeers(arg0 context.Context, arg1 string) ([]peer.Peer, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetResponsiblePeers", arg0, arg1)
|
||||
ret0, _ := ret[0].([]peer.Peer)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetResponsiblePeers indicates an expected call of GetResponsiblePeers.
|
||||
func (mr *MockConfConnectorMockRecorder) GetResponsiblePeers(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResponsiblePeers", reflect.TypeOf((*MockConfConnector)(nil).GetResponsiblePeers), arg0, arg1)
|
||||
}
|
||||
|
||||
// Pool mocks base method.
|
||||
func (m *MockConfConnector) Pool() pool.Pool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Pool")
|
||||
ret0, _ := ret[0].(pool.Pool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Pool indicates an expected call of Pool.
|
||||
func (mr *MockConfConnectorMockRecorder) Pool() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Pool", reflect.TypeOf((*MockConfConnector)(nil).Pool))
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package commonspace
|
||||
|
||||
type ConfigGetter interface {
|
||||
GetSpace() Config
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
GCTTL int `yaml:"gcTTL"`
|
||||
SyncPeriod int `yaml:"syncPeriod"`
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package config
|
||||
|
||||
type ConfigGetter interface {
|
||||
GetSpace() Config
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
GCTTL int `yaml:"gcTTL"`
|
||||
SyncPeriod int `yaml:"syncPeriod"`
|
||||
KeepTreeDataInMemory bool `yaml:"keepTreeDataInMemory"`
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
//go:generate mockgen -destination mock_credentialprovider/mock_credentialprovider.go github.com/anyproto/any-sync/commonspace/credentialprovider CredentialProvider
|
||||
package credentialprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
)
|
||||
|
||||
const CName = "common.commonspace.credentialprovider"
|
||||
|
||||
func NewNoOp() CredentialProvider {
|
||||
return &noOpProvider{}
|
||||
}
|
||||
|
||||
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,80 +0,0 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/credentialprovider (interfaces: CredentialProvider)
|
||||
|
||||
// Package mock_credentialprovider is a generated GoMock package.
|
||||
package mock_credentialprovider
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
app "github.com/anyproto/any-sync/app"
|
||||
spacesyncproto "github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockCredentialProvider is a mock of CredentialProvider interface.
|
||||
type MockCredentialProvider struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockCredentialProviderMockRecorder
|
||||
}
|
||||
|
||||
// MockCredentialProviderMockRecorder is the mock recorder for MockCredentialProvider.
|
||||
type MockCredentialProviderMockRecorder struct {
|
||||
mock *MockCredentialProvider
|
||||
}
|
||||
|
||||
// NewMockCredentialProvider creates a new mock instance.
|
||||
func NewMockCredentialProvider(ctrl *gomock.Controller) *MockCredentialProvider {
|
||||
mock := &MockCredentialProvider{ctrl: ctrl}
|
||||
mock.recorder = &MockCredentialProviderMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockCredentialProvider) EXPECT() *MockCredentialProviderMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetCredential mocks base method.
|
||||
func (m *MockCredentialProvider) GetCredential(arg0 context.Context, arg1 *spacesyncproto.RawSpaceHeaderWithId) ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCredential", arg0, arg1)
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetCredential indicates an expected call of GetCredential.
|
||||
func (mr *MockCredentialProviderMockRecorder) GetCredential(arg0, arg1 interface{}) *gomock.Call {
|
||||
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))
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
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))
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
// 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))
|
||||
}
|
||||
@@ -2,18 +2,16 @@ package headsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/commonspace/confconnector"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/synctree"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/treegetter"
|
||||
"github.com/anytypeio/any-sync/commonspace/settings/deletionstate"
|
||||
"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"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
@@ -22,42 +20,45 @@ type DiffSyncer interface {
|
||||
Sync(ctx context.Context) error
|
||||
RemoveObjects(ids []string)
|
||||
UpdateHeads(id string, heads []string)
|
||||
Init()
|
||||
Close() error
|
||||
Init(deletionState deletionstate.DeletionState)
|
||||
}
|
||||
|
||||
func newDiffSyncer(hs *headSync) DiffSyncer {
|
||||
func newDiffSyncer(
|
||||
spaceId string,
|
||||
diff ldiff.Diff,
|
||||
confConnector confconnector.ConfConnector,
|
||||
cache treegetter.TreeGetter,
|
||||
storage spacestorage.SpaceStorage,
|
||||
clientFactory spacesyncproto.ClientFactory,
|
||||
syncStatus syncstatus.StatusUpdater,
|
||||
log *zap.Logger) DiffSyncer {
|
||||
return &diffSyncer{
|
||||
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: hs.syncStatus,
|
||||
deletionState: hs.deletionState,
|
||||
diff: diff,
|
||||
spaceId: spaceId,
|
||||
cache: cache,
|
||||
storage: storage,
|
||||
confConnector: confConnector,
|
||||
clientFactory: clientFactory,
|
||||
log: log,
|
||||
syncStatus: syncStatus,
|
||||
}
|
||||
}
|
||||
|
||||
type diffSyncer struct {
|
||||
spaceId string
|
||||
diff ldiff.Diff
|
||||
peerManager peermanager.PeerManager
|
||||
treeManager treemanager.TreeManager
|
||||
storage spacestorage.SpaceStorage
|
||||
clientFactory spacesyncproto.ClientFactory
|
||||
log logger.CtxLogger
|
||||
deletionState deletionstate.ObjectDeletionState
|
||||
credentialProvider credentialprovider.CredentialProvider
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
treeSyncer treemanager.TreeSyncer
|
||||
spaceId string
|
||||
diff ldiff.Diff
|
||||
confConnector confconnector.ConfConnector
|
||||
cache treegetter.TreeGetter
|
||||
storage spacestorage.SpaceStorage
|
||||
clientFactory spacesyncproto.ClientFactory
|
||||
log *zap.Logger
|
||||
deletionState deletionstate.DeletionState
|
||||
syncStatus syncstatus.StatusUpdater
|
||||
}
|
||||
|
||||
func (d *diffSyncer) Init() {
|
||||
func (d *diffSyncer) Init(deletionState deletionstate.DeletionState) {
|
||||
d.deletionState = deletionState
|
||||
d.deletionState.AddObserver(d.RemoveObjects)
|
||||
d.treeSyncer = d.treeManager.NewTreeSyncer(d.spaceId, d.treeManager)
|
||||
}
|
||||
|
||||
func (d *diffSyncer) RemoveObjects(ids []string) {
|
||||
@@ -83,37 +84,24 @@ func (d *diffSyncer) UpdateHeads(id string, heads []string) {
|
||||
}
|
||||
|
||||
func (d *diffSyncer) Sync(ctx context.Context) error {
|
||||
// TODO: split diffsyncer into components
|
||||
st := time.Now()
|
||||
// diffing with responsible peers according to configuration
|
||||
peers, err := d.peerManager.GetResponsiblePeers(ctx)
|
||||
peers, err := d.confConnector.GetResponsiblePeers(ctx, d.spaceId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var peerIds = make([]string, 0, len(peers))
|
||||
for _, p := range peers {
|
||||
peerIds = append(peerIds, p.Id())
|
||||
}
|
||||
d.log.DebugCtx(ctx, "start diffsync", zap.Strings("peerIds", peerIds))
|
||||
for _, p := range peers {
|
||||
if err = d.syncWithPeer(peer.CtxWithPeerId(ctx, p.Id()), p); err != nil {
|
||||
d.log.ErrorCtx(ctx, "can't sync with peer", zap.String("peer", p.Id()), zap.Error(err))
|
||||
if err := d.syncWithPeer(ctx, p); err != nil {
|
||||
d.log.Error("can't sync with peer", zap.String("peer", p.Id()), zap.Error(err))
|
||||
}
|
||||
}
|
||||
d.log.InfoCtx(ctx, "diff done", zap.String("spaceId", d.spaceId), zap.Duration("dur", time.Since(st)))
|
||||
d.log.Info("synced", zap.String("spaceId", d.spaceId), zap.Duration("dur", time.Since(st)))
|
||||
return nil
|
||||
}
|
||||
|
||||
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(conn)
|
||||
cl = d.clientFactory.Client(p)
|
||||
rdiff = NewRemoteDiff(d.spaceId, cl)
|
||||
stateCounter = d.syncStatus.StateCounter()
|
||||
)
|
||||
@@ -121,40 +109,50 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
|
||||
newIds, changedIds, removedIds, err := d.diff.Diff(ctx, rdiff)
|
||||
err = rpcerr.Unwrap(err)
|
||||
if err != nil && err != spacesyncproto.ErrSpaceMissing {
|
||||
if err == spacesyncproto.ErrSpaceIsDeleted {
|
||||
d.log.Debug("got space deleted while syncing")
|
||||
d.treeSyncer.SyncAll(ctx, p.Id(), []string{d.storage.SpaceSettingsId()}, nil)
|
||||
}
|
||||
d.syncStatus.SetNodesOnline(p.Id(), false)
|
||||
return fmt.Errorf("diff error: %v", err)
|
||||
return err
|
||||
}
|
||||
d.syncStatus.SetNodesOnline(p.Id(), true)
|
||||
|
||||
if err == spacesyncproto.ErrSpaceMissing {
|
||||
return d.sendPushSpaceRequest(ctx, p.Id(), cl)
|
||||
return d.sendPushSpaceRequest(ctx, cl)
|
||||
}
|
||||
|
||||
totalLen := len(newIds) + len(changedIds) + len(removedIds)
|
||||
// not syncing ids which were removed through settings document
|
||||
missingIds := d.deletionState.Filter(newIds)
|
||||
existingIds := append(d.deletionState.Filter(removedIds), d.deletionState.Filter(changedIds)...)
|
||||
filteredIds := d.deletionState.FilterJoin(newIds, changedIds, removedIds)
|
||||
|
||||
d.syncStatus.RemoveAllExcept(p.Id(), existingIds, stateCounter)
|
||||
d.syncStatus.RemoveAllExcept(p.Id(), filteredIds, stateCounter)
|
||||
|
||||
ctx = peer.CtxWithPeerId(ctx, p.Id())
|
||||
d.pingTreesInCache(ctx, filteredIds)
|
||||
|
||||
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(existingIds)-len(missingIds)),
|
||||
zap.String("peerId", p.Id()),
|
||||
)
|
||||
zap.Int("already deleted ids", totalLen-len(filteredIds)))
|
||||
return
|
||||
}
|
||||
|
||||
func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, peerId string, cl spacesyncproto.DRPCSpaceSyncClient) (err error) {
|
||||
func (d *diffSyncer) pingTreesInCache(ctx context.Context, trees []string) {
|
||||
for _, tId := range trees {
|
||||
tree, err := d.cache.GetTree(ctx, d.spaceId, tId)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
syncTree, ok := tree.(synctree.SyncTree)
|
||||
if !ok {
|
||||
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
|
||||
syncTree.Ping()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, cl spacesyncproto.DRPCSpaceSyncClient) (err error) {
|
||||
aclStorage, err := d.storage.AclStorage()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -179,10 +177,6 @@ func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, peerId string, cl
|
||||
return
|
||||
}
|
||||
|
||||
cred, err := d.credentialProvider.GetCredential(ctx, header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
spacePayload := &spacesyncproto.SpacePayload{
|
||||
SpaceHeader: header,
|
||||
AclPayload: root.Payload,
|
||||
@@ -191,32 +185,7 @@ func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, peerId string, cl
|
||||
SpaceSettingsPayloadId: spaceSettingsRoot.Id,
|
||||
}
|
||||
_, err = cl.SpacePush(ctx, &spacesyncproto.SpacePushRequest{
|
||||
Payload: spacePayload,
|
||||
Credential: cred,
|
||||
Payload: spacePayload,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if e := d.subscribe(ctx, peerId); e != nil {
|
||||
d.log.WarnCtx(ctx, "error subscribing for space", zap.Error(e))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *diffSyncer) subscribe(ctx context.Context, peerId string) (err error) {
|
||||
var msg = &spacesyncproto.SpaceSubscription{
|
||||
SpaceIds: []string{d.spaceId},
|
||||
Action: spacesyncproto.SpaceSubscriptionAction_Subscribe,
|
||||
}
|
||||
payload, err := msg.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return d.peerManager.SendPeer(ctx, peerId, &spacesyncproto.ObjectSyncMessage{
|
||||
Payload: payload,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *diffSyncer) Close() error {
|
||||
return d.treeSyncer.Close()
|
||||
}
|
||||
@@ -1,17 +1,25 @@
|
||||
package headsync
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"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/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/confconnector/mock_confconnector"
|
||||
"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/treegetter/mock_treegetter"
|
||||
"github.com/anytypeio/any-sync/commonspace/settings/deletionstate/mock_deletionstate"
|
||||
"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/golang/mock/gomock"
|
||||
"github.com/libp2p/go-libp2p/core/sec"
|
||||
"github.com/stretchr/testify/require"
|
||||
"storj.io/drpc"
|
||||
"testing"
|
||||
@@ -22,153 +30,140 @@ type pushSpaceRequestMatcher struct {
|
||||
spaceId string
|
||||
aclRootId string
|
||||
settingsId string
|
||||
credential []byte
|
||||
spaceHeader *spacesyncproto.RawSpaceHeaderWithId
|
||||
}
|
||||
|
||||
func newPushSpaceRequestMatcher(
|
||||
spaceId string,
|
||||
aclRootId string,
|
||||
settingsId string,
|
||||
credential []byte,
|
||||
spaceHeader *spacesyncproto.RawSpaceHeaderWithId) *pushSpaceRequestMatcher {
|
||||
return &pushSpaceRequestMatcher{
|
||||
spaceId: spaceId,
|
||||
aclRootId: aclRootId,
|
||||
settingsId: settingsId,
|
||||
credential: credential,
|
||||
spaceHeader: spaceHeader,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId
|
||||
}
|
||||
|
||||
func (p pushSpaceRequestMatcher) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type mockPeer struct {
|
||||
}
|
||||
type mockPeer struct{}
|
||||
|
||||
func (m mockPeer) Id() string {
|
||||
return "peerId"
|
||||
return "mockId"
|
||||
}
|
||||
|
||||
func (m mockPeer) Context() context.Context {
|
||||
return context.Background()
|
||||
func (m mockPeer) LastUsage() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (m mockPeer) AcquireDrpcConn(ctx context.Context) (drpc.Conn, error) {
|
||||
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 (m mockPeer) ReleaseDrpcConn(conn drpc.Conn) {
|
||||
return
|
||||
func newPushSpaceRequestMatcher(
|
||||
spaceId string,
|
||||
aclRootId string,
|
||||
settingsId string,
|
||||
spaceHeader *spacesyncproto.RawSpaceHeaderWithId) *pushSpaceRequestMatcher {
|
||||
return &pushSpaceRequestMatcher{
|
||||
spaceId: spaceId,
|
||||
aclRootId: aclRootId,
|
||||
settingsId: settingsId,
|
||||
spaceHeader: spaceHeader,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
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) {
|
||||
func TestDiffSyncer_Sync(t *testing.T) {
|
||||
// setup
|
||||
ctx := context.Background()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
diffMock := mock_ldiff.NewMockDiff(ctrl)
|
||||
connectorMock := mock_confconnector.NewMockConfConnector(ctrl)
|
||||
cacheMock := mock_treegetter.NewMockTreeGetter(ctrl)
|
||||
stMock := mock_spacestorage.NewMockSpaceStorage(ctrl)
|
||||
clientMock := mock_spacesyncproto.NewMockDRPCSpaceSyncClient(ctrl)
|
||||
factory := spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceSyncClient {
|
||||
return clientMock
|
||||
})
|
||||
delState := mock_deletionstate.NewMockDeletionState(ctrl)
|
||||
spaceId := "spaceId"
|
||||
aclRootId := "aclRootId"
|
||||
l := logger.NewNamed(spaceId)
|
||||
diffSyncer := newDiffSyncer(spaceId, diffMock, connectorMock, cacheMock, stMock, factory, syncstatus.NewNoOpSyncStatus(), l)
|
||||
delState.EXPECT().AddObserver(gomock.Any())
|
||||
diffSyncer.Init(delState)
|
||||
|
||||
t.Run("diff syncer sync", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
mPeer := mockPeer{}
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
Return([]peer.Peer{mPeer}, nil)
|
||||
fx.diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
||||
connectorMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any(), spaceId).
|
||||
Return([]peer.Peer{mockPeer{}}, nil)
|
||||
diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||
Return([]string{"new"}, []string{"changed"}, nil, nil)
|
||||
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))
|
||||
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))
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync conf error", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
ctx := context.Background()
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
connectorMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any(), spaceId).
|
||||
Return(nil, fmt.Errorf("some error"))
|
||||
|
||||
require.Error(t, fx.diffSyncer.Sync(ctx))
|
||||
require.Error(t, diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("deletion state remove objects", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
deletedId := "id"
|
||||
fx.deletionStateMock.EXPECT().Exists(deletedId).Return(true)
|
||||
delState.EXPECT().Exists(deletedId).Return(true)
|
||||
|
||||
// this should not result in any mock being called
|
||||
fx.diffSyncer.UpdateHeads(deletedId, []string{"someHead"})
|
||||
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"
|
||||
fx.diffMock.EXPECT().Set(ldiff.Element{
|
||||
diffMock.EXPECT().Set(ldiff.Element{
|
||||
Id: newId,
|
||||
Head: concatStrings(newHeads),
|
||||
})
|
||||
fx.diffMock.EXPECT().Hash().Return(hash)
|
||||
fx.deletionStateMock.EXPECT().Exists(newId).Return(false)
|
||||
fx.storageMock.EXPECT().WriteSpaceHash(hash)
|
||||
fx.diffSyncer.UpdateHeads(newId, newHeads)
|
||||
diffMock.EXPECT().Hash().Return(hash)
|
||||
delState.EXPECT().Exists(newId).Return(false)
|
||||
stMock.EXPECT().WriteSpaceHash(hash)
|
||||
diffSyncer.UpdateHeads(newId, newHeads)
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync space missing", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
aclStorageMock := mock_liststorage.NewMockListStorage(fx.ctrl)
|
||||
settingsStorage := mock_treestorage.NewMockTreeStorage(fx.ctrl)
|
||||
aclStorageMock := mock_liststorage.NewMockListStorage(ctrl)
|
||||
settingsStorage := mock_treestorage.NewMockTreeStorage(ctrl)
|
||||
settingsId := "settingsId"
|
||||
aclRootId := "aclRootId"
|
||||
aclRoot := &aclrecordproto.RawAclRecordWithId{
|
||||
Id: aclRootId,
|
||||
}
|
||||
@@ -177,63 +172,38 @@ func TestDiffSyncer(t *testing.T) {
|
||||
}
|
||||
spaceHeader := &spacesyncproto.RawSpaceHeaderWithId{}
|
||||
spaceSettingsId := "spaceSettingsId"
|
||||
credential := []byte("credential")
|
||||
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
connectorMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any(), spaceId).
|
||||
Return([]peer.Peer{mockPeer{}}, nil)
|
||||
fx.diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
||||
diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||
Return(nil, nil, nil, spacesyncproto.ErrSpaceMissing)
|
||||
|
||||
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)
|
||||
stMock.EXPECT().AclStorage().Return(aclStorageMock, nil)
|
||||
stMock.EXPECT().SpaceHeader().Return(spaceHeader, nil)
|
||||
stMock.EXPECT().SpaceSettingsId().Return(spaceSettingsId)
|
||||
stMock.EXPECT().TreeStorage(spaceSettingsId).Return(settingsStorage, nil)
|
||||
|
||||
settingsStorage.EXPECT().Root().Return(settingsRoot, nil)
|
||||
aclStorageMock.EXPECT().
|
||||
Root().
|
||||
Return(aclRoot, nil)
|
||||
fx.credentialProviderMock.EXPECT().
|
||||
GetCredential(gomock.Any(), spaceHeader).
|
||||
Return(credential, nil)
|
||||
fx.clientMock.EXPECT().
|
||||
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(fx.spaceState.SpaceId, aclRootId, settingsId, credential, spaceHeader)).
|
||||
clientMock.EXPECT().
|
||||
SpacePush(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRootId, settingsId, spaceHeader)).
|
||||
Return(nil, nil)
|
||||
fx.peerManagerMock.EXPECT().SendPeer(gomock.Any(), "peerId", gomock.Any())
|
||||
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
require.NoError(t, diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync unexpected", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
t.Run("diff syncer sync other error", func(t *testing.T) {
|
||||
connectorMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any(), spaceId).
|
||||
Return([]peer.Peer{mockPeer{}}, nil)
|
||||
fx.diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(fx.spaceState.SpaceId, fx.clientMock))).
|
||||
diffMock.EXPECT().
|
||||
Diff(gomock.Any(), gomock.Eq(NewRemoteDiff(spaceId, clientMock))).
|
||||
Return(nil, nil, nil, spacesyncproto.ErrUnexpected)
|
||||
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
})
|
||||
|
||||
t.Run("diff syncer sync space is deleted error", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.initDiffSyncer(t)
|
||||
defer fx.stop()
|
||||
mPeer := mockPeer{}
|
||||
fx.peerManagerMock.EXPECT().
|
||||
GetResponsiblePeers(gomock.Any()).
|
||||
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)
|
||||
fx.storageMock.EXPECT().SpaceSettingsId().Return("settingsId")
|
||||
fx.treeSyncerMock.EXPECT().SyncAll(gomock.Any(), mPeer.Id(), []string{"settingsId"}, nil).Return(nil)
|
||||
|
||||
require.NoError(t, fx.diffSyncer.Sync(ctx))
|
||||
require.NoError(t, diffSyncer.Sync(ctx))
|
||||
})
|
||||
}
|
||||
@@ -1,147 +1,95 @@
|
||||
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anyproto/any-sync/commonspace/headsync DiffSyncer
|
||||
//go:generate mockgen -destination mock_headsync/mock_headsync.go github.com/anytypeio/any-sync/commonspace/headsync DiffSyncer
|
||||
package headsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/commonspace/confconnector"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/treegetter"
|
||||
"github.com/anytypeio/any-sync/commonspace/settings/deletionstate"
|
||||
"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/util/periodicsync"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
"sync/atomic"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
const CName = "common.commonspace.headsync"
|
||||
|
||||
type TreeHeads struct {
|
||||
Id string
|
||||
Heads []string
|
||||
}
|
||||
|
||||
type HeadSync interface {
|
||||
app.ComponentRunnable
|
||||
ExternalIds() []string
|
||||
DebugAllHeads() (res []TreeHeads)
|
||||
AllIds() []string
|
||||
Init(objectIds []string, deletionState deletionstate.DeletionState)
|
||||
|
||||
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
|
||||
spaceIsDeleted *atomic.Bool
|
||||
syncPeriod int
|
||||
spaceId string
|
||||
periodicSync periodicsync.PeriodicSync
|
||||
storage spacestorage.SpaceStorage
|
||||
diff ldiff.Diff
|
||||
log *zap.Logger
|
||||
syncer DiffSyncer
|
||||
|
||||
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
|
||||
syncPeriod int
|
||||
}
|
||||
|
||||
func New() HeadSync {
|
||||
return &headSync{}
|
||||
}
|
||||
func NewHeadSync(
|
||||
spaceId string,
|
||||
syncPeriod int,
|
||||
storage spacestorage.SpaceStorage,
|
||||
confConnector confconnector.ConfConnector,
|
||||
cache treegetter.TreeGetter,
|
||||
syncStatus syncstatus.StatusUpdater,
|
||||
log *zap.Logger) HeadSync {
|
||||
|
||||
var createDiffSyncer = newDiffSyncer
|
||||
diff := ldiff.New(16, 16)
|
||||
l := log.With(zap.String("spaceId", spaceId))
|
||||
factory := spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceSyncClient)
|
||||
syncer := newDiffSyncer(spaceId, diff, confConnector, cache, storage, factory, syncStatus, l)
|
||||
periodicSync := periodicsync.NewPeriodicSync(syncPeriod, time.Minute, syncer.Sync, l)
|
||||
|
||||
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 h.spaceIsDeleted.Load() && !h.configuration.IsResponsible(h.spaceId) {
|
||||
return spacesyncproto.ErrSpaceIsDeleted
|
||||
}
|
||||
return h.syncer.Sync(ctx)
|
||||
return &headSync{
|
||||
spaceId: spaceId,
|
||||
storage: storage,
|
||||
syncer: syncer,
|
||||
periodicSync: periodicSync,
|
||||
diff: diff,
|
||||
log: log,
|
||||
syncPeriod: syncPeriod,
|
||||
}
|
||||
h.periodicSync = periodicsync.NewPeriodicSync(h.syncPeriod, time.Minute, sync, h.log)
|
||||
// TODO: move to run?
|
||||
h.syncer.Init()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *headSync) Name() (name string) {
|
||||
return CName
|
||||
func (d *headSync) Init(objectIds []string, deletionState deletionstate.DeletionState) {
|
||||
d.fillDiff(objectIds)
|
||||
d.syncer.Init(deletionState)
|
||||
d.periodicSync.Run()
|
||||
}
|
||||
|
||||
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 (d *headSync) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) {
|
||||
return HandleRangeRequest(ctx, d.diff, req)
|
||||
}
|
||||
|
||||
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(h.configuration.NodeIds(h.spaceId), peerId) {
|
||||
return nil, spacesyncproto.ErrSpaceIsDeleted
|
||||
}
|
||||
}
|
||||
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 (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()
|
||||
func (d *headSync) DebugAllHeads() (res []TreeHeads) {
|
||||
els := d.diff.Elements()
|
||||
for _, el := range els {
|
||||
idHead := TreeHeads{
|
||||
Id: el.Id,
|
||||
@@ -152,19 +100,19 @@ func (h *headSync) DebugAllHeads() (res []TreeHeads) {
|
||||
return
|
||||
}
|
||||
|
||||
func (h *headSync) RemoveObjects(ids []string) {
|
||||
h.syncer.RemoveObjects(ids)
|
||||
func (d *headSync) RemoveObjects(ids []string) {
|
||||
d.syncer.RemoveObjects(ids)
|
||||
}
|
||||
|
||||
func (h *headSync) Close(ctx context.Context) (err error) {
|
||||
h.periodicSync.Close()
|
||||
return h.syncer.Close()
|
||||
func (d *headSync) Close() (err error) {
|
||||
d.periodicSync.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *headSync) fillDiff(objectIds []string) {
|
||||
func (d *headSync) fillDiff(objectIds []string) {
|
||||
var els = make([]ldiff.Element, 0, len(objectIds))
|
||||
for _, id := range objectIds {
|
||||
st, err := h.storage.TreeStorage(id)
|
||||
st, err := d.storage.TreeStorage(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -177,8 +125,32 @@ func (h *headSync) fillDiff(objectIds []string) {
|
||||
Head: concatStrings(heads),
|
||||
})
|
||||
}
|
||||
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))
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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,179 +1,70 @@
|
||||
package headsync
|
||||
|
||||
import (
|
||||
"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/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/deletionstate/mock_deletionstate"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacestorage/mock_spacestorage"
|
||||
"github.com/anytypeio/any-sync/util/periodicsync/mock_periodicsync"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockConfig struct {
|
||||
}
|
||||
|
||||
func (m mockConfig) Init(a *app.App) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
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{},
|
||||
}
|
||||
func TestDiffService(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
configurationMock := mock_nodeconf.NewMockService(ctrl)
|
||||
configurationMock.EXPECT().Name().AnyTimes().Return(nodeconf.CName)
|
||||
defer ctrl.Finish()
|
||||
|
||||
spaceId := "spaceId"
|
||||
l := logger.NewNamed("sync")
|
||||
pSyncMock := mock_periodicsync.NewMockPeriodicSync(ctrl)
|
||||
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)
|
||||
treeStorageMock := mock_treestorage.NewMockTreeStorage(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,
|
||||
syncer := mock_headsync.NewMockDiffSyncer(ctrl)
|
||||
delState := mock_deletionstate.NewMockDeletionState(ctrl)
|
||||
syncPeriod := 1
|
||||
initId := "initId"
|
||||
|
||||
service := &headSync{
|
||||
spaceId: spaceId,
|
||||
storage: storageMock,
|
||||
periodicSync: pSyncMock,
|
||||
syncer: syncer,
|
||||
diff: diffMock,
|
||||
log: l,
|
||||
syncPeriod: syncPeriod,
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
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,
|
||||
Head: "h1h2",
|
||||
})
|
||||
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)
|
||||
hash := "123"
|
||||
diffMock.EXPECT().Hash().Return(hash)
|
||||
storageMock.EXPECT().WriteSpaceHash(hash)
|
||||
pSyncMock.EXPECT().Run()
|
||||
service.Init([]string{initId}, delState)
|
||||
})
|
||||
|
||||
t.Run("update heads", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.init(t)
|
||||
defer fx.stop()
|
||||
|
||||
fx.diffSyncerMock.EXPECT().UpdateHeads("id1", []string{"h1"})
|
||||
fx.headSync.UpdateHeads("id1", []string{"h1"})
|
||||
syncer.EXPECT().UpdateHeads(initId, []string{"h1", "h2"})
|
||||
service.UpdateHeads(initId, []string{"h1", "h2"})
|
||||
})
|
||||
|
||||
t.Run("remove objects", func(t *testing.T) {
|
||||
fx := newHeadSyncFixture(t)
|
||||
fx.init(t)
|
||||
defer fx.stop()
|
||||
syncer.EXPECT().RemoveObjects([]string{"h1", "h2"})
|
||||
service.RemoveObjects([]string{"h1", "h2"})
|
||||
})
|
||||
|
||||
fx.diffSyncerMock.EXPECT().RemoveObjects([]string{"id1"})
|
||||
fx.headSync.RemoveObjects([]string{"id1"})
|
||||
t.Run("close", func(t *testing.T) {
|
||||
pSyncMock.EXPECT().Close()
|
||||
service.Close()
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/headsync (interfaces: DiffSyncer)
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/headsync (interfaces: DiffSyncer)
|
||||
|
||||
// Package mock_headsync is a generated GoMock package.
|
||||
package mock_headsync
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
deletionstate "github.com/anytypeio/any-sync/commonspace/settings/deletionstate"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
@@ -34,30 +35,16 @@ func (m *MockDiffSyncer) EXPECT() *MockDiffSyncerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Close mocks base method.
|
||||
func (m *MockDiffSyncer) 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 *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() {
|
||||
func (m *MockDiffSyncer) Init(arg0 deletionstate.DeletionState) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Init")
|
||||
m.ctrl.Call(m, "Init", arg0)
|
||||
}
|
||||
|
||||
// Init indicates an expected call of Init.
|
||||
func (mr *MockDiffSyncerMockRecorder) Init() *gomock.Call {
|
||||
func (mr *MockDiffSyncerMockRecorder) Init(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init))
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init), arg0)
|
||||
}
|
||||
|
||||
// RemoveObjects mocks base method.
|
||||
|
||||
@@ -2,8 +2,8 @@ package headsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
|
||||
@@ -3,8 +3,8 @@ package headsync
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/app/ldiff"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anytypeio/any-sync/app/ldiff"
|
||||
"github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,36 +1,14 @@
|
||||
package accountdata
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
type AccountKeys struct {
|
||||
PeerKey crypto.PrivKey
|
||||
SignKey crypto.PrivKey
|
||||
PeerId string
|
||||
}
|
||||
|
||||
func New(peerKey, signKey crypto.PrivKey) *AccountKeys {
|
||||
return &AccountKeys{
|
||||
PeerKey: peerKey,
|
||||
SignKey: signKey,
|
||||
PeerId: peerKey.GetPublic().PeerId(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewRandom() (*AccountKeys, error) {
|
||||
peerKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signKey, _, err := crypto.GenerateEd25519Key(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AccountKeys{
|
||||
PeerKey: peerKey,
|
||||
SignKey: signKey,
|
||||
PeerId: peerKey.GetPublic().PeerId(),
|
||||
}, nil
|
||||
type AccountData struct { // TODO: create a convenient constructor for this
|
||||
Identity []byte // public key
|
||||
PeerKey signingkey.PrivKey
|
||||
SignKey signingkey.PrivKey
|
||||
EncKey encryptionkey.PrivKey
|
||||
PeerId string
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package aclrecordproto
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/util/keys/symmetric"
|
||||
)
|
||||
|
||||
func AclReadKeyDerive(signKey []byte, encKey []byte) (*symmetric.Key, error) {
|
||||
concBuf := make([]byte, 0, len(signKey)+len(encKey))
|
||||
concBuf = append(concBuf, signKey...)
|
||||
concBuf = append(concBuf, encKey...)
|
||||
return symmetric.DeriveFromBytes(concBuf)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -18,17 +18,18 @@ message AclRecord {
|
||||
string prevId = 1;
|
||||
bytes identity = 2;
|
||||
bytes data = 3;
|
||||
string readKeyId = 4;
|
||||
uint64 currentReadKeyHash = 4;
|
||||
int64 timestamp = 5;
|
||||
}
|
||||
|
||||
message AclRoot {
|
||||
bytes identity = 1;
|
||||
bytes masterKey = 2;
|
||||
bytes encryptionKey = 2;
|
||||
string spaceId = 3;
|
||||
bytes encryptedReadKey = 4;
|
||||
int64 timestamp = 5;
|
||||
bytes identitySignature = 6;
|
||||
string derivationScheme = 5;
|
||||
uint64 currentReadKeyHash = 6;
|
||||
int64 timestamp = 7;
|
||||
}
|
||||
|
||||
message AclContentValue {
|
||||
@@ -46,33 +47,37 @@ message AclData {
|
||||
}
|
||||
|
||||
message AclState {
|
||||
repeated string readKeyIds = 1;
|
||||
repeated uint64 readKeyHashes = 1;
|
||||
repeated AclUserState userStates = 2;
|
||||
map<string, AclUserInvite> invites = 3;
|
||||
}
|
||||
|
||||
message AclUserState {
|
||||
bytes identity = 1;
|
||||
AclUserPermissions permissions = 2;
|
||||
bytes encryptionKey = 2;
|
||||
AclUserPermissions permissions = 3;
|
||||
}
|
||||
|
||||
message AclUserAdd {
|
||||
bytes identity = 1;
|
||||
repeated bytes encryptedReadKeys = 2;
|
||||
AclUserPermissions permissions = 3;
|
||||
bytes encryptionKey = 2;
|
||||
repeated bytes encryptedReadKeys = 3;
|
||||
AclUserPermissions permissions = 4;
|
||||
}
|
||||
|
||||
message AclUserInvite {
|
||||
bytes acceptPublicKey = 1;
|
||||
repeated bytes encryptedReadKeys = 2;
|
||||
AclUserPermissions permissions = 3;
|
||||
uint64 encryptSymKeyHash = 2;
|
||||
repeated bytes encryptedReadKeys = 3;
|
||||
AclUserPermissions permissions = 4;
|
||||
}
|
||||
|
||||
message AclUserJoin {
|
||||
bytes identity = 1;
|
||||
bytes acceptSignature = 2;
|
||||
bytes acceptPubKey = 3;
|
||||
repeated bytes encryptedReadKeys = 4;
|
||||
bytes encryptionKey = 2;
|
||||
bytes acceptSignature = 3;
|
||||
bytes acceptPubKey = 4;
|
||||
repeated bytes encryptedReadKeys = 5;
|
||||
}
|
||||
|
||||
message AclUserRemove {
|
||||
@@ -82,7 +87,8 @@ message AclUserRemove {
|
||||
|
||||
message AclReadKeyReplace {
|
||||
bytes identity = 1;
|
||||
bytes encryptedReadKey = 2;
|
||||
bytes encryptionKey = 2;
|
||||
bytes encryptedReadKey = 3;
|
||||
}
|
||||
|
||||
message AclUserPermissionChange {
|
||||
@@ -97,7 +103,7 @@ enum AclUserPermissions {
|
||||
}
|
||||
|
||||
message AclSyncMessage {
|
||||
AclSyncContentValue content = 1;
|
||||
AclSyncContentValue content = 2;
|
||||
}
|
||||
|
||||
// AclSyncContentValue provides different types for acl sync
|
||||
|
||||
@@ -1,63 +1,131 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"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/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/keychain"
|
||||
"github.com/anytypeio/any-sync/util/cidutil"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/any-sync/util/keys/symmetric"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RootContent struct {
|
||||
PrivKey crypto.PrivKey
|
||||
MasterKey crypto.PrivKey
|
||||
SpaceId string
|
||||
EncryptedReadKey []byte
|
||||
}
|
||||
|
||||
// remove interface
|
||||
type AclRecordBuilder interface {
|
||||
Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error)
|
||||
BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error)
|
||||
ConvertFromRaw(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error)
|
||||
BuildUserJoin(acceptPrivKeyBytes []byte, encSymKeyBytes []byte, state *AclState) (rec *aclrecordproto.RawAclRecord, err error)
|
||||
}
|
||||
|
||||
type aclRecordBuilder struct {
|
||||
id string
|
||||
keyStorage crypto.KeyStorage
|
||||
id string
|
||||
keychain *keychain.Keychain
|
||||
}
|
||||
|
||||
func NewAclRecordBuilder(id string, keyStorage crypto.KeyStorage) AclRecordBuilder {
|
||||
func newAclRecordBuilder(id string, keychain *keychain.Keychain) AclRecordBuilder {
|
||||
return &aclRecordBuilder{
|
||||
id: id,
|
||||
keyStorage: keyStorage,
|
||||
id: id,
|
||||
keychain: keychain,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error) {
|
||||
var (
|
||||
rawRec = &aclrecordproto.RawAclRecord{}
|
||||
pubKey crypto.PubKey
|
||||
)
|
||||
func (a *aclRecordBuilder) BuildUserJoin(acceptPrivKeyBytes []byte, encSymKeyBytes []byte, state *AclState) (rec *aclrecordproto.RawAclRecord, err error) {
|
||||
acceptPrivKey, err := signingkey.NewSigningEd25519PrivKeyFromBytes(acceptPrivKeyBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
acceptPubKeyBytes, err := acceptPrivKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encSymKey, err := symmetric.FromBytes(encSymKeyBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
invite, err := state.Invite(acceptPubKeyBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
encPrivKey, signPrivKey := state.UserKeys()
|
||||
var symKeys [][]byte
|
||||
for _, rk := range invite.EncryptedReadKeys {
|
||||
dec, err := encSymKey.Decrypt(rk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newEnc, err := encPrivKey.GetPublic().Encrypt(dec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
symKeys = append(symKeys, newEnc)
|
||||
}
|
||||
idSignature, err := acceptPrivKey.Sign(state.Identity())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encPubKeyBytes, err := encPrivKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
userJoin := &aclrecordproto.AclUserJoin{
|
||||
Identity: state.Identity(),
|
||||
EncryptionKey: encPubKeyBytes,
|
||||
AcceptSignature: idSignature,
|
||||
AcceptPubKey: acceptPubKeyBytes,
|
||||
EncryptedReadKeys: symKeys,
|
||||
}
|
||||
aclData := &aclrecordproto.AclData{AclContent: []*aclrecordproto.AclContentValue{
|
||||
{Value: &aclrecordproto.AclContentValue_UserJoin{UserJoin: userJoin}},
|
||||
}}
|
||||
marshalledJoin, err := aclData.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aclRecord := &aclrecordproto.AclRecord{
|
||||
PrevId: state.LastRecordId(),
|
||||
Identity: state.Identity(),
|
||||
Data: marshalledJoin,
|
||||
CurrentReadKeyHash: state.CurrentReadKeyHash(),
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
}
|
||||
marshalledRecord, err := aclRecord.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
recSignature, err := signPrivKey.Sign(marshalledRecord)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rec = &aclrecordproto.RawAclRecord{
|
||||
Payload: marshalledRecord,
|
||||
Signature: recSignature,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) ConvertFromRaw(rawIdRecord *aclrecordproto.RawAclRecordWithId) (rec *AclRecord, err error) {
|
||||
rawRec := &aclrecordproto.RawAclRecord{}
|
||||
err = proto.Unmarshal(rawIdRecord.Payload, rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if rawIdRecord.Id == a.id {
|
||||
aclRoot := &aclrecordproto.AclRoot{}
|
||||
err = proto.Unmarshal(rawRec.Payload, aclRoot)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pubKey, err = a.keyStorage.PubKeyFromProto(aclRoot.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rec = &AclRecord{
|
||||
Id: rawIdRecord.Id,
|
||||
ReadKeyId: rawIdRecord.Id,
|
||||
Timestamp: aclRoot.Timestamp,
|
||||
Signature: rawRec.Signature,
|
||||
Identity: pubKey,
|
||||
Model: aclRoot,
|
||||
Id: rawIdRecord.Id,
|
||||
CurrentReadKeyHash: aclRoot.CurrentReadKeyHash,
|
||||
Timestamp: aclRoot.Timestamp,
|
||||
Signature: rawRec.Signature,
|
||||
Identity: aclRoot.Identity,
|
||||
Model: aclRoot,
|
||||
}
|
||||
} else {
|
||||
aclRecord := &aclrecordproto.AclRecord{}
|
||||
@@ -65,63 +133,34 @@ func (a *aclRecordBuilder) Unmarshall(rawIdRecord *aclrecordproto.RawAclRecordWi
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pubKey, err = a.keyStorage.PubKeyFromProto(aclRecord.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rec = &AclRecord{
|
||||
Id: rawIdRecord.Id,
|
||||
PrevId: aclRecord.PrevId,
|
||||
ReadKeyId: aclRecord.ReadKeyId,
|
||||
Timestamp: aclRecord.Timestamp,
|
||||
Data: aclRecord.Data,
|
||||
Signature: rawRec.Signature,
|
||||
Identity: pubKey,
|
||||
Id: rawIdRecord.Id,
|
||||
PrevId: aclRecord.PrevId,
|
||||
CurrentReadKeyHash: aclRecord.CurrentReadKeyHash,
|
||||
Timestamp: aclRecord.Timestamp,
|
||||
Data: aclRecord.Data,
|
||||
Signature: rawRec.Signature,
|
||||
Identity: aclRecord.Identity,
|
||||
}
|
||||
}
|
||||
|
||||
err = verifyRaw(pubKey, rawRec, rawIdRecord)
|
||||
err = verifyRaw(a.keychain, rawRec, rawIdRecord, rec.Identity)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *aclRecordBuilder) BuildRoot(content RootContent) (rec *aclrecordproto.RawAclRecordWithId, err error) {
|
||||
rawIdentity, err := content.PrivKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
identity, err := content.PrivKey.GetPublic().Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
masterKey, err := content.MasterKey.GetPublic().Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
identitySignature, err := content.MasterKey.Sign(rawIdentity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var timestamp int64
|
||||
if content.EncryptedReadKey != nil {
|
||||
timestamp = time.Now().Unix()
|
||||
}
|
||||
aclRoot := &aclrecordproto.AclRoot{
|
||||
Identity: identity,
|
||||
SpaceId: content.SpaceId,
|
||||
EncryptedReadKey: content.EncryptedReadKey,
|
||||
MasterKey: masterKey,
|
||||
IdentitySignature: identitySignature,
|
||||
Timestamp: timestamp,
|
||||
}
|
||||
return marshalAclRoot(aclRoot, content.PrivKey)
|
||||
}
|
||||
|
||||
func verifyRaw(
|
||||
pubKey crypto.PubKey,
|
||||
keychain *keychain.Keychain,
|
||||
rawRec *aclrecordproto.RawAclRecord,
|
||||
recWithId *aclrecordproto.RawAclRecordWithId) (err error) {
|
||||
recWithId *aclrecordproto.RawAclRecordWithId,
|
||||
identity []byte) (err error) {
|
||||
identityKey, err := keychain.GetOrAdd(string(identity))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// verifying signature
|
||||
res, err := pubKey.Verify(rawRec.Payload, rawRec.Signature)
|
||||
res, err := identityKey.Verify(rawRec.Payload, rawRec.Signature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -136,31 +175,3 @@ func verifyRaw(
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func marshalAclRoot(aclRoot *aclrecordproto.AclRoot, key crypto.PrivKey) (rawWithId *aclrecordproto.RawAclRecordWithId, err error) {
|
||||
marshalledRoot, err := aclRoot.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signature, err := key.Sign(marshalledRoot)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
raw := &aclrecordproto.RawAclRecord{
|
||||
Payload: marshalledRoot,
|
||||
Signature: signature,
|
||||
}
|
||||
marshalledRaw, err := raw.Marshal()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aclHeadId, err := cidutil.NewCidFromBytes(marshalledRaw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rawWithId = &aclrecordproto.RawAclRecordWithId{
|
||||
Payload: marshalledRaw,
|
||||
Id: aclHeadId,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,9 +1,50 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
acllistbuilder2 "github.com/anytypeio/any-sync/commonspace/object/acl/testutils/acllistbuilder"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/keychain"
|
||||
"github.com/anytypeio/any-sync/util/cidutil"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAclRecordBuilder_BuildUserJoin(t *testing.T) {
|
||||
return
|
||||
st, err := acllistbuilder2.NewListStorageWithTestName("userjoinexample.yml")
|
||||
require.NoError(t, err, "building storage should not result in error")
|
||||
|
||||
testKeychain := st.(*acllistbuilder2.AclListStorageBuilder).GetKeychain()
|
||||
identity := testKeychain.GeneratedIdentities["D"]
|
||||
signPrivKey := testKeychain.SigningKeysByYAMLName["D"]
|
||||
encPrivKey := testKeychain.EncryptionKeysByYAMLName["D"]
|
||||
acc := &accountdata.AccountData{
|
||||
Identity: []byte(identity),
|
||||
SignKey: signPrivKey,
|
||||
EncKey: encPrivKey,
|
||||
}
|
||||
|
||||
aclList, err := BuildAclListWithIdentity(acc, st)
|
||||
require.NoError(t, err, "building acl list should be without error")
|
||||
recordBuilder := newAclRecordBuilder(aclList.Id(), keychain.NewKeychain())
|
||||
rk, err := testKeychain.GetKey("key.Read.EncKey").(*acllistbuilder2.SymKey).Key.Raw()
|
||||
require.NoError(t, err)
|
||||
privKey, err := testKeychain.GetKey("key.Sign.Onetime1").(signingkey.PrivKey).Raw()
|
||||
require.NoError(t, err)
|
||||
|
||||
userJoin, err := recordBuilder.BuildUserJoin(privKey, rk, aclList.AclState())
|
||||
require.NoError(t, err)
|
||||
marshalledJoin, err := userJoin.Marshal()
|
||||
require.NoError(t, err)
|
||||
id, err := cidutil.NewCidFromBytes(marshalledJoin)
|
||||
require.NoError(t, err)
|
||||
rawRec := &aclrecordproto.RawAclRecordWithId{
|
||||
Payload: marshalledJoin,
|
||||
Id: id,
|
||||
}
|
||||
res, err := aclList.AddRawRecord(rawRec)
|
||||
require.True(t, res)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, aclrecordproto.AclUserPermissions_Writer, aclList.AclState().UserStates()[identity].Permissions)
|
||||
}
|
||||
@@ -1,16 +1,22 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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/anytypeio/any-sync/app/logger"
|
||||
aclrecordproto2 "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/keychain"
|
||||
"github.com/anytypeio/any-sync/util/keys"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/any-sync/util/keys/symmetric"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
"hash/fnv"
|
||||
)
|
||||
|
||||
var log = logger.NewNamedSugared("common.commonspace.acllist")
|
||||
var log = logger.NewNamed("acllist").Sugar()
|
||||
|
||||
var (
|
||||
ErrNoSuchUser = errors.New("no such user")
|
||||
@@ -29,75 +35,86 @@ var (
|
||||
)
|
||||
|
||||
type UserPermissionPair struct {
|
||||
Identity crypto.PubKey
|
||||
Permission aclrecordproto.AclUserPermissions
|
||||
Identity string
|
||||
Permission aclrecordproto2.AclUserPermissions
|
||||
}
|
||||
|
||||
type AclState struct {
|
||||
id string
|
||||
currentReadKeyId string
|
||||
userReadKeys map[string]crypto.SymKey
|
||||
userStates map[string]AclUserState
|
||||
statesAtRecord map[string][]AclUserState
|
||||
key crypto.PrivKey
|
||||
pubKey crypto.PubKey
|
||||
keyStore crypto.KeyStorage
|
||||
totalReadKeys int
|
||||
id string
|
||||
currentReadKeyHash uint64
|
||||
userReadKeys map[uint64]*symmetric.Key
|
||||
userStates map[string]*aclrecordproto2.AclUserState
|
||||
userInvites map[string]*aclrecordproto2.AclUserInvite
|
||||
encryptionKey encryptionkey.PrivKey
|
||||
signingKey signingkey.PrivKey
|
||||
totalReadKeys int
|
||||
|
||||
lastRecordId string
|
||||
identity string
|
||||
permissionsAtRecord map[string][]UserPermissionPair
|
||||
lastRecordId string
|
||||
|
||||
keychain *keychain.Keychain
|
||||
}
|
||||
|
||||
func newAclStateWithKeys(
|
||||
id string,
|
||||
key crypto.PrivKey) (*AclState, error) {
|
||||
signingKey signingkey.PrivKey,
|
||||
encryptionKey encryptionkey.PrivKey) (*AclState, error) {
|
||||
identity, err := signingKey.GetPublic().Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AclState{
|
||||
id: id,
|
||||
key: key,
|
||||
pubKey: key.GetPublic(),
|
||||
userReadKeys: make(map[string]crypto.SymKey),
|
||||
userStates: make(map[string]AclUserState),
|
||||
statesAtRecord: make(map[string][]AclUserState),
|
||||
id: id,
|
||||
identity: string(identity),
|
||||
signingKey: signingKey,
|
||||
encryptionKey: encryptionKey,
|
||||
userReadKeys: make(map[uint64]*symmetric.Key),
|
||||
userStates: make(map[string]*aclrecordproto2.AclUserState),
|
||||
userInvites: make(map[string]*aclrecordproto2.AclUserInvite),
|
||||
permissionsAtRecord: make(map[string][]UserPermissionPair),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newAclState(id string) *AclState {
|
||||
return &AclState{
|
||||
id: id,
|
||||
userReadKeys: make(map[string]crypto.SymKey),
|
||||
userStates: make(map[string]AclUserState),
|
||||
statesAtRecord: make(map[string][]AclUserState),
|
||||
id: id,
|
||||
userReadKeys: make(map[uint64]*symmetric.Key),
|
||||
userStates: make(map[string]*aclrecordproto2.AclUserState),
|
||||
userInvites: make(map[string]*aclrecordproto2.AclUserInvite),
|
||||
permissionsAtRecord: make(map[string][]UserPermissionPair),
|
||||
}
|
||||
}
|
||||
|
||||
func (st *AclState) CurrentReadKeyId() string {
|
||||
return st.currentReadKeyId
|
||||
func (st *AclState) CurrentReadKeyHash() uint64 {
|
||||
return st.currentReadKeyHash
|
||||
}
|
||||
|
||||
func (st *AclState) CurrentReadKey() (crypto.SymKey, error) {
|
||||
key, exists := st.userReadKeys[st.currentReadKeyId]
|
||||
func (st *AclState) CurrentReadKey() (*symmetric.Key, error) {
|
||||
key, exists := st.userReadKeys[st.currentReadKeyHash]
|
||||
if !exists {
|
||||
return nil, ErrNoReadKey
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (st *AclState) UserReadKeys() map[string]crypto.SymKey {
|
||||
func (st *AclState) UserReadKeys() map[uint64]*symmetric.Key {
|
||||
return st.userReadKeys
|
||||
}
|
||||
|
||||
func (st *AclState) StateAtRecord(id string, pubKey crypto.PubKey) (AclUserState, error) {
|
||||
userState, ok := st.statesAtRecord[id]
|
||||
func (st *AclState) PermissionsAtRecord(id string, identity string) (UserPermissionPair, error) {
|
||||
permissions, ok := st.permissionsAtRecord[id]
|
||||
if !ok {
|
||||
log.Errorf("missing record at id %s", id)
|
||||
return AclUserState{}, ErrNoSuchRecord
|
||||
return UserPermissionPair{}, ErrNoSuchRecord
|
||||
}
|
||||
|
||||
for _, perm := range userState {
|
||||
if perm.PubKey.Equals(pubKey) {
|
||||
for _, perm := range permissions {
|
||||
if perm.Identity == identity {
|
||||
return perm, nil
|
||||
}
|
||||
}
|
||||
return AclUserState{}, ErrNoSuchUser
|
||||
return UserPermissionPair{}, ErrNoSuchUser
|
||||
}
|
||||
|
||||
func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
||||
@@ -111,18 +128,24 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
||||
return
|
||||
}
|
||||
if record.Id == st.id {
|
||||
err = st.applyRoot(record)
|
||||
root, ok := record.Model.(*aclrecordproto2.AclRoot)
|
||||
if !ok {
|
||||
return ErrIncorrectRoot
|
||||
}
|
||||
err = st.applyRoot(root)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
st.statesAtRecord[record.Id] = []AclUserState{
|
||||
{PubKey: record.Identity, Permissions: aclrecordproto.AclUserPermissions_Admin},
|
||||
st.permissionsAtRecord[record.Id] = []UserPermissionPair{
|
||||
{Identity: string(root.Identity), Permission: aclrecordproto2.AclUserPermissions_Admin},
|
||||
}
|
||||
return
|
||||
}
|
||||
aclData := &aclrecordproto2.AclData{}
|
||||
|
||||
if record.Model == nil {
|
||||
aclData := &aclrecordproto.AclData{}
|
||||
if record.Model != nil {
|
||||
aclData = record.Model.(*aclrecordproto2.AclData)
|
||||
} else {
|
||||
err = proto.Unmarshal(record.Data, aclData)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -130,90 +153,109 @@ func (st *AclState) applyRecord(record *AclRecord) (err error) {
|
||||
record.Model = aclData
|
||||
}
|
||||
|
||||
err = st.applyChangeData(record)
|
||||
err = st.applyChangeData(aclData, record.CurrentReadKeyHash, record.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// getting all states for users at record
|
||||
var states []AclUserState
|
||||
// getting all permissions for users at record
|
||||
var permissions []UserPermissionPair
|
||||
for _, state := range st.userStates {
|
||||
states = append(states, state)
|
||||
permission := UserPermissionPair{
|
||||
Identity: string(state.Identity),
|
||||
Permission: state.Permissions,
|
||||
}
|
||||
permissions = append(permissions, permission)
|
||||
}
|
||||
|
||||
st.statesAtRecord[record.Id] = states
|
||||
st.permissionsAtRecord[record.Id] = permissions
|
||||
return
|
||||
}
|
||||
|
||||
func (st *AclState) applyRoot(record *AclRecord) (err error) {
|
||||
if st.key != nil && st.pubKey.Equals(record.Identity) {
|
||||
err = st.saveReadKeyFromRoot(record)
|
||||
func (st *AclState) applyRoot(root *aclrecordproto2.AclRoot) (err error) {
|
||||
if st.signingKey != nil && st.encryptionKey != nil && st.identity == string(root.Identity) {
|
||||
err = st.saveReadKeyFromRoot(root)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// adding user to the list
|
||||
userState := AclUserState{
|
||||
PubKey: record.Identity,
|
||||
Permissions: aclrecordproto.AclUserPermissions_Admin,
|
||||
userState := &aclrecordproto2.AclUserState{
|
||||
Identity: root.Identity,
|
||||
EncryptionKey: root.EncryptionKey,
|
||||
Permissions: aclrecordproto2.AclUserPermissions_Admin,
|
||||
}
|
||||
st.currentReadKeyId = record.ReadKeyId
|
||||
st.userStates[mapKeyFromPubKey(record.Identity)] = userState
|
||||
st.currentReadKeyHash = root.CurrentReadKeyHash
|
||||
st.userStates[string(root.Identity)] = userState
|
||||
st.totalReadKeys++
|
||||
return
|
||||
}
|
||||
|
||||
func (st *AclState) saveReadKeyFromRoot(record *AclRecord) (err error) {
|
||||
var readKey crypto.SymKey
|
||||
root, ok := record.Model.(*aclrecordproto.AclRoot)
|
||||
if !ok {
|
||||
return ErrIncorrectRoot
|
||||
}
|
||||
if root.EncryptedReadKey == nil {
|
||||
readKey, err = st.deriveKey()
|
||||
func (st *AclState) saveReadKeyFromRoot(root *aclrecordproto2.AclRoot) (err error) {
|
||||
var readKey *symmetric.Key
|
||||
if len(root.GetDerivationScheme()) != 0 {
|
||||
var encPrivKey []byte
|
||||
encPrivKey, err = st.encryptionKey.Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var signPrivKey []byte
|
||||
signPrivKey, err = st.signingKey.Raw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
readKey, err = aclrecordproto2.AclReadKeyDerive(signPrivKey, encPrivKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
readKey, err = st.decryptReadKey(root.EncryptedReadKey)
|
||||
readKey, _, err = st.decryptReadKeyAndHash(root.EncryptedReadKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
st.userReadKeys[record.Id] = readKey
|
||||
hasher := fnv.New64()
|
||||
_, err = hasher.Write(readKey.Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if hasher.Sum64() != root.CurrentReadKeyHash {
|
||||
return ErrIncorrectRoot
|
||||
}
|
||||
st.userReadKeys[root.CurrentReadKeyHash] = readKey
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (st *AclState) applyChangeData(record *AclRecord) (err error) {
|
||||
func (st *AclState) applyChangeData(changeData *aclrecordproto2.AclData, hash uint64, identity []byte) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if record.ReadKeyId != st.currentReadKeyId {
|
||||
if hash != st.currentReadKeyHash {
|
||||
st.totalReadKeys++
|
||||
st.currentReadKeyId = record.ReadKeyId
|
||||
st.currentReadKeyHash = hash
|
||||
}
|
||||
}()
|
||||
model := record.Model.(*aclrecordproto.AclData)
|
||||
if !st.isUserJoin(model) {
|
||||
|
||||
if !st.isUserJoin(changeData) {
|
||||
// we check signature when we add this to the List, so no need to do it here
|
||||
if _, exists := st.userStates[mapKeyFromPubKey(record.Identity)]; !exists {
|
||||
if _, exists := st.userStates[string(identity)]; !exists {
|
||||
err = ErrNoSuchUser
|
||||
return
|
||||
}
|
||||
|
||||
// only Admins can do non-user join changes
|
||||
if !st.HasPermission(record.Identity, aclrecordproto.AclUserPermissions_Admin) {
|
||||
// TODO: add string encoding
|
||||
err = fmt.Errorf("user %s must have admin permissions", record.Identity.Account())
|
||||
if !st.HasPermission(identity, aclrecordproto2.AclUserPermissions_Admin) {
|
||||
err = fmt.Errorf("user %s must have admin permissions", identity)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, ch := range model.GetAclContent() {
|
||||
if err = st.applyChangeContent(ch, record.Id); err != nil {
|
||||
for _, ch := range changeData.GetAclContent() {
|
||||
if err = st.applyChangeContent(ch); err != nil {
|
||||
log.Info("error while applying changes: %v; ignore", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
@@ -222,29 +264,26 @@ func (st *AclState) applyChangeData(record *AclRecord) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyChangeContent(ch *aclrecordproto.AclContentValue, recordId string) error {
|
||||
func (st *AclState) applyChangeContent(ch *aclrecordproto2.AclContentValue) error {
|
||||
switch {
|
||||
case ch.GetUserPermissionChange() != nil:
|
||||
return st.applyUserPermissionChange(ch.GetUserPermissionChange(), recordId)
|
||||
return st.applyUserPermissionChange(ch.GetUserPermissionChange())
|
||||
case ch.GetUserAdd() != nil:
|
||||
return st.applyUserAdd(ch.GetUserAdd(), recordId)
|
||||
return st.applyUserAdd(ch.GetUserAdd())
|
||||
case ch.GetUserRemove() != nil:
|
||||
return st.applyUserRemove(ch.GetUserRemove(), recordId)
|
||||
return st.applyUserRemove(ch.GetUserRemove())
|
||||
case ch.GetUserInvite() != nil:
|
||||
return st.applyUserInvite(ch.GetUserInvite(), recordId)
|
||||
return st.applyUserInvite(ch.GetUserInvite())
|
||||
case ch.GetUserJoin() != nil:
|
||||
return st.applyUserJoin(ch.GetUserJoin(), recordId)
|
||||
return st.applyUserJoin(ch.GetUserJoin())
|
||||
default:
|
||||
return fmt.Errorf("unexpected change type: %v", ch)
|
||||
}
|
||||
}
|
||||
|
||||
func (st *AclState) applyUserPermissionChange(ch *aclrecordproto.AclUserPermissionChange, recordId string) error {
|
||||
chIdentity, err := st.keyStore.PubKeyFromProto(ch.Identity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, exists := st.userStates[mapKeyFromPubKey(chIdentity)]
|
||||
func (st *AclState) applyUserPermissionChange(ch *aclrecordproto2.AclUserPermissionChange) error {
|
||||
chIdentity := string(ch.Identity)
|
||||
state, exists := st.userStates[chIdentity]
|
||||
if !exists {
|
||||
return ErrNoSuchUser
|
||||
}
|
||||
@@ -253,38 +292,131 @@ func (st *AclState) applyUserPermissionChange(ch *aclrecordproto.AclUserPermissi
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyUserInvite(ch *aclrecordproto.AclUserInvite, recordId string) error {
|
||||
// TODO: check old code and bring it back :-)
|
||||
func (st *AclState) applyUserInvite(ch *aclrecordproto2.AclUserInvite) error {
|
||||
st.userInvites[string(ch.AcceptPublicKey)] = ch
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyUserJoin(ch *aclrecordproto.AclUserJoin, recordId string) error {
|
||||
return nil
|
||||
}
|
||||
func (st *AclState) applyUserJoin(ch *aclrecordproto2.AclUserJoin) error {
|
||||
invite, exists := st.userInvites[string(ch.AcceptPubKey)]
|
||||
if !exists {
|
||||
return fmt.Errorf("no such invite with such public key %s", keys.EncodeBytesToString(ch.AcceptPubKey))
|
||||
}
|
||||
chIdentity := string(ch.Identity)
|
||||
|
||||
func (st *AclState) applyUserAdd(ch *aclrecordproto.AclUserAdd, recordId string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyUserRemove(ch *aclrecordproto.AclUserRemove, recordId string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) decryptReadKey(msg []byte) (crypto.SymKey, error) {
|
||||
decrypted, err := st.key.Decrypt(msg)
|
||||
if err != nil {
|
||||
return nil, ErrFailedToDecrypt
|
||||
if _, exists = st.userStates[chIdentity]; exists {
|
||||
return ErrUserAlreadyExists
|
||||
}
|
||||
|
||||
key, err := crypto.UnmarshallAESKey(decrypted)
|
||||
// validating signature
|
||||
signature := ch.GetAcceptSignature()
|
||||
verificationKey, err := signingkey.NewSigningEd25519PubKeyFromBytes(invite.AcceptPublicKey)
|
||||
if err != nil {
|
||||
return nil, ErrFailedToDecrypt
|
||||
return fmt.Errorf("public key verifying invite accepts is given in incorrect format: %v", err)
|
||||
}
|
||||
return key, nil
|
||||
|
||||
res, err := verificationKey.Verify(ch.Identity, signature)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verification returned error: %w", err)
|
||||
}
|
||||
if !res {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
|
||||
// if ourselves -> we need to decrypt the read keys
|
||||
if st.identity == chIdentity {
|
||||
for _, key := range ch.EncryptedReadKeys {
|
||||
key, hash, err := st.decryptReadKeyAndHash(key)
|
||||
if err != nil {
|
||||
return ErrFailedToDecrypt
|
||||
}
|
||||
|
||||
st.userReadKeys[hash] = key
|
||||
}
|
||||
}
|
||||
|
||||
// adding user to the list
|
||||
userState := &aclrecordproto2.AclUserState{
|
||||
Identity: ch.Identity,
|
||||
EncryptionKey: ch.EncryptionKey,
|
||||
Permissions: invite.Permissions,
|
||||
}
|
||||
st.userStates[chIdentity] = userState
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) HasPermission(identity crypto.PubKey, permission aclrecordproto.AclUserPermissions) bool {
|
||||
state, exists := st.userStates[mapKeyFromPubKey(identity)]
|
||||
func (st *AclState) applyUserAdd(ch *aclrecordproto2.AclUserAdd) error {
|
||||
chIdentity := string(ch.Identity)
|
||||
if _, exists := st.userStates[chIdentity]; exists {
|
||||
return ErrUserAlreadyExists
|
||||
}
|
||||
|
||||
st.userStates[chIdentity] = &aclrecordproto2.AclUserState{
|
||||
Identity: ch.Identity,
|
||||
EncryptionKey: ch.EncryptionKey,
|
||||
Permissions: ch.Permissions,
|
||||
}
|
||||
|
||||
if chIdentity == st.identity {
|
||||
for _, key := range ch.EncryptedReadKeys {
|
||||
key, hash, err := st.decryptReadKeyAndHash(key)
|
||||
if err != nil {
|
||||
return ErrFailedToDecrypt
|
||||
}
|
||||
|
||||
st.userReadKeys[hash] = key
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) applyUserRemove(ch *aclrecordproto2.AclUserRemove) error {
|
||||
chIdentity := string(ch.Identity)
|
||||
if chIdentity == st.identity {
|
||||
return ErrDocumentForbidden
|
||||
}
|
||||
|
||||
if _, exists := st.userStates[chIdentity]; !exists {
|
||||
return ErrNoSuchUser
|
||||
}
|
||||
|
||||
delete(st.userStates, chIdentity)
|
||||
|
||||
for _, replace := range ch.ReadKeyReplaces {
|
||||
repIdentity := string(replace.Identity)
|
||||
// if this is our identity then we have to decrypt the key
|
||||
if repIdentity == st.identity {
|
||||
key, hash, err := st.decryptReadKeyAndHash(replace.EncryptedReadKey)
|
||||
if err != nil {
|
||||
return ErrFailedToDecrypt
|
||||
}
|
||||
|
||||
st.userReadKeys[hash] = key
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *AclState) decryptReadKeyAndHash(msg []byte) (*symmetric.Key, uint64, error) {
|
||||
decrypted, err := st.encryptionKey.Decrypt(msg)
|
||||
if err != nil {
|
||||
return nil, 0, ErrFailedToDecrypt
|
||||
}
|
||||
|
||||
key, err := symmetric.FromBytes(decrypted)
|
||||
if err != nil {
|
||||
return nil, 0, ErrFailedToDecrypt
|
||||
}
|
||||
|
||||
hasher := fnv.New64()
|
||||
hasher.Write(decrypted)
|
||||
return key, hasher.Sum64(), nil
|
||||
}
|
||||
|
||||
func (st *AclState) HasPermission(identity []byte, permission aclrecordproto2.AclUserPermissions) bool {
|
||||
state, exists := st.userStates[string(identity)]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
@@ -292,35 +424,41 @@ func (st *AclState) HasPermission(identity crypto.PubKey, permission aclrecordpr
|
||||
return state.Permissions == permission
|
||||
}
|
||||
|
||||
func (st *AclState) isUserJoin(data *aclrecordproto.AclData) bool {
|
||||
func (st *AclState) isUserJoin(data *aclrecordproto2.AclData) bool {
|
||||
// if we have a UserJoin, then it should always be the first one applied
|
||||
return data.GetAclContent() != nil && data.GetAclContent()[0].GetUserJoin() != nil
|
||||
}
|
||||
|
||||
func (st *AclState) isUserAdd(data *aclrecordproto.AclData, identity []byte) bool {
|
||||
return false
|
||||
func (st *AclState) isUserAdd(data *aclrecordproto2.AclData, identity []byte) bool {
|
||||
// if we have a UserAdd, then it should always be the first one applied
|
||||
userAdd := data.GetAclContent()[0].GetUserAdd()
|
||||
return data.GetAclContent() != nil && userAdd != nil && bytes.Compare(userAdd.GetIdentity(), identity) == 0
|
||||
}
|
||||
|
||||
func (st *AclState) UserStates() map[string]AclUserState {
|
||||
func (st *AclState) UserStates() map[string]*aclrecordproto2.AclUserState {
|
||||
return st.userStates
|
||||
}
|
||||
|
||||
func (st *AclState) Invite(acceptPubKey []byte) (invite *aclrecordproto.AclUserInvite, err error) {
|
||||
func (st *AclState) Invite(acceptPubKey []byte) (invite *aclrecordproto2.AclUserInvite, err error) {
|
||||
invite, exists := st.userInvites[string(acceptPubKey)]
|
||||
if !exists {
|
||||
err = ErrNoSuchInvite
|
||||
return
|
||||
}
|
||||
if len(invite.EncryptedReadKeys) != st.totalReadKeys {
|
||||
err = ErrOldInvite
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (st *AclState) UserKeys() (encKey encryptionkey.PrivKey, signKey signingkey.PrivKey) {
|
||||
return st.encryptionKey, st.signingKey
|
||||
}
|
||||
|
||||
func (st *AclState) Identity() []byte {
|
||||
return []byte(st.identity)
|
||||
}
|
||||
|
||||
func (st *AclState) LastRecordId() string {
|
||||
return st.lastRecordId
|
||||
}
|
||||
|
||||
func (st *AclState) deriveKey() (crypto.SymKey, error) {
|
||||
keyBytes, err := st.key.Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return crypto.DeriveSymmetricKey(keyBytes, crypto.AnysyncSpacePath)
|
||||
}
|
||||
|
||||
func mapKeyFromPubKey(pubKey crypto.PubKey) string {
|
||||
return string(pubKey.Storage())
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
type aclStateBuilder struct {
|
||||
privKey crypto.PrivKey
|
||||
id string
|
||||
signPrivKey signingkey.PrivKey
|
||||
encPrivKey encryptionkey.PrivKey
|
||||
id string
|
||||
}
|
||||
|
||||
func newAclStateBuilderWithIdentity(keys *accountdata.AccountKeys) *aclStateBuilder {
|
||||
func newAclStateBuilderWithIdentity(accountData *accountdata.AccountData) *aclStateBuilder {
|
||||
return &aclStateBuilder{
|
||||
privKey: keys.SignKey,
|
||||
signPrivKey: accountData.SignKey,
|
||||
encPrivKey: accountData.EncKey,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +28,8 @@ func (sb *aclStateBuilder) Init(id string) {
|
||||
}
|
||||
|
||||
func (sb *aclStateBuilder) Build(records []*AclRecord) (state *AclState, err error) {
|
||||
if sb.privKey != nil {
|
||||
state, err = newAclStateWithKeys(sb.id, sb.privKey)
|
||||
if sb.encPrivKey != nil && sb.signPrivKey != nil {
|
||||
state, err = newAclStateWithKeys(sb.id, sb.signPrivKey, sb.encPrivKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
//go:generate mockgen -destination mock_list/mock_list.go github.com/anyproto/any-sync/commonspace/object/acl/list AclList
|
||||
//go:generate mockgen -destination mock_list/mock_list.go github.com/anytypeio/any-sync/commonspace/object/acl/list AclList
|
||||
package list
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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"
|
||||
"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/commonspace/object/keychain"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -33,7 +33,6 @@ type AclList interface {
|
||||
Get(id string) (*AclRecord, error)
|
||||
Iterate(iterFunc IterFunc)
|
||||
IterateFrom(startId string, iterFunc IterFunc)
|
||||
KeyStorage() crypto.KeyStorage
|
||||
|
||||
AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added bool, err error)
|
||||
|
||||
@@ -48,25 +47,23 @@ type aclList struct {
|
||||
|
||||
stateBuilder *aclStateBuilder
|
||||
recordBuilder AclRecordBuilder
|
||||
keyStorage crypto.KeyStorage
|
||||
aclState *AclState
|
||||
keychain *keychain.Keychain
|
||||
storage liststorage.ListStorage
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func BuildAclListWithIdentity(acc *accountdata.AccountKeys, storage liststorage.ListStorage) (AclList, error) {
|
||||
func BuildAclListWithIdentity(acc *accountdata.AccountData, storage liststorage.ListStorage) (AclList, error) {
|
||||
builder := newAclStateBuilderWithIdentity(acc)
|
||||
keyStorage := crypto.NewKeyStorage()
|
||||
return build(storage.Id(), keyStorage, builder, NewAclRecordBuilder(storage.Id(), keyStorage), storage)
|
||||
return build(storage.Id(), builder, newAclRecordBuilder(storage.Id(), keychain.NewKeychain()), storage)
|
||||
}
|
||||
|
||||
func BuildAclList(storage liststorage.ListStorage) (AclList, error) {
|
||||
keyStorage := crypto.NewKeyStorage()
|
||||
return build(storage.Id(), keyStorage, newAclStateBuilder(), NewAclRecordBuilder(storage.Id(), crypto.NewKeyStorage()), storage)
|
||||
return build(storage.Id(), newAclStateBuilder(), newAclRecordBuilder(storage.Id(), keychain.NewKeychain()), storage)
|
||||
}
|
||||
|
||||
func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder, storage liststorage.ListStorage) (list AclList, err error) {
|
||||
func build(id string, stateBuilder *aclStateBuilder, recBuilder AclRecordBuilder, storage liststorage.ListStorage) (list AclList, err error) {
|
||||
head, err := storage.Head()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -77,7 +74,7 @@ func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilde
|
||||
return
|
||||
}
|
||||
|
||||
record, err := recBuilder.Unmarshall(rawRecordWithId)
|
||||
record, err := recBuilder.ConvertFromRaw(rawRecordWithId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -89,7 +86,7 @@ func build(id string, keyStorage crypto.KeyStorage, stateBuilder *aclStateBuilde
|
||||
return
|
||||
}
|
||||
|
||||
record, err = recBuilder.Unmarshall(rawRecordWithId)
|
||||
record, err = recBuilder.ConvertFromRaw(rawRecordWithId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -140,7 +137,7 @@ func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added
|
||||
if _, ok := a.indexes[rawRec.Id]; ok {
|
||||
return
|
||||
}
|
||||
record, err := a.recordBuilder.Unmarshall(rawRec)
|
||||
record, err := a.recordBuilder.ConvertFromRaw(rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -159,7 +156,7 @@ func (a *aclList) AddRawRecord(rawRec *aclrecordproto.RawAclRecordWithId) (added
|
||||
}
|
||||
|
||||
func (a *aclList) IsValidNext(rawRec *aclrecordproto.RawAclRecordWithId) (err error) {
|
||||
_, err = a.recordBuilder.Unmarshall(rawRec)
|
||||
_, err = a.recordBuilder.ConvertFromRaw(rawRec)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -179,10 +176,6 @@ func (a *aclList) AclState() *AclState {
|
||||
return a.aclState
|
||||
}
|
||||
|
||||
func (a *aclList) KeyStorage() crypto.KeyStorage {
|
||||
return a.keyStorage
|
||||
}
|
||||
|
||||
func (a *aclList) IsAfter(first string, second string) (bool, error) {
|
||||
firstRec, okFirst := a.indexes[first]
|
||||
secondRec, okSecond := a.indexes[second]
|
||||
|
||||
@@ -1,16 +1,91 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/testutils/acllistbuilder"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAclList_BuildRoot(t *testing.T) {
|
||||
randomKeys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
randomAcl, err := NewTestDerivedAcl("spaceId", randomKeys)
|
||||
require.NoError(t, err)
|
||||
fmt.Println(randomAcl.Id())
|
||||
func TestAclList_AclState_UserInviteAndJoin(t *testing.T) {
|
||||
st, err := acllistbuilder.NewListStorageWithTestName("userjoinexample.yml")
|
||||
require.NoError(t, err, "building storage should not result in error")
|
||||
|
||||
keychain := st.(*acllistbuilder.AclListStorageBuilder).GetKeychain()
|
||||
|
||||
aclList, err := BuildAclList(st)
|
||||
require.NoError(t, err, "building acl list should be without error")
|
||||
|
||||
idA := keychain.GetIdentity("A")
|
||||
idB := keychain.GetIdentity("B")
|
||||
idC := keychain.GetIdentity("C")
|
||||
|
||||
// checking final state
|
||||
assert.Equal(t, aclrecordproto.AclUserPermissions_Admin, aclList.AclState().UserStates()[idA].Permissions)
|
||||
assert.Equal(t, aclrecordproto.AclUserPermissions_Writer, aclList.AclState().UserStates()[idB].Permissions)
|
||||
assert.Equal(t, aclrecordproto.AclUserPermissions_Reader, aclList.AclState().UserStates()[idC].Permissions)
|
||||
assert.Equal(t, aclList.Head().CurrentReadKeyHash, aclList.AclState().CurrentReadKeyHash())
|
||||
|
||||
var records []*AclRecord
|
||||
aclList.Iterate(func(record *AclRecord) (IsContinue bool) {
|
||||
records = append(records, record)
|
||||
return true
|
||||
})
|
||||
|
||||
// checking permissions at specific records
|
||||
assert.Equal(t, 3, len(records))
|
||||
|
||||
_, err = aclList.AclState().PermissionsAtRecord(records[1].Id, idB)
|
||||
assert.Error(t, err, "B should have no permissions at record 1")
|
||||
|
||||
perm, err := aclList.AclState().PermissionsAtRecord(records[2].Id, idB)
|
||||
assert.NoError(t, err, "should have no error with permissions of B in the record 2")
|
||||
assert.Equal(t, UserPermissionPair{
|
||||
Identity: idB,
|
||||
Permission: aclrecordproto.AclUserPermissions_Writer,
|
||||
}, perm)
|
||||
}
|
||||
|
||||
func TestAclList_AclState_UserJoinAndRemove(t *testing.T) {
|
||||
st, err := acllistbuilder.NewListStorageWithTestName("userremoveexample.yml")
|
||||
require.NoError(t, err, "building storage should not result in error")
|
||||
|
||||
keychain := st.(*acllistbuilder.AclListStorageBuilder).GetKeychain()
|
||||
|
||||
aclList, err := BuildAclList(st)
|
||||
require.NoError(t, err, "building acl list should be without error")
|
||||
|
||||
idA := keychain.GetIdentity("A")
|
||||
idB := keychain.GetIdentity("B")
|
||||
idC := keychain.GetIdentity("C")
|
||||
|
||||
// checking final state
|
||||
assert.Equal(t, aclrecordproto.AclUserPermissions_Admin, aclList.AclState().UserStates()[idA].Permissions)
|
||||
assert.Equal(t, aclrecordproto.AclUserPermissions_Reader, aclList.AclState().UserStates()[idC].Permissions)
|
||||
assert.Equal(t, aclList.Head().CurrentReadKeyHash, aclList.AclState().CurrentReadKeyHash())
|
||||
|
||||
_, exists := aclList.AclState().UserStates()[idB]
|
||||
assert.Equal(t, false, exists)
|
||||
|
||||
var records []*AclRecord
|
||||
aclList.Iterate(func(record *AclRecord) (IsContinue bool) {
|
||||
records = append(records, record)
|
||||
return true
|
||||
})
|
||||
|
||||
// checking permissions at specific records
|
||||
assert.Equal(t, 4, len(records))
|
||||
|
||||
assert.NotEqual(t, records[2].CurrentReadKeyHash, aclList.AclState().CurrentReadKeyHash())
|
||||
|
||||
perm, err := aclList.AclState().PermissionsAtRecord(records[2].Id, idB)
|
||||
assert.NoError(t, err, "should have no error with permissions of B in the record 2")
|
||||
assert.Equal(t, UserPermissionPair{
|
||||
Identity: idB,
|
||||
Permission: aclrecordproto.AclUserPermissions_Writer,
|
||||
}, perm)
|
||||
|
||||
_, err = aclList.AclState().PermissionsAtRecord(records[3].Id, idB)
|
||||
assert.Error(t, err, "B should have no permissions at record 3, because user should be removed")
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"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) {
|
||||
builder := NewAclRecordBuilder("", crypto.NewKeyStorage())
|
||||
masterKey, _, err := crypto.GenerateRandomEd25519KeyPair()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root, err := builder.BuildRoot(RootContent{
|
||||
PrivKey: keys.SignKey,
|
||||
SpaceId: spaceId,
|
||||
MasterKey: masterKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, err := liststorage.NewInMemoryAclListStorage(root.Id, []*aclrecordproto.RawAclRecordWithId{
|
||||
root,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BuildAclListWithIdentity(keys, st)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/acl/list (interfaces: AclList)
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/acl/list (interfaces: AclList)
|
||||
|
||||
// Package mock_list is a generated GoMock package.
|
||||
package mock_list
|
||||
@@ -7,9 +7,8 @@ package mock_list
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
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"
|
||||
aclrecordproto "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
list "github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
@@ -161,20 +160,6 @@ func (mr *MockAclListMockRecorder) IterateFrom(arg0, arg1 interface{}) *gomock.C
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateFrom", reflect.TypeOf((*MockAclList)(nil).IterateFrom), arg0, arg1)
|
||||
}
|
||||
|
||||
// KeyStorage mocks base method.
|
||||
func (m *MockAclList) KeyStorage() crypto.KeyStorage {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "KeyStorage")
|
||||
ret0, _ := ret[0].(crypto.KeyStorage)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// KeyStorage indicates an expected call of KeyStorage.
|
||||
func (mr *MockAclListMockRecorder) KeyStorage() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyStorage", reflect.TypeOf((*MockAclList)(nil).KeyStorage))
|
||||
}
|
||||
|
||||
// Lock mocks base method.
|
||||
func (m *MockAclList) Lock() {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
)
|
||||
|
||||
type AclRecord struct {
|
||||
Id string
|
||||
PrevId string
|
||||
ReadKeyId string
|
||||
Timestamp int64
|
||||
Data []byte
|
||||
Identity crypto.PubKey
|
||||
Model interface{}
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
type AclUserState struct {
|
||||
PubKey crypto.PubKey
|
||||
Permissions aclrecordproto.AclUserPermissions
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package list
|
||||
|
||||
type AclRecord struct {
|
||||
Id string
|
||||
PrevId string
|
||||
CurrentReadKeyHash uint64
|
||||
Timestamp int64
|
||||
Data []byte
|
||||
Identity []byte
|
||||
Model interface{}
|
||||
Signature []byte
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package liststorage
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//go:generate mockgen -destination mock_liststorage/mock_liststorage.go github.com/anyproto/any-sync/commonspace/object/acl/liststorage ListStorage
|
||||
//go:generate mockgen -destination mock_liststorage/mock_liststorage.go github.com/anytypeio/any-sync/commonspace/object/acl/liststorage ListStorage
|
||||
package liststorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -13,10 +13,6 @@ var (
|
||||
ErrUnknownRecord = errors.New("record doesn't exist")
|
||||
)
|
||||
|
||||
type Exporter interface {
|
||||
ListStorage(root *aclrecordproto.RawAclRecordWithId) (ListStorage, error)
|
||||
}
|
||||
|
||||
type ListStorage interface {
|
||||
Id() string
|
||||
Root() (*aclrecordproto.RawAclRecordWithId, error)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/acl/liststorage (interfaces: ListStorage)
|
||||
// Source: github.com/anytypeio/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/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
aclrecordproto "github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,43 +1,21 @@
|
||||
package syncacl
|
||||
|
||||
import (
|
||||
"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"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
)
|
||||
|
||||
const CName = "common.acl.syncacl"
|
||||
|
||||
func New() *SyncAcl {
|
||||
return &SyncAcl{}
|
||||
}
|
||||
|
||||
type SyncAcl struct {
|
||||
list.AclList
|
||||
synchandler.SyncHandler
|
||||
streamPool objectsync.StreamPool
|
||||
}
|
||||
|
||||
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
|
||||
func NewSyncAcl(aclList list.AclList, streamPool objectsync.StreamPool) *SyncAcl {
|
||||
return &SyncAcl{
|
||||
AclList: aclList,
|
||||
SyncHandler: nil,
|
||||
streamPool: streamPool,
|
||||
}
|
||||
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/anyproto/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"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"
|
||||
)
|
||||
|
||||
type syncAclHandler struct {
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package acllistbuilder
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/util/keys"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/any-sync/util/keys/symmetric"
|
||||
"hash/fnv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SymKey struct {
|
||||
Hash uint64
|
||||
Key *symmetric.Key
|
||||
}
|
||||
|
||||
type YAMLKeychain struct {
|
||||
SigningKeysByYAMLName map[string]signingkey.PrivKey
|
||||
SigningKeysByRealIdentity map[string]signingkey.PrivKey
|
||||
EncryptionKeysByYAMLName map[string]encryptionkey.PrivKey
|
||||
ReadKeysByYAMLName map[string]*SymKey
|
||||
ReadKeysByHash map[uint64]*SymKey
|
||||
GeneratedIdentities map[string]string
|
||||
}
|
||||
|
||||
func NewKeychain() *YAMLKeychain {
|
||||
return &YAMLKeychain{
|
||||
SigningKeysByYAMLName: map[string]signingkey.PrivKey{},
|
||||
SigningKeysByRealIdentity: map[string]signingkey.PrivKey{},
|
||||
EncryptionKeysByYAMLName: map[string]encryptionkey.PrivKey{},
|
||||
GeneratedIdentities: map[string]string{},
|
||||
ReadKeysByYAMLName: map[string]*SymKey{},
|
||||
ReadKeysByHash: map[uint64]*SymKey{},
|
||||
}
|
||||
}
|
||||
|
||||
func (k *YAMLKeychain) ParseKeys(keys *Keys) {
|
||||
for _, encKey := range keys.Enc {
|
||||
k.AddEncryptionKey(encKey)
|
||||
}
|
||||
|
||||
for _, signKey := range keys.Sign {
|
||||
k.AddSigningKey(signKey)
|
||||
}
|
||||
|
||||
for _, readKey := range keys.Read {
|
||||
k.AddReadKey(readKey)
|
||||
}
|
||||
}
|
||||
|
||||
func (k *YAMLKeychain) AddEncryptionKey(key *Key) {
|
||||
if _, exists := k.EncryptionKeysByYAMLName[key.Name]; exists {
|
||||
return
|
||||
}
|
||||
var (
|
||||
newPrivKey encryptionkey.PrivKey
|
||||
err error
|
||||
)
|
||||
if key.Value == "generated" {
|
||||
newPrivKey, _, err = encryptionkey.GenerateRandomRSAKeyPair(2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
newPrivKey, err = keys.DecodeKeyFromString(key.Value, encryptionkey.NewEncryptionRsaPrivKeyFromBytes, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
k.EncryptionKeysByYAMLName[key.Name] = newPrivKey
|
||||
}
|
||||
|
||||
func (k *YAMLKeychain) AddSigningKey(key *Key) {
|
||||
if _, exists := k.SigningKeysByYAMLName[key.Name]; exists {
|
||||
return
|
||||
}
|
||||
var (
|
||||
newPrivKey signingkey.PrivKey
|
||||
pubKey signingkey.PubKey
|
||||
err error
|
||||
)
|
||||
if key.Value == "generated" {
|
||||
newPrivKey, pubKey, err = signingkey.GenerateRandomEd25519KeyPair()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
newPrivKey, err = keys.DecodeKeyFromString(key.Value, signingkey.NewSigningEd25519PrivKeyFromBytes, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pubKey = newPrivKey.GetPublic()
|
||||
}
|
||||
|
||||
k.SigningKeysByYAMLName[key.Name] = newPrivKey
|
||||
rawPubKey, err := pubKey.Raw()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
encoded := string(rawPubKey)
|
||||
|
||||
k.SigningKeysByRealIdentity[encoded] = newPrivKey
|
||||
k.GeneratedIdentities[key.Name] = encoded
|
||||
}
|
||||
|
||||
func (k *YAMLKeychain) AddReadKey(key *Key) {
|
||||
if _, exists := k.ReadKeysByYAMLName[key.Name]; exists {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
rkey *symmetric.Key
|
||||
err error
|
||||
)
|
||||
if key.Value == "generated" {
|
||||
rkey, err = symmetric.NewRandom()
|
||||
if err != nil {
|
||||
panic("should be able to generate symmetric key")
|
||||
}
|
||||
} else if key.Value == "derived" {
|
||||
signKey, _ := k.SigningKeysByYAMLName[key.Name].Raw()
|
||||
encKey, _ := k.EncryptionKeysByYAMLName[key.Name].Raw()
|
||||
rkey, err = aclrecordproto.AclReadKeyDerive(signKey, encKey)
|
||||
if err != nil {
|
||||
panic("should be able to derive symmetric key")
|
||||
}
|
||||
} else {
|
||||
rkey, err = symmetric.FromString(key.Value)
|
||||
if err != nil {
|
||||
panic("should be able to parse symmetric key")
|
||||
}
|
||||
}
|
||||
|
||||
hasher := fnv.New64()
|
||||
hasher.Write(rkey.Bytes())
|
||||
|
||||
k.ReadKeysByYAMLName[key.Name] = &SymKey{
|
||||
Hash: hasher.Sum64(),
|
||||
Key: rkey,
|
||||
}
|
||||
k.ReadKeysByHash[hasher.Sum64()] = &SymKey{
|
||||
Hash: hasher.Sum64(),
|
||||
Key: rkey,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *YAMLKeychain) AddKey(key *Key) {
|
||||
parts := strings.Split(key.Name, ".")
|
||||
if len(parts) != 3 {
|
||||
panic("cannot parse a key")
|
||||
}
|
||||
|
||||
switch parts[1] {
|
||||
case "Signature":
|
||||
k.AddSigningKey(key)
|
||||
case "Enc":
|
||||
k.AddEncryptionKey(key)
|
||||
case "Read":
|
||||
k.AddReadKey(key)
|
||||
default:
|
||||
panic("incorrect format")
|
||||
}
|
||||
}
|
||||
|
||||
func (k *YAMLKeychain) GetKey(key string) interface{} {
|
||||
parts := strings.Split(key, ".")
|
||||
if len(parts) != 3 {
|
||||
panic("cannot parse a key")
|
||||
}
|
||||
name := parts[2]
|
||||
|
||||
switch parts[1] {
|
||||
case "Sign":
|
||||
if key, exists := k.SigningKeysByYAMLName[name]; exists {
|
||||
return key
|
||||
}
|
||||
case "Enc":
|
||||
if key, exists := k.EncryptionKeysByYAMLName[name]; exists {
|
||||
return key
|
||||
}
|
||||
case "Read":
|
||||
if key, exists := k.ReadKeysByYAMLName[name]; exists {
|
||||
return key
|
||||
}
|
||||
default:
|
||||
panic("incorrect format")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *YAMLKeychain) GetIdentity(name string) string {
|
||||
return k.GeneratedIdentities[name]
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
package acllistbuilder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/liststorage"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/testutils/yamltests"
|
||||
"github.com/anytypeio/any-sync/util/cidutil"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/encryptionkey"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/any-sync/util/keys/symmetric"
|
||||
"gopkg.in/yaml.v3"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type AclListStorageBuilder struct {
|
||||
liststorage.ListStorage
|
||||
keychain *YAMLKeychain
|
||||
}
|
||||
|
||||
func NewAclListStorageBuilder(keychain *YAMLKeychain) *AclListStorageBuilder {
|
||||
return &AclListStorageBuilder{
|
||||
keychain: keychain,
|
||||
}
|
||||
}
|
||||
|
||||
func NewListStorageWithTestName(name string) (liststorage.ListStorage, error) {
|
||||
filePath := path.Join(yamltests.Path(), name)
|
||||
return NewAclListStorageBuilderFromFile(filePath)
|
||||
}
|
||||
|
||||
func NewAclListStorageBuilderFromFile(file string) (*AclListStorageBuilder, error) {
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ymlTree := YMLList{}
|
||||
err = yaml.Unmarshal(content, &ymlTree)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tb := NewAclListStorageBuilder(NewKeychain())
|
||||
tb.Parse(&ymlTree)
|
||||
|
||||
return tb, nil
|
||||
}
|
||||
|
||||
func (t *AclListStorageBuilder) createRaw(rec proto.Marshaler, identity []byte) *aclrecordproto.RawAclRecordWithId {
|
||||
protoMarshalled, err := rec.Marshal()
|
||||
if err != nil {
|
||||
panic("should be able to marshal final acl message!")
|
||||
}
|
||||
|
||||
signature, err := t.keychain.SigningKeysByRealIdentity[string(identity)].Sign(protoMarshalled)
|
||||
if err != nil {
|
||||
panic("should be able to sign final acl message!")
|
||||
}
|
||||
|
||||
rawRec := &aclrecordproto.RawAclRecord{
|
||||
Payload: protoMarshalled,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
rawMarshalled, err := proto.Marshal(rawRec)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
id, _ := cidutil.NewCidFromBytes(rawMarshalled)
|
||||
|
||||
return &aclrecordproto.RawAclRecordWithId{
|
||||
Payload: rawMarshalled,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *AclListStorageBuilder) GetKeychain() *YAMLKeychain {
|
||||
return t.keychain
|
||||
}
|
||||
|
||||
func (t *AclListStorageBuilder) Parse(l *YMLList) {
|
||||
// Just to clarify - we are generating new identities for the ones that
|
||||
// are specified in the yml file, because our identities should be Ed25519
|
||||
// the same thing is happening for the encryption keys
|
||||
t.keychain.ParseKeys(&l.Keys)
|
||||
rawRoot := t.parseRoot(l.Root)
|
||||
var err error
|
||||
t.ListStorage, err = liststorage.NewInMemoryAclListStorage(rawRoot.Id, []*aclrecordproto.RawAclRecordWithId{rawRoot})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
prevId := rawRoot.Id
|
||||
for _, rec := range l.Records {
|
||||
newRecord := t.parseRecord(rec, prevId)
|
||||
rawRecord := t.createRaw(newRecord, newRecord.Identity)
|
||||
err = t.AddRawRecord(context.Background(), rawRecord)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
prevId = rawRecord.Id
|
||||
}
|
||||
t.SetHead(prevId)
|
||||
}
|
||||
|
||||
func (t *AclListStorageBuilder) parseRecord(rec *Record, prevId string) *aclrecordproto.AclRecord {
|
||||
k := t.keychain.GetKey(rec.ReadKey).(*SymKey)
|
||||
var aclChangeContents []*aclrecordproto.AclContentValue
|
||||
for _, ch := range rec.AclChanges {
|
||||
aclChangeContent := t.parseAclChange(ch)
|
||||
aclChangeContents = append(aclChangeContents, aclChangeContent)
|
||||
}
|
||||
data := &aclrecordproto.AclData{
|
||||
AclContent: aclChangeContents,
|
||||
}
|
||||
bytes, _ := data.Marshal()
|
||||
|
||||
return &aclrecordproto.AclRecord{
|
||||
PrevId: prevId,
|
||||
Identity: []byte(t.keychain.GetIdentity(rec.Identity)),
|
||||
Data: bytes,
|
||||
CurrentReadKeyHash: k.Hash,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *AclListStorageBuilder) parseAclChange(ch *AclChange) (convCh *aclrecordproto.AclContentValue) {
|
||||
switch {
|
||||
case ch.UserAdd != nil:
|
||||
add := ch.UserAdd
|
||||
|
||||
encKey := t.keychain.GetKey(add.EncryptionKey).(encryptionkey.PrivKey)
|
||||
rawKey, _ := encKey.GetPublic().Raw()
|
||||
|
||||
convCh = &aclrecordproto.AclContentValue{
|
||||
Value: &aclrecordproto.AclContentValue_UserAdd{
|
||||
UserAdd: &aclrecordproto.AclUserAdd{
|
||||
Identity: []byte(t.keychain.GetIdentity(add.Identity)),
|
||||
EncryptionKey: rawKey,
|
||||
EncryptedReadKeys: t.encryptReadKeysWithPubKey(add.EncryptedReadKeys, encKey),
|
||||
Permissions: t.convertPermission(add.Permission),
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserJoin != nil:
|
||||
join := ch.UserJoin
|
||||
|
||||
encKey := t.keychain.GetKey(join.EncryptionKey).(encryptionkey.PrivKey)
|
||||
rawKey, _ := encKey.GetPublic().Raw()
|
||||
|
||||
idKey, _ := t.keychain.SigningKeysByYAMLName[join.Identity].GetPublic().Raw()
|
||||
signKey := t.keychain.GetKey(join.AcceptKey).(signingkey.PrivKey)
|
||||
signature, err := signKey.Sign(idKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
acceptPubKey, _ := signKey.GetPublic().Raw()
|
||||
|
||||
convCh = &aclrecordproto.AclContentValue{
|
||||
Value: &aclrecordproto.AclContentValue_UserJoin{
|
||||
UserJoin: &aclrecordproto.AclUserJoin{
|
||||
Identity: []byte(t.keychain.GetIdentity(join.Identity)),
|
||||
EncryptionKey: rawKey,
|
||||
AcceptSignature: signature,
|
||||
AcceptPubKey: acceptPubKey,
|
||||
EncryptedReadKeys: t.encryptReadKeysWithPubKey(join.EncryptedReadKeys, encKey),
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserInvite != nil:
|
||||
invite := ch.UserInvite
|
||||
rawAcceptKey, _ := t.keychain.GetKey(invite.AcceptKey).(signingkey.PrivKey).GetPublic().Raw()
|
||||
hash := t.keychain.GetKey(invite.EncryptionKey).(*SymKey).Hash
|
||||
encKey := t.keychain.ReadKeysByHash[hash]
|
||||
|
||||
convCh = &aclrecordproto.AclContentValue{
|
||||
Value: &aclrecordproto.AclContentValue_UserInvite{
|
||||
UserInvite: &aclrecordproto.AclUserInvite{
|
||||
AcceptPublicKey: rawAcceptKey,
|
||||
EncryptSymKeyHash: hash,
|
||||
EncryptedReadKeys: t.encryptReadKeysWithSymKey(invite.EncryptedReadKeys, encKey.Key),
|
||||
Permissions: t.convertPermission(invite.Permissions),
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserPermissionChange != nil:
|
||||
permissionChange := ch.UserPermissionChange
|
||||
|
||||
convCh = &aclrecordproto.AclContentValue{
|
||||
Value: &aclrecordproto.AclContentValue_UserPermissionChange{
|
||||
UserPermissionChange: &aclrecordproto.AclUserPermissionChange{
|
||||
Identity: []byte(t.keychain.GetIdentity(permissionChange.Identity)),
|
||||
Permissions: t.convertPermission(permissionChange.Permission),
|
||||
},
|
||||
},
|
||||
}
|
||||
case ch.UserRemove != nil:
|
||||
remove := ch.UserRemove
|
||||
|
||||
newReadKey := t.keychain.GetKey(remove.NewReadKey).(*SymKey)
|
||||
|
||||
var replaces []*aclrecordproto.AclReadKeyReplace
|
||||
for _, id := range remove.IdentitiesLeft {
|
||||
encKey := t.keychain.EncryptionKeysByYAMLName[id]
|
||||
rawEncKey, _ := encKey.GetPublic().Raw()
|
||||
encReadKey, err := encKey.GetPublic().Encrypt(newReadKey.Key.Bytes())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
replaces = append(replaces, &aclrecordproto.AclReadKeyReplace{
|
||||
Identity: []byte(t.keychain.GetIdentity(id)),
|
||||
EncryptionKey: rawEncKey,
|
||||
EncryptedReadKey: encReadKey,
|
||||
})
|
||||
}
|
||||
|
||||
convCh = &aclrecordproto.AclContentValue{
|
||||
Value: &aclrecordproto.AclContentValue_UserRemove{
|
||||
UserRemove: &aclrecordproto.AclUserRemove{
|
||||
Identity: []byte(t.keychain.GetIdentity(remove.RemovedIdentity)),
|
||||
ReadKeyReplaces: replaces,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if convCh == nil {
|
||||
panic("cannot have empty acl change")
|
||||
}
|
||||
|
||||
return convCh
|
||||
}
|
||||
|
||||
func (t *AclListStorageBuilder) encryptReadKeysWithPubKey(keys []string, encKey encryptionkey.PrivKey) (enc [][]byte) {
|
||||
for _, k := range keys {
|
||||
realKey := t.keychain.GetKey(k).(*SymKey).Key.Bytes()
|
||||
res, err := encKey.GetPublic().Encrypt(realKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
enc = append(enc, res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *AclListStorageBuilder) encryptReadKeysWithSymKey(keys []string, key *symmetric.Key) (enc [][]byte) {
|
||||
for _, k := range keys {
|
||||
realKey := t.keychain.GetKey(k).(*SymKey).Key.Bytes()
|
||||
res, err := key.Encrypt(realKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
enc = append(enc, res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *AclListStorageBuilder) convertPermission(perm string) aclrecordproto.AclUserPermissions {
|
||||
switch perm {
|
||||
case "admin":
|
||||
return aclrecordproto.AclUserPermissions_Admin
|
||||
case "writer":
|
||||
return aclrecordproto.AclUserPermissions_Writer
|
||||
case "reader":
|
||||
return aclrecordproto.AclUserPermissions_Reader
|
||||
default:
|
||||
panic(fmt.Sprintf("incorrect permission: %s", perm))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *AclListStorageBuilder) traverseFromHead(f func(rec *aclrecordproto.AclRecord, id string) error) (err error) {
|
||||
panic("this was removed, add if needed")
|
||||
}
|
||||
|
||||
func (t *AclListStorageBuilder) parseRoot(root *Root) (rawRoot *aclrecordproto.RawAclRecordWithId) {
|
||||
rawSignKey, _ := t.keychain.SigningKeysByYAMLName[root.Identity].GetPublic().Raw()
|
||||
rawEncKey, _ := t.keychain.EncryptionKeysByYAMLName[root.Identity].GetPublic().Raw()
|
||||
readKey := t.keychain.ReadKeysByYAMLName[root.Identity]
|
||||
aclRoot := &aclrecordproto.AclRoot{
|
||||
Identity: rawSignKey,
|
||||
EncryptionKey: rawEncKey,
|
||||
SpaceId: root.SpaceId,
|
||||
EncryptedReadKey: nil,
|
||||
DerivationScheme: "scheme",
|
||||
CurrentReadKeyHash: readKey.Hash,
|
||||
}
|
||||
return t.createRaw(aclRoot, rawSignKey)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build ((!linux && !darwin) || android || ios || nographviz) && !amd64
|
||||
// +build !linux,!darwin android ios nographviz
|
||||
// +build !amd64
|
||||
|
||||
package acllistbuilder
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (t *AclListStorageBuilder) Graph() (string, error) {
|
||||
return "", fmt.Errorf("building graphs is not supported")
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//go:build (linux || darwin) && !android && !ios && !nographviz && (amd64 || arm64)
|
||||
// +build linux darwin
|
||||
// +build !android
|
||||
// +build !ios
|
||||
// +build !nographviz
|
||||
// +build amd64 arm64
|
||||
|
||||
package acllistbuilder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/awalterschulze/gographviz"
|
||||
)
|
||||
|
||||
// To quickly look at visualized string you can use https://dreampuf.github.io/GraphvizOnline
|
||||
|
||||
type EdgeParameters struct {
|
||||
style string
|
||||
color string
|
||||
label string
|
||||
}
|
||||
|
||||
func (t *AclListStorageBuilder) Graph() (string, error) {
|
||||
// TODO: check updates on https://github.com/goccy/go-graphviz/issues/52 or make a fix yourself to use better library here
|
||||
graph := gographviz.NewGraph()
|
||||
graph.SetName("G")
|
||||
graph.SetDir(true)
|
||||
var nodes = make(map[string]struct{})
|
||||
|
||||
var addNodes = func(r *aclrecordproto.AclRecord, id string) error {
|
||||
style := "solid"
|
||||
|
||||
var chSymbs []string
|
||||
aclData := &aclrecordproto.AclData{}
|
||||
err := proto.Unmarshal(r.GetData(), aclData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, chc := range aclData.AclContent {
|
||||
tp := fmt.Sprintf("%T", chc.Value)
|
||||
tp = strings.Replace(tp, "AclChangeAclContentValueValueOf", "", 1)
|
||||
res := ""
|
||||
for _, ts := range tp {
|
||||
if unicode.IsUpper(ts) {
|
||||
res += string(ts)
|
||||
}
|
||||
}
|
||||
chSymbs = append(chSymbs, res)
|
||||
}
|
||||
|
||||
shortId := id
|
||||
label := fmt.Sprintf("Id: %s\nChanges: %s\n",
|
||||
shortId,
|
||||
strings.Join(chSymbs, ","),
|
||||
)
|
||||
e := graph.AddNode("G", "\""+id+"\"", map[string]string{
|
||||
"label": "\"" + label + "\"",
|
||||
"style": "\"" + style + "\"",
|
||||
})
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
nodes[id] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var createEdge = func(firstId, secondId string, params EdgeParameters) error {
|
||||
_, exists := nodes[firstId]
|
||||
if !exists {
|
||||
return fmt.Errorf("no such node")
|
||||
}
|
||||
_, exists = nodes[secondId]
|
||||
if !exists {
|
||||
return fmt.Errorf("no previous node")
|
||||
}
|
||||
|
||||
err := graph.AddEdge("\""+firstId+"\"", "\""+secondId+"\"", true, map[string]string{
|
||||
"color": params.color,
|
||||
"style": params.style,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var addLinks = func(r *aclrecordproto.AclRecord, id string) error {
|
||||
if r.PrevId == "" {
|
||||
return nil
|
||||
}
|
||||
err := createEdge(id, r.PrevId, EdgeParameters{
|
||||
style: "dashed",
|
||||
color: "red",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := t.traverseFromHead(addNodes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = t.traverseFromHead(addLinks)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return graph.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package acllistbuilder
|
||||
|
||||
type Key struct {
|
||||
Name string `yaml:"name"`
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
|
||||
type Keys struct {
|
||||
Derived string `yaml:"Derived"`
|
||||
Enc []*Key `yaml:"Enc"`
|
||||
Sign []*Key `yaml:"Sign"`
|
||||
Read []*Key `yaml:"Read"`
|
||||
}
|
||||
|
||||
type AclChange struct {
|
||||
UserAdd *struct {
|
||||
Identity string `yaml:"identity"`
|
||||
EncryptionKey string `yaml:"encryptionKey"`
|
||||
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
|
||||
Permission string `yaml:"permission"`
|
||||
} `yaml:"userAdd"`
|
||||
|
||||
UserJoin *struct {
|
||||
Identity string `yaml:"identity"`
|
||||
EncryptionKey string `yaml:"encryptionKey"`
|
||||
AcceptKey string `yaml:"acceptKey"`
|
||||
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
|
||||
} `yaml:"userJoin"`
|
||||
|
||||
UserInvite *struct {
|
||||
AcceptKey string `yaml:"acceptKey"`
|
||||
EncryptionKey string `yaml:"encryptionKey"`
|
||||
EncryptedReadKeys []string `yaml:"encryptedReadKeys"`
|
||||
Permissions string `yaml:"permissions"`
|
||||
} `yaml:"userInvite"`
|
||||
|
||||
UserRemove *struct {
|
||||
RemovedIdentity string `yaml:"removedIdentity"`
|
||||
NewReadKey string `yaml:"newReadKey"`
|
||||
IdentitiesLeft []string `yaml:"identitiesLeft"`
|
||||
} `yaml:"userRemove"`
|
||||
|
||||
UserPermissionChange *struct {
|
||||
Identity string `yaml:"identity"`
|
||||
Permission string `yaml:"permission"`
|
||||
}
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
Identity string `yaml:"identity"`
|
||||
AclChanges []*AclChange `yaml:"aclChanges"`
|
||||
ReadKey string `yaml:"readKey"`
|
||||
}
|
||||
|
||||
type Header struct {
|
||||
FirstChangeId string `yaml:"firstChangeId"`
|
||||
IsWorkspace bool `yaml:"isWorkspace"`
|
||||
}
|
||||
|
||||
type Root struct {
|
||||
Identity string `yaml:"identity"`
|
||||
SpaceId string `yaml:"spaceId"`
|
||||
}
|
||||
|
||||
type YMLList struct {
|
||||
Root *Root
|
||||
Records []*Record `yaml:"records"`
|
||||
|
||||
Keys Keys `yaml:"keys"`
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package yamltests
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
_, b, _, _ = runtime.Caller(0)
|
||||
basepath = filepath.Dir(b)
|
||||
)
|
||||
|
||||
func Path() string {
|
||||
return basepath
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
root:
|
||||
identity: A
|
||||
spaceId: space
|
||||
records:
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userInvite:
|
||||
acceptKey: key.Sign.Onetime1
|
||||
encryptionKey: key.Read.EncKey
|
||||
encryptedReadKeys: [key.Read.A]
|
||||
permissions: writer
|
||||
- userAdd:
|
||||
identity: C
|
||||
permission: reader
|
||||
encryptionKey: key.Enc.C
|
||||
encryptedReadKeys: [key.Read.A]
|
||||
readKey: key.Read.A
|
||||
- identity: B
|
||||
aclChanges:
|
||||
- userJoin:
|
||||
identity: B
|
||||
encryptionKey: key.Enc.B
|
||||
acceptKey: key.Sign.Onetime1
|
||||
encryptedReadKeys: [key.Read.A]
|
||||
readKey: key.Read.A
|
||||
keys:
|
||||
Enc:
|
||||
- name: A
|
||||
value: generated
|
||||
- name: B
|
||||
value: generated
|
||||
- name: C
|
||||
value: generated
|
||||
- name: D
|
||||
value: generated
|
||||
- name: Onetime1
|
||||
value: generated
|
||||
Sign:
|
||||
- name: A
|
||||
value: generated
|
||||
- name: B
|
||||
value: generated
|
||||
- name: C
|
||||
value: generated
|
||||
- name: D
|
||||
value: generated
|
||||
- name: Onetime1
|
||||
value: generated
|
||||
Read:
|
||||
- name: A
|
||||
value: derived
|
||||
- name: EncKey
|
||||
value: generated
|
||||
@@ -0,0 +1,58 @@
|
||||
root:
|
||||
identity: A
|
||||
spaceId: space
|
||||
records:
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userInvite:
|
||||
acceptKey: key.Sign.Onetime1
|
||||
encryptionKey: key.Read.EncKey
|
||||
encryptedReadKeys: [key.Read.A]
|
||||
permissions: writer
|
||||
- userAdd:
|
||||
identity: C
|
||||
permission: reader
|
||||
encryptionKey: key.Enc.C
|
||||
encryptedReadKeys: [key.Read.A]
|
||||
readKey: key.Read.A
|
||||
- identity: B
|
||||
aclChanges:
|
||||
- userJoin:
|
||||
identity: B
|
||||
encryptionKey: key.Enc.B
|
||||
acceptKey: key.Sign.Onetime1
|
||||
encryptedReadKeys: [key.Read.A]
|
||||
readKey: key.Read.A
|
||||
- identity: A
|
||||
aclChanges:
|
||||
- userRemove:
|
||||
removedIdentity: B
|
||||
newReadKey: key.Read.2
|
||||
identitiesLeft: [A, C]
|
||||
readKey: key.Read.2
|
||||
keys:
|
||||
Enc:
|
||||
- name: A
|
||||
value: generated
|
||||
- name: B
|
||||
value: generated
|
||||
- name: C
|
||||
value: generated
|
||||
- name: Onetime1
|
||||
value: generated
|
||||
Sign:
|
||||
- name: A
|
||||
value: generated
|
||||
- name: B
|
||||
value: generated
|
||||
- name: C
|
||||
value: generated
|
||||
- name: Onetime1
|
||||
value: generated
|
||||
Read:
|
||||
- name: A
|
||||
value: derived
|
||||
- name: 2
|
||||
value: generated
|
||||
- name: EncKey
|
||||
value: generated
|
||||
@@ -0,0 +1,28 @@
|
||||
package keychain
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
type Keychain struct {
|
||||
keys map[string]signingkey.PubKey
|
||||
}
|
||||
|
||||
func NewKeychain() *Keychain {
|
||||
return &Keychain{
|
||||
keys: make(map[string]signingkey.PubKey),
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Keychain) GetOrAdd(identity string) (signingkey.PubKey, error) {
|
||||
if key, exists := k.keys[identity]; exists {
|
||||
return key, nil
|
||||
}
|
||||
res, err := signingkey.NewSigningEd25519PubKeyFromBytes([]byte(identity))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k.keys[identity] = res.(signingkey.PubKey)
|
||||
return res.(signingkey.PubKey), nil
|
||||
}
|
||||
@@ -2,11 +2,10 @@ package syncobjectgetter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/anyproto/any-sync/commonspace/objectsync/synchandler"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync/synchandler"
|
||||
)
|
||||
|
||||
type SyncObject interface {
|
||||
Id() string
|
||||
synchandler.SyncHandler
|
||||
}
|
||||
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package exporter
|
||||
|
||||
import (
|
||||
"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 {
|
||||
Unmarshall(decrypted []byte) (any, error)
|
||||
Marshall(model any) ([]byte, error)
|
||||
}
|
||||
|
||||
type TreeExporterParams struct {
|
||||
ListStorageExporter liststorage.Exporter
|
||||
TreeStorageExporter treestorage.Exporter
|
||||
DataConverter DataConverter
|
||||
}
|
||||
|
||||
type TreeExporter interface {
|
||||
ExportUnencrypted(tree objecttree.ReadableObjectTree) (err error)
|
||||
}
|
||||
|
||||
type treeExporter struct {
|
||||
listExporter liststorage.Exporter
|
||||
treeExporter treestorage.Exporter
|
||||
converter DataConverter
|
||||
}
|
||||
|
||||
func NewTreeExporter(params TreeExporterParams) TreeExporter {
|
||||
return &treeExporter{
|
||||
listExporter: params.ListStorageExporter,
|
||||
treeExporter: params.TreeStorageExporter,
|
||||
converter: params.DataConverter,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *treeExporter) ExportUnencrypted(tree objecttree.ReadableObjectTree) (err error) {
|
||||
lst := tree.AclList()
|
||||
// this exports root which should be enough before we implement acls
|
||||
_, err = t.listExporter.ListStorage(lst.Root())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
treeStorage, err := t.treeExporter.TreeStorage(tree.Header())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
changeBuilder := objecttree.NewChangeBuilder(crypto.NewKeyStorage(), tree.Header())
|
||||
putStorage := func(change *objecttree.Change) (err error) {
|
||||
var raw *treechangeproto.RawTreeChangeWithId
|
||||
raw, err = changeBuilder.Marshall(change)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return treeStorage.AddRawChange(raw)
|
||||
}
|
||||
err = tree.IterateRoot(t.converter.Unmarshall, func(change *objecttree.Change) bool {
|
||||
if change.Id == tree.Id() {
|
||||
err = putStorage(change)
|
||||
return err == nil
|
||||
}
|
||||
var data []byte
|
||||
data, err = t.converter.Marshall(change.Model)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// that means that change is unencrypted
|
||||
change.ReadKeyId = ""
|
||||
change.Data = data
|
||||
err = putStorage(change)
|
||||
return err == nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return treeStorage.SetHeads(tree.Heads())
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package exporter
|
||||
|
||||
import (
|
||||
"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 {
|
||||
ListStorage liststorage.ListStorage
|
||||
TreeStorage treestorage.TreeStorage
|
||||
BeforeId string
|
||||
IncludeBeforeId bool
|
||||
}
|
||||
|
||||
func ImportHistoryTree(params TreeImportParams) (tree objecttree.ReadableObjectTree, err error) {
|
||||
aclList, err := list.BuildAclList(params.ListStorage)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return objecttree.BuildNonVerifiableHistoryTree(objecttree.HistoryTreeParams{
|
||||
TreeStorage: params.TreeStorage,
|
||||
AclList: aclList,
|
||||
BeforeId: params.BeforeId,
|
||||
IncludeBeforeId: params.IncludeBeforeId,
|
||||
})
|
||||
}
|
||||
@@ -2,9 +2,7 @@ package objecttree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -19,51 +17,44 @@ type Change struct {
|
||||
AclHeadId string
|
||||
Id string
|
||||
SnapshotId string
|
||||
IsSnapshot bool
|
||||
Timestamp int64
|
||||
ReadKeyId string
|
||||
Identity crypto.PubKey
|
||||
ReadKeyHash uint64
|
||||
Identity string
|
||||
Data []byte
|
||||
Model interface{}
|
||||
Signature []byte
|
||||
|
||||
// iterator helpers
|
||||
visited bool
|
||||
branchesFinished bool
|
||||
IsSnapshot bool
|
||||
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
func NewChange(id string, identity crypto.PubKey, ch *treechangeproto.TreeChange, signature []byte) *Change {
|
||||
func NewChange(id string, ch *treechangeproto.TreeChange, signature []byte) *Change {
|
||||
return &Change{
|
||||
Next: nil,
|
||||
PreviousIds: ch.TreeHeadIds,
|
||||
AclHeadId: ch.AclHeadId,
|
||||
Timestamp: ch.Timestamp,
|
||||
ReadKeyId: ch.ReadKeyId,
|
||||
ReadKeyHash: ch.CurrentReadKeyHash,
|
||||
Id: id,
|
||||
Data: ch.ChangesData,
|
||||
SnapshotId: ch.SnapshotBaseId,
|
||||
IsSnapshot: ch.IsSnapshot,
|
||||
Identity: identity,
|
||||
Identity: string(ch.Identity),
|
||||
Signature: signature,
|
||||
}
|
||||
}
|
||||
|
||||
func NewChangeFromRoot(id string, identity crypto.PubKey, ch *treechangeproto.RootChange, signature []byte) *Change {
|
||||
changeInfo := &treechangeproto.TreeChangeInfo{
|
||||
ChangeType: ch.ChangeType,
|
||||
ChangePayload: ch.ChangePayload,
|
||||
}
|
||||
data, _ := proto.Marshal(changeInfo)
|
||||
func NewChangeFromRoot(id string, ch *treechangeproto.RootChange, signature []byte) *Change {
|
||||
return &Change{
|
||||
Next: nil,
|
||||
AclHeadId: ch.AclHeadId,
|
||||
Id: id,
|
||||
IsSnapshot: true,
|
||||
Timestamp: ch.Timestamp,
|
||||
Identity: identity,
|
||||
Identity: string(ch.Identity),
|
||||
Signature: signature,
|
||||
Data: data,
|
||||
Model: changeInfo,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,76 +2,57 @@ package objecttree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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/anytypeio/any-sync/commonspace/object/keychain"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/util/cidutil"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/any-sync/util/keys/symmetric"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrEmptyChange = errors.New("change payload should not be empty")
|
||||
|
||||
type BuilderContent struct {
|
||||
TreeHeadIds []string
|
||||
AclHeadId string
|
||||
SnapshotBaseId string
|
||||
ReadKeyId string
|
||||
IsSnapshot bool
|
||||
PrivKey crypto.PrivKey
|
||||
ReadKey crypto.SymKey
|
||||
Content []byte
|
||||
Timestamp int64
|
||||
TreeHeadIds []string
|
||||
AclHeadId string
|
||||
SnapshotBaseId string
|
||||
CurrentReadKeyHash uint64
|
||||
Identity []byte
|
||||
IsSnapshot bool
|
||||
SigningKey signingkey.PrivKey
|
||||
ReadKey *symmetric.Key
|
||||
Content []byte
|
||||
}
|
||||
|
||||
type InitialContent struct {
|
||||
AclHeadId string
|
||||
PrivKey crypto.PrivKey
|
||||
SpaceId string
|
||||
Seed []byte
|
||||
ChangeType string
|
||||
ChangePayload []byte
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
type nonVerifiableChangeBuilder struct {
|
||||
ChangeBuilder
|
||||
}
|
||||
|
||||
func (c *nonVerifiableChangeBuilder) BuildRoot(payload InitialContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
return c.ChangeBuilder.BuildRoot(payload)
|
||||
}
|
||||
|
||||
func (c *nonVerifiableChangeBuilder) Unmarshall(rawChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) {
|
||||
return c.ChangeBuilder.Unmarshall(rawChange, false)
|
||||
}
|
||||
|
||||
func (c *nonVerifiableChangeBuilder) Build(payload BuilderContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
return c.ChangeBuilder.Build(payload)
|
||||
}
|
||||
|
||||
func (c *nonVerifiableChangeBuilder) Marshall(ch *Change) (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
return c.ChangeBuilder.Marshall(ch)
|
||||
AclHeadId string
|
||||
Identity []byte
|
||||
SigningKey signingkey.PrivKey
|
||||
SpaceId string
|
||||
Seed []byte
|
||||
ChangeType string
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
type ChangeBuilder interface {
|
||||
Unmarshall(rawIdChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error)
|
||||
Build(payload BuilderContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error)
|
||||
BuildRoot(payload InitialContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error)
|
||||
Marshall(ch *Change) (*treechangeproto.RawTreeChangeWithId, error)
|
||||
ConvertFromRaw(rawIdChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error)
|
||||
BuildContent(payload BuilderContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error)
|
||||
BuildInitialContent(payload InitialContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error)
|
||||
BuildRaw(ch *Change) (*treechangeproto.RawTreeChangeWithId, error)
|
||||
SetRootRawChange(rawIdChange *treechangeproto.RawTreeChangeWithId)
|
||||
}
|
||||
|
||||
type newChangeFunc = func(id string, identity crypto.PubKey, ch *treechangeproto.TreeChange, signature []byte) *Change
|
||||
|
||||
type changeBuilder struct {
|
||||
rootChange *treechangeproto.RawTreeChangeWithId
|
||||
keys crypto.KeyStorage
|
||||
newChange newChangeFunc
|
||||
keys *keychain.Keychain
|
||||
}
|
||||
|
||||
func NewChangeBuilder(keys crypto.KeyStorage, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder {
|
||||
return &changeBuilder{keys: keys, rootChange: rootChange, newChange: NewChange}
|
||||
func NewChangeBuilder(keys *keychain.Keychain, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder {
|
||||
return &changeBuilder{keys: keys, rootChange: rootChange}
|
||||
}
|
||||
|
||||
func (c *changeBuilder) Unmarshall(rawIdChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) {
|
||||
func (c *changeBuilder) ConvertFromRaw(rawIdChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) {
|
||||
if rawIdChange.GetRawChange() == nil {
|
||||
err = ErrEmptyChange
|
||||
return
|
||||
@@ -96,9 +77,15 @@ func (c *changeBuilder) Unmarshall(rawIdChange *treechangeproto.RawTreeChangeWit
|
||||
}
|
||||
|
||||
if verify {
|
||||
var identityKey signingkey.PubKey
|
||||
identityKey, err = c.keys.GetOrAdd(ch.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// verifying signature
|
||||
var res bool
|
||||
res, err = ch.Identity.Verify(raw.Payload, raw.Signature)
|
||||
res, err = identityKey.Verify(raw.Payload, raw.Signature)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -114,41 +101,43 @@ func (c *changeBuilder) SetRootRawChange(rawIdChange *treechangeproto.RawTreeCha
|
||||
c.rootChange = rawIdChange
|
||||
}
|
||||
|
||||
func (c *changeBuilder) BuildRoot(payload InitialContent) (ch *Change, rawIdChange *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
identity, err := payload.PrivKey.GetPublic().Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
func (c *changeBuilder) BuildInitialContent(payload InitialContent) (ch *Change, rawIdChange *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
change := &treechangeproto.RootChange{
|
||||
AclHeadId: payload.AclHeadId,
|
||||
Timestamp: payload.Timestamp,
|
||||
Identity: identity,
|
||||
ChangeType: payload.ChangeType,
|
||||
ChangePayload: payload.ChangePayload,
|
||||
SpaceId: payload.SpaceId,
|
||||
Seed: payload.Seed,
|
||||
AclHeadId: payload.AclHeadId,
|
||||
Timestamp: payload.Timestamp,
|
||||
Identity: payload.Identity,
|
||||
ChangeType: payload.ChangeType,
|
||||
SpaceId: payload.SpaceId,
|
||||
Seed: payload.Seed,
|
||||
}
|
||||
|
||||
marshalledChange, err := proto.Marshal(change)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signature, err := payload.PrivKey.Sign(marshalledChange)
|
||||
|
||||
signature, err := payload.SigningKey.Sign(marshalledChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raw := &treechangeproto.RawTreeChange{
|
||||
Payload: marshalledChange,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
marshalledRawChange, err := proto.Marshal(raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := cidutil.NewCidFromBytes(marshalledRawChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ch = NewChangeFromRoot(id, payload.PrivKey.GetPublic(), change, signature)
|
||||
|
||||
ch = NewChangeFromRoot(id, change, signature)
|
||||
|
||||
rawIdChange = &treechangeproto.RawTreeChangeWithId{
|
||||
RawChange: marshalledRawChange,
|
||||
Id: id,
|
||||
@@ -156,19 +145,15 @@ func (c *changeBuilder) BuildRoot(payload InitialContent) (ch *Change, rawIdChan
|
||||
return
|
||||
}
|
||||
|
||||
func (c *changeBuilder) Build(payload BuilderContent) (ch *Change, rawIdChange *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
identity, err := payload.PrivKey.GetPublic().Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
func (c *changeBuilder) BuildContent(payload BuilderContent) (ch *Change, rawIdChange *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
change := &treechangeproto.TreeChange{
|
||||
TreeHeadIds: payload.TreeHeadIds,
|
||||
AclHeadId: payload.AclHeadId,
|
||||
SnapshotBaseId: payload.SnapshotBaseId,
|
||||
ReadKeyId: payload.ReadKeyId,
|
||||
Timestamp: payload.Timestamp,
|
||||
Identity: identity,
|
||||
IsSnapshot: payload.IsSnapshot,
|
||||
TreeHeadIds: payload.TreeHeadIds,
|
||||
AclHeadId: payload.AclHeadId,
|
||||
SnapshotBaseId: payload.SnapshotBaseId,
|
||||
CurrentReadKeyHash: payload.CurrentReadKeyHash,
|
||||
Timestamp: int64(time.Now().Nanosecond()),
|
||||
Identity: payload.Identity,
|
||||
IsSnapshot: payload.IsSnapshot,
|
||||
}
|
||||
if payload.ReadKey != nil {
|
||||
var encrypted []byte
|
||||
@@ -180,27 +165,34 @@ func (c *changeBuilder) Build(payload BuilderContent) (ch *Change, rawIdChange *
|
||||
} else {
|
||||
change.ChangesData = payload.Content
|
||||
}
|
||||
|
||||
marshalledChange, err := proto.Marshal(change)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signature, err := payload.PrivKey.Sign(marshalledChange)
|
||||
|
||||
signature, err := payload.SigningKey.Sign(marshalledChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
raw := &treechangeproto.RawTreeChange{
|
||||
Payload: marshalledChange,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
marshalledRawChange, err := proto.Marshal(raw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := cidutil.NewCidFromBytes(marshalledRawChange)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ch = c.newChange(id, payload.PrivKey.GetPublic(), change, signature)
|
||||
|
||||
ch = NewChange(id, change, signature)
|
||||
|
||||
rawIdChange = &treechangeproto.RawTreeChangeWithId{
|
||||
RawChange: marshalledRawChange,
|
||||
Id: id,
|
||||
@@ -208,23 +200,19 @@ func (c *changeBuilder) Build(payload BuilderContent) (ch *Change, rawIdChange *
|
||||
return
|
||||
}
|
||||
|
||||
func (c *changeBuilder) Marshall(ch *Change) (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
if c.isRoot(ch.Id) {
|
||||
func (c *changeBuilder) BuildRaw(ch *Change) (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
if ch.Id == c.rootChange.Id {
|
||||
return c.rootChange, nil
|
||||
}
|
||||
identity, err := ch.Identity.Marshall()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
treeChange := &treechangeproto.TreeChange{
|
||||
TreeHeadIds: ch.PreviousIds,
|
||||
AclHeadId: ch.AclHeadId,
|
||||
SnapshotBaseId: ch.SnapshotId,
|
||||
ChangesData: ch.Data,
|
||||
ReadKeyId: ch.ReadKeyId,
|
||||
Timestamp: ch.Timestamp,
|
||||
Identity: identity,
|
||||
IsSnapshot: ch.IsSnapshot,
|
||||
TreeHeadIds: ch.PreviousIds,
|
||||
AclHeadId: ch.AclHeadId,
|
||||
SnapshotBaseId: ch.SnapshotId,
|
||||
ChangesData: ch.Data,
|
||||
CurrentReadKeyHash: ch.ReadKeyHash,
|
||||
Timestamp: ch.Timestamp,
|
||||
Identity: []byte(ch.Identity),
|
||||
IsSnapshot: ch.IsSnapshot,
|
||||
}
|
||||
var marshalled []byte
|
||||
marshalled, err = treeChange.Marshal()
|
||||
@@ -248,36 +236,22 @@ func (c *changeBuilder) Marshall(ch *Change) (raw *treechangeproto.RawTreeChange
|
||||
}
|
||||
|
||||
func (c *changeBuilder) unmarshallRawChange(raw *treechangeproto.RawTreeChange, id string) (ch *Change, err error) {
|
||||
var key crypto.PubKey
|
||||
if c.isRoot(id) {
|
||||
if c.rootChange.Id == id {
|
||||
unmarshalled := &treechangeproto.RootChange{}
|
||||
err = proto.Unmarshal(raw.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
key, err = c.keys.PubKeyFromProto(unmarshalled.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ch = NewChangeFromRoot(id, key, unmarshalled, raw.Signature)
|
||||
ch = NewChangeFromRoot(id, unmarshalled, raw.Signature)
|
||||
return
|
||||
}
|
||||
|
||||
unmarshalled := &treechangeproto.TreeChange{}
|
||||
err = proto.Unmarshal(raw.Payload, unmarshalled)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
key, err = c.keys.PubKeyFromProto(unmarshalled.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ch = c.newChange(id, key, unmarshalled, raw.Signature)
|
||||
|
||||
ch = NewChange(id, unmarshalled, raw.Signature)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *changeBuilder) isRoot(id string) bool {
|
||||
if c.rootChange != nil {
|
||||
return c.rootChange.Id == id
|
||||
}
|
||||
return false
|
||||
}
|
||||
+5
-40
@@ -1,12 +1,9 @@
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
)
|
||||
|
||||
type ObjectTreeValidator interface {
|
||||
@@ -16,16 +13,6 @@ type ObjectTreeValidator interface {
|
||||
ValidateNewChanges(tree *Tree, aclList list.AclList, newChanges []*Change) error
|
||||
}
|
||||
|
||||
type noOpTreeValidator struct{}
|
||||
|
||||
func (n *noOpTreeValidator) ValidateFullTree(tree *Tree, aclList list.AclList) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *noOpTreeValidator) ValidateNewChanges(tree *Tree, aclList list.AclList, newChanges []*Change) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type objectTreeValidator struct{}
|
||||
|
||||
func newTreeValidator() ObjectTreeValidator {
|
||||
@@ -52,16 +39,16 @@ func (v *objectTreeValidator) ValidateNewChanges(tree *Tree, aclList list.AclLis
|
||||
|
||||
func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c *Change) (err error) {
|
||||
var (
|
||||
perm list.AclUserState
|
||||
perm list.UserPermissionPair
|
||||
state = aclList.AclState()
|
||||
)
|
||||
// checking if the user could write
|
||||
perm, err = state.StateAtRecord(c.AclHeadId, c.Identity)
|
||||
perm, err = state.PermissionsAtRecord(c.AclHeadId, c.Identity)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if perm.Permissions != aclrecordproto.AclUserPermissions_Writer && perm.Permissions != aclrecordproto.AclUserPermissions_Admin {
|
||||
if perm.Permission != aclrecordproto.AclUserPermissions_Writer && perm.Permission != aclrecordproto.AclUserPermissions_Admin {
|
||||
err = list.ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
@@ -88,25 +75,3 @@ func (v *objectTreeValidator) validateChange(tree *Tree, aclList list.AclList, c
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ValidateRawTree(payload treestorage.TreeStorageCreatePayload, aclList list.AclList) (err error) {
|
||||
treeStorage, err := treestorage.NewInMemoryTreeStorage(payload.RootRawChange, []string{payload.RootRawChange.Id}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tree, err := BuildObjectTree(treeStorage, aclList)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res, err := tree.AddRawChanges(context.Background(), RawChangesPayload{
|
||||
NewHeads: payload.Heads,
|
||||
RawChanges: payload.Changes,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !slice.UnsortedEquals(res.Heads, payload.Heads) {
|
||||
return ErrHasInvalidChanges
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var ErrLoadBeforeRoot = errors.New("can't load before root")
|
||||
|
||||
type HistoryTree interface {
|
||||
ReadableObjectTree
|
||||
}
|
||||
|
||||
type historyTree struct {
|
||||
*objectTree
|
||||
}
|
||||
|
||||
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 = h.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if !include {
|
||||
beforeChange, err := h.treeBuilder.loadChange(beforeId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
heads = beforeChange.PreviousIds
|
||||
}
|
||||
|
||||
h.tree, err = h.treeBuilder.build(heads, nil, nil)
|
||||
return
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/objecttree (interfaces: ObjectTree)
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/objecttree (interfaces: ObjectTree)
|
||||
|
||||
// Package mock_objecttree is a generated GoMock package.
|
||||
package mock_objecttree
|
||||
@@ -7,12 +7,10 @@ package mock_objecttree
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
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"
|
||||
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"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
@@ -39,20 +37,6 @@ func (m *MockObjectTree) EXPECT() *MockObjectTreeMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AclList mocks base method.
|
||||
func (m *MockObjectTree) AclList() list.AclList {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AclList")
|
||||
ret0, _ := ret[0].(list.AclList)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AclList indicates an expected call of AclList.
|
||||
func (mr *MockObjectTreeMockRecorder) AclList() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AclList", reflect.TypeOf((*MockObjectTree)(nil).AclList))
|
||||
}
|
||||
|
||||
// AddContent mocks base method.
|
||||
func (m *MockObjectTree) AddContent(arg0 context.Context, arg1 objecttree.SignableChangeContent) (objecttree.AddResult, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -83,20 +67,6 @@ func (mr *MockObjectTreeMockRecorder) AddRawChanges(arg0, arg1 interface{}) *gom
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChanges", reflect.TypeOf((*MockObjectTree)(nil).AddRawChanges), arg0, arg1)
|
||||
}
|
||||
|
||||
// ChangeInfo mocks base method.
|
||||
func (m *MockObjectTree) ChangeInfo() *treechangeproto.TreeChangeInfo {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ChangeInfo")
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeChangeInfo)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ChangeInfo indicates an expected call of ChangeInfo.
|
||||
func (mr *MockObjectTreeMockRecorder) ChangeInfo() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangeInfo", reflect.TypeOf((*MockObjectTree)(nil).ChangeInfo))
|
||||
}
|
||||
|
||||
// ChangesAfterCommonSnapshot mocks base method.
|
||||
func (m *MockObjectTree) ChangesAfterCommonSnapshot(arg0, arg1 []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -126,19 +96,19 @@ func (mr *MockObjectTreeMockRecorder) Close() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockObjectTree)(nil).Close))
|
||||
}
|
||||
|
||||
// Debug mocks base method.
|
||||
func (m *MockObjectTree) Debug(arg0 objecttree.DescriptionParser) (objecttree.DebugInfo, error) {
|
||||
// DebugDump mocks base method.
|
||||
func (m *MockObjectTree) DebugDump(arg0 objecttree.DescriptionParser) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Debug", arg0)
|
||||
ret0, _ := ret[0].(objecttree.DebugInfo)
|
||||
ret := m.ctrl.Call(m, "DebugDump", arg0)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Debug indicates an expected call of Debug.
|
||||
func (mr *MockObjectTreeMockRecorder) Debug(arg0 interface{}) *gomock.Call {
|
||||
// DebugDump indicates an expected call of DebugDump.
|
||||
func (mr *MockObjectTreeMockRecorder) DebugDump(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockObjectTree)(nil).Debug), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DebugDump", reflect.TypeOf((*MockObjectTree)(nil).DebugDump), arg0)
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
@@ -155,21 +125,6 @@ func (mr *MockObjectTreeMockRecorder) Delete() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockObjectTree)(nil).Delete))
|
||||
}
|
||||
|
||||
// GetChange mocks base method.
|
||||
func (m *MockObjectTree) GetChange(arg0 string) (*objecttree.Change, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChange", arg0)
|
||||
ret0, _ := ret[0].(*objecttree.Change)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChange indicates an expected call of GetChange.
|
||||
func (mr *MockObjectTreeMockRecorder) GetChange(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChange", reflect.TypeOf((*MockObjectTree)(nil).GetChange), arg0)
|
||||
}
|
||||
|
||||
// HasChanges mocks base method.
|
||||
func (m *MockObjectTree) HasChanges(arg0 ...string) bool {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -258,20 +213,6 @@ func (mr *MockObjectTreeMockRecorder) IterateRoot(arg0, arg1 interface{}) *gomoc
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateRoot", reflect.TypeOf((*MockObjectTree)(nil).IterateRoot), arg0, arg1)
|
||||
}
|
||||
|
||||
// Len mocks base method.
|
||||
func (m *MockObjectTree) Len() int {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Len")
|
||||
ret0, _ := ret[0].(int)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Len indicates an expected call of Len.
|
||||
func (mr *MockObjectTreeMockRecorder) Len() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Len", reflect.TypeOf((*MockObjectTree)(nil).Len))
|
||||
}
|
||||
|
||||
// Lock mocks base method.
|
||||
func (m *MockObjectTree) Lock() {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -284,21 +225,6 @@ func (mr *MockObjectTreeMockRecorder) Lock() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockObjectTree)(nil).Lock))
|
||||
}
|
||||
|
||||
// PrepareChange mocks base method.
|
||||
func (m *MockObjectTree) PrepareChange(arg0 objecttree.SignableChangeContent) (*treechangeproto.RawTreeChangeWithId, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "PrepareChange", arg0)
|
||||
ret0, _ := ret[0].(*treechangeproto.RawTreeChangeWithId)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// PrepareChange indicates an expected call of PrepareChange.
|
||||
func (mr *MockObjectTreeMockRecorder) PrepareChange(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareChange", reflect.TypeOf((*MockObjectTree)(nil).PrepareChange), arg0)
|
||||
}
|
||||
|
||||
// RLock mocks base method.
|
||||
func (m *MockObjectTree) RLock() {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -365,49 +291,6 @@ func (mr *MockObjectTreeMockRecorder) Storage() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Storage", reflect.TypeOf((*MockObjectTree)(nil).Storage))
|
||||
}
|
||||
|
||||
// TryClose mocks base method.
|
||||
func (m *MockObjectTree) TryClose(arg0 time.Duration) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "TryClose", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// TryClose indicates an expected call of TryClose.
|
||||
func (mr *MockObjectTreeMockRecorder) TryClose(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryClose", reflect.TypeOf((*MockObjectTree)(nil).TryClose), arg0)
|
||||
}
|
||||
|
||||
// TryLock mocks base method.
|
||||
func (m *MockObjectTree) TryLock() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "TryLock")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// TryLock indicates an expected call of TryLock.
|
||||
func (mr *MockObjectTreeMockRecorder) TryLock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryLock", reflect.TypeOf((*MockObjectTree)(nil).TryLock))
|
||||
}
|
||||
|
||||
// TryRLock mocks base method.
|
||||
func (m *MockObjectTree) TryRLock() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "TryRLock")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// TryRLock indicates an expected call of TryRLock.
|
||||
func (mr *MockObjectTreeMockRecorder) TryRLock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryRLock", reflect.TypeOf((*MockObjectTree)(nil).TryRLock))
|
||||
}
|
||||
|
||||
// Unlock mocks base method.
|
||||
func (m *MockObjectTree) Unlock() {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -433,18 +316,3 @@ func (mr *MockObjectTreeMockRecorder) UnmarshalledHeader() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnmarshalledHeader", reflect.TypeOf((*MockObjectTree)(nil).UnmarshalledHeader))
|
||||
}
|
||||
|
||||
// UnpackChange mocks base method.
|
||||
func (m *MockObjectTree) UnpackChange(arg0 *treechangeproto.RawTreeChangeWithId) ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UnpackChange", arg0)
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UnpackChange indicates an expected call of UnpackChange.
|
||||
func (mr *MockObjectTreeMockRecorder) UnpackChange(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnpackChange", reflect.TypeOf((*MockObjectTree)(nil).UnpackChange), arg0)
|
||||
}
|
||||
@@ -1,33 +1,28 @@
|
||||
//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anyproto/any-sync/commonspace/object/tree/objecttree ObjectTree
|
||||
//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anytypeio/any-sync/commonspace/object/tree/objecttree ObjectTree
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
list2 "github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/keychain"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/keys/symmetric"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
sync.Locker
|
||||
RLock()
|
||||
RUnlock()
|
||||
TryRLock() bool
|
||||
TryLock() bool
|
||||
}
|
||||
|
||||
var (
|
||||
ErrHasInvalidChanges = errors.New("the change is invalid")
|
||||
ErrNoCommonSnapshot = errors.New("trees doesn't have a common snapshot")
|
||||
ErrNoChangeInTree = errors.New("no such change in tree")
|
||||
ErrMissingKey = errors.New("missing current read key")
|
||||
)
|
||||
|
||||
type AddResultSummary int
|
||||
@@ -48,29 +43,19 @@ type RawChangesPayload struct {
|
||||
type ChangeIterateFunc = func(change *Change) bool
|
||||
type ChangeConvertFunc = func(decrypted []byte) (any, error)
|
||||
|
||||
type ReadableObjectTree interface {
|
||||
type ObjectTree interface {
|
||||
RWLocker
|
||||
|
||||
Id() string
|
||||
Header() *treechangeproto.RawTreeChangeWithId
|
||||
UnmarshalledHeader() *Change
|
||||
ChangeInfo() *treechangeproto.TreeChangeInfo
|
||||
Heads() []string
|
||||
Root() *Change
|
||||
Len() int
|
||||
|
||||
AclList() list.AclList
|
||||
|
||||
HasChanges(...string) bool
|
||||
GetChange(string) (*Change, error)
|
||||
DebugDump(parser DescriptionParser) (string, error)
|
||||
|
||||
Debug(parser DescriptionParser) (DebugInfo, error)
|
||||
IterateRoot(convert ChangeConvertFunc, iterate ChangeIterateFunc) error
|
||||
IterateFrom(id string, convert ChangeConvertFunc, iterate ChangeIterateFunc) error
|
||||
}
|
||||
|
||||
type ObjectTree interface {
|
||||
ReadableObjectTree
|
||||
|
||||
SnapshotPath() []string
|
||||
ChangesAfterCommonSnapshot(snapshotPath, heads []string) ([]*treechangeproto.RawTreeChangeWithId, error)
|
||||
@@ -80,12 +65,8 @@ type ObjectTree interface {
|
||||
AddContent(ctx context.Context, content SignableChangeContent) (AddResult, error)
|
||||
AddRawChanges(ctx context.Context, changes RawChangesPayload) (AddResult, error)
|
||||
|
||||
UnpackChange(raw *treechangeproto.RawTreeChangeWithId) (data []byte, err error)
|
||||
PrepareChange(content SignableChangeContent) (res *treechangeproto.RawTreeChangeWithId, err error)
|
||||
|
||||
Delete() error
|
||||
Close() error
|
||||
TryClose(objectTTL time.Duration) (bool, error)
|
||||
}
|
||||
|
||||
type objectTree struct {
|
||||
@@ -94,15 +75,14 @@ type objectTree struct {
|
||||
validator ObjectTreeValidator
|
||||
rawChangeLoader *rawChangeLoader
|
||||
treeBuilder *treeBuilder
|
||||
aclList list.AclList
|
||||
aclList list2.AclList
|
||||
|
||||
id string
|
||||
rawRoot *treechangeproto.RawTreeChangeWithId
|
||||
root *Change
|
||||
tree *Tree
|
||||
|
||||
keys map[string]crypto.SymKey
|
||||
currentReadKey crypto.SymKey
|
||||
keys map[uint64]*symmetric.Key
|
||||
|
||||
// buffers
|
||||
difSnapshotBuf []*treechangeproto.RawTreeChangeWithId
|
||||
@@ -115,26 +95,41 @@ type objectTree struct {
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
type objectTreeDeps struct {
|
||||
changeBuilder ChangeBuilder
|
||||
treeBuilder *treeBuilder
|
||||
treeStorage treestorage.TreeStorage
|
||||
validator ObjectTreeValidator
|
||||
rawChangeLoader *rawChangeLoader
|
||||
aclList list2.AclList
|
||||
}
|
||||
|
||||
func defaultObjectTreeDeps(
|
||||
rootChange *treechangeproto.RawTreeChangeWithId,
|
||||
treeStorage treestorage.TreeStorage,
|
||||
aclList list2.AclList) objectTreeDeps {
|
||||
|
||||
keychain := keychain.NewKeychain()
|
||||
changeBuilder := NewChangeBuilder(keychain, rootChange)
|
||||
treeBuilder := newTreeBuilder(treeStorage, changeBuilder)
|
||||
return objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: treeBuilder,
|
||||
treeStorage: treeStorage,
|
||||
validator: newTreeValidator(),
|
||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||
aclList: aclList,
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -148,14 +143,6 @@ func (ot *objectTree) Id() string {
|
||||
return ot.id
|
||||
}
|
||||
|
||||
func (ot *objectTree) Len() int {
|
||||
return ot.tree.Len()
|
||||
}
|
||||
|
||||
func (ot *objectTree) AclList() list.AclList {
|
||||
return ot.aclList
|
||||
}
|
||||
|
||||
func (ot *objectTree) Header() *treechangeproto.RawTreeChangeWithId {
|
||||
return ot.rawRoot
|
||||
}
|
||||
@@ -164,21 +151,10 @@ func (ot *objectTree) UnmarshalledHeader() *Change {
|
||||
return ot.root
|
||||
}
|
||||
|
||||
func (ot *objectTree) ChangeInfo() *treechangeproto.TreeChangeInfo {
|
||||
return ot.root.Model.(*treechangeproto.TreeChangeInfo)
|
||||
}
|
||||
|
||||
func (ot *objectTree) Storage() treestorage.TreeStorage {
|
||||
return ot.treeStorage
|
||||
}
|
||||
|
||||
func (ot *objectTree) GetChange(id string) (*Change, error) {
|
||||
if ch, ok := ot.tree.attached[id]; ok {
|
||||
return ch, nil
|
||||
}
|
||||
return nil, ErrNoChangeInTree
|
||||
}
|
||||
|
||||
func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeContent) (res AddResult, err error) {
|
||||
payload, err := ot.prepareBuilderContent(content)
|
||||
if err != nil {
|
||||
@@ -189,7 +165,7 @@ func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeCont
|
||||
oldHeads := make([]string, 0, len(ot.tree.Heads()))
|
||||
oldHeads = append(oldHeads, ot.tree.Heads()...)
|
||||
|
||||
objChange, rawChange, err := ot.changeBuilder.Build(payload)
|
||||
objChange, rawChange, err := ot.changeBuilder.BuildContent(payload)
|
||||
if content.IsSnapshot {
|
||||
// clearing tree, because we already saved everything in the last snapshot
|
||||
ot.tree = &Tree{}
|
||||
@@ -199,7 +175,7 @@ func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeCont
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ot.treeStorage.AddRawChangesSetHeads([]*treechangeproto.RawTreeChangeWithId{rawChange}, []string{objChange.Id})
|
||||
err = ot.treeStorage.TransactionAdd([]*treechangeproto.RawTreeChangeWithId{rawChange}, []string{objChange.Id})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -220,63 +196,39 @@ func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeCont
|
||||
return
|
||||
}
|
||||
|
||||
func (ot *objectTree) UnpackChange(raw *treechangeproto.RawTreeChangeWithId) (data []byte, err error) {
|
||||
unmarshalled, err := ot.changeBuilder.Unmarshall(raw, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data = unmarshalled.Data
|
||||
return
|
||||
}
|
||||
|
||||
func (ot *objectTree) PrepareChange(content SignableChangeContent) (res *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
payload, err := ot.prepareBuilderContent(content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, res, err = ot.changeBuilder.Build(payload)
|
||||
return
|
||||
}
|
||||
|
||||
func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt BuilderContent, err error) {
|
||||
ot.aclList.RLock()
|
||||
defer ot.aclList.RUnlock()
|
||||
|
||||
var (
|
||||
state = ot.aclList.AclState() // special method for own keys
|
||||
readKey crypto.SymKey
|
||||
pubKey = content.Key.GetPublic()
|
||||
readKeyId string
|
||||
state = ot.aclList.AclState() // special method for own keys
|
||||
readKey *symmetric.Key
|
||||
readKeyHash uint64
|
||||
)
|
||||
canWrite := state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Writer) ||
|
||||
state.HasPermission(pubKey, aclrecordproto.AclUserPermissions_Admin)
|
||||
canWrite := state.HasPermission(content.Identity, aclrecordproto.AclUserPermissions_Writer) ||
|
||||
state.HasPermission(content.Identity, aclrecordproto.AclUserPermissions_Admin)
|
||||
if !canWrite {
|
||||
err = list.ErrInsufficientPermissions
|
||||
err = list2.ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
|
||||
if content.IsEncrypted {
|
||||
readKeyId = state.CurrentReadKeyId()
|
||||
if ot.currentReadKey == nil {
|
||||
err = ErrMissingKey
|
||||
readKeyHash = state.CurrentReadKeyHash()
|
||||
readKey, err = state.CurrentReadKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
readKey = ot.currentReadKey
|
||||
}
|
||||
timestamp := content.Timestamp
|
||||
if timestamp <= 0 {
|
||||
timestamp = time.Now().Unix()
|
||||
}
|
||||
cnt = BuilderContent{
|
||||
TreeHeadIds: ot.tree.Heads(),
|
||||
AclHeadId: ot.aclList.Head().Id,
|
||||
SnapshotBaseId: ot.tree.RootId(),
|
||||
ReadKeyId: readKeyId,
|
||||
IsSnapshot: content.IsSnapshot,
|
||||
PrivKey: content.Key,
|
||||
ReadKey: readKey,
|
||||
Content: content.Data,
|
||||
Timestamp: timestamp,
|
||||
TreeHeadIds: ot.tree.Heads(),
|
||||
AclHeadId: ot.aclList.Head().Id,
|
||||
SnapshotBaseId: ot.tree.RootId(),
|
||||
CurrentReadKeyHash: readKeyHash,
|
||||
Identity: content.Identity,
|
||||
IsSnapshot: content.IsSnapshot,
|
||||
SigningKey: content.Key,
|
||||
ReadKey: readKey,
|
||||
Content: content.Data,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -296,11 +248,7 @@ func (ot *objectTree) AddRawChanges(ctx context.Context, changesPayload RawChang
|
||||
addResult.Mode = Rebuild
|
||||
}
|
||||
|
||||
err = ot.treeStorage.AddRawChangesSetHeads(addResult.Added, addResult.Heads)
|
||||
if err != nil {
|
||||
// rolling back all changes made to inmemory state
|
||||
ot.rebuildFromStorage(nil, nil)
|
||||
}
|
||||
err = ot.treeStorage.TransactionAdd(addResult.Added, addResult.Heads)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -331,7 +279,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
|
||||
if unAttached, exists := ot.tree.unAttached[ch.Id]; exists {
|
||||
change = unAttached
|
||||
} else {
|
||||
change, err = ot.changeBuilder.Unmarshall(ch, true)
|
||||
change, err = ot.changeBuilder.ConvertFromRaw(ch, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -363,7 +311,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
|
||||
}
|
||||
|
||||
// checks if we need to go to database
|
||||
snapshotNotInTree := func(ch *Change) bool {
|
||||
isOldSnapshot := func(ch *Change) bool {
|
||||
if ch.SnapshotId == ot.tree.RootId() {
|
||||
return false
|
||||
}
|
||||
@@ -378,12 +326,26 @@ func (ot *objectTree) addRawChanges(ctx context.Context, changesPayload RawChang
|
||||
|
||||
shouldRebuildFromStorage := false
|
||||
// checking if we have some changes with different snapshot and then rebuilding
|
||||
for _, ch := range ot.newChangesBuf {
|
||||
if snapshotNotInTree(ch) {
|
||||
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
|
||||
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)
|
||||
@@ -468,7 +430,7 @@ func (ot *objectTree) createAddResult(oldHeads []string, mode Mode, treeChangesA
|
||||
// if we got some changes that we need to convert to raw
|
||||
if _, exists := alreadyConverted[ch]; !exists {
|
||||
var raw *treechangeproto.RawTreeChangeWithId
|
||||
raw, err = ot.changeBuilder.Marshall(ch)
|
||||
raw, err = ot.changeBuilder.BuildRaw(ch)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -480,11 +442,6 @@ func (ot *objectTree) createAddResult(oldHeads []string, mode Mode, treeChangesA
|
||||
|
||||
var added []*treechangeproto.RawTreeChangeWithId
|
||||
added, err = getAddedChanges(treeChangesAdded)
|
||||
if !ot.treeBuilder.keepInMemoryData {
|
||||
for _, ch := range treeChangesAdded {
|
||||
ch.Data = nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -508,13 +465,13 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate
|
||||
}
|
||||
decrypt := func(c *Change) (decrypted []byte, err error) {
|
||||
// the change is not encrypted
|
||||
if c.ReadKeyId == "" {
|
||||
if c.ReadKeyHash == 0 {
|
||||
decrypted = c.Data
|
||||
return
|
||||
}
|
||||
readKey, exists := ot.keys[c.ReadKeyId]
|
||||
readKey, exists := ot.keys[c.ReadKeyHash]
|
||||
if !exists {
|
||||
err = list.ErrNoReadKey
|
||||
err = list2.ErrNoReadKey
|
||||
return
|
||||
}
|
||||
|
||||
@@ -551,8 +508,22 @@ 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 _, attachedExists := ot.tree.attached[ch]; !attachedExists {
|
||||
if !hasChange(ch) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -568,10 +539,6 @@ func (ot *objectTree) Root() *Change {
|
||||
return ot.tree.Root()
|
||||
}
|
||||
|
||||
func (ot *objectTree) TryClose(objectTTL time.Duration) (bool, error) {
|
||||
return true, ot.Close()
|
||||
}
|
||||
|
||||
func (ot *objectTree) Close() error {
|
||||
return nil
|
||||
}
|
||||
@@ -619,7 +586,19 @@ func (ot *objectTree) ChangesAfterCommonSnapshot(theirPath, theirHeads []string)
|
||||
}
|
||||
}
|
||||
|
||||
return ot.rawChangeLoader.Load(commonSnapshot, ot.tree, theirHeads)
|
||||
if commonSnapshot == ot.tree.RootId() {
|
||||
return ot.getChangesFromTree(theirHeads)
|
||||
} else {
|
||||
return ot.getChangesFromDB(commonSnapshot, theirHeads)
|
||||
}
|
||||
}
|
||||
|
||||
func (ot *objectTree) getChangesFromTree(theirHeads []string) (rawChanges []*treechangeproto.RawTreeChangeWithId, err error) {
|
||||
return ot.rawChangeLoader.LoadFromTree(ot.tree, theirHeads)
|
||||
}
|
||||
|
||||
func (ot *objectTree) getChangesFromDB(commonSnapshot string, theirHeads []string) (rawChanges []*treechangeproto.RawTreeChangeWithId, err error) {
|
||||
return ot.rawChangeLoader.LoadFromStorage(commonSnapshot, ot.tree.headIds, theirHeads)
|
||||
}
|
||||
|
||||
func (ot *objectTree) snapshotPathIsActual() bool {
|
||||
@@ -631,9 +610,11 @@ func (ot *objectTree) validateTree(newChanges []*Change) error {
|
||||
defer ot.aclList.RUnlock()
|
||||
state := ot.aclList.AclState()
|
||||
|
||||
err := ot.readKeysFromAclState(state)
|
||||
if err != nil {
|
||||
return err
|
||||
// just not to take lock many times, updating the key map from aclList
|
||||
if len(ot.keys) != len(state.UserReadKeys()) {
|
||||
for key, value := range state.UserReadKeys() {
|
||||
ot.keys[key] = value
|
||||
}
|
||||
}
|
||||
if len(newChanges) == 0 {
|
||||
return ot.validator.ValidateFullTree(ot.tree, ot.aclList)
|
||||
@@ -642,26 +623,6 @@ func (ot *objectTree) validateTree(newChanges []*Change) error {
|
||||
return ot.validator.ValidateNewChanges(ot.tree, ot.aclList, newChanges)
|
||||
}
|
||||
|
||||
func (ot *objectTree) readKeysFromAclState(state *list.AclState) (err error) {
|
||||
// just not to take lock many times, updating the key map from aclList
|
||||
if len(ot.keys) == len(state.UserReadKeys()) {
|
||||
return nil
|
||||
}
|
||||
for key, value := range state.UserReadKeys() {
|
||||
treeKey, err := deriveTreeKey(value, ot.id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ot.keys[key] = treeKey
|
||||
}
|
||||
curKey, err := state.CurrentReadKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ot.currentReadKey, err = deriveTreeKey(curKey, ot.id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ot *objectTree) Debug(parser DescriptionParser) (DebugInfo, error) {
|
||||
return objectTreeDebug{}.debugInfo(ot, parser)
|
||||
func (ot *objectTree) DebugDump(parser DescriptionParser) (string, error) {
|
||||
return ot.tree.Graph(parser)
|
||||
}
|
||||
@@ -2,73 +2,133 @@ package objecttree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/testutils/acllistbuilder"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type mockChangeCreator struct{}
|
||||
|
||||
func (c *mockChangeCreator) createRoot(id, aclId string) *treechangeproto.RawTreeChangeWithId {
|
||||
aclChange := &treechangeproto.RootChange{
|
||||
AclHeadId: aclId,
|
||||
}
|
||||
res, _ := aclChange.Marshal()
|
||||
|
||||
raw := &treechangeproto.RawTreeChange{
|
||||
Payload: res,
|
||||
Signature: nil,
|
||||
}
|
||||
rawMarshalled, _ := raw.Marshal()
|
||||
|
||||
return &treechangeproto.RawTreeChangeWithId{
|
||||
RawChange: rawMarshalled,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mockChangeCreator) createRaw(id, aclId, snapshotId string, isSnapshot bool, prevIds ...string) *treechangeproto.RawTreeChangeWithId {
|
||||
aclChange := &treechangeproto.TreeChange{
|
||||
TreeHeadIds: prevIds,
|
||||
AclHeadId: aclId,
|
||||
SnapshotBaseId: snapshotId,
|
||||
ChangesData: nil,
|
||||
IsSnapshot: isSnapshot,
|
||||
}
|
||||
res, _ := aclChange.Marshal()
|
||||
|
||||
raw := &treechangeproto.RawTreeChange{
|
||||
Payload: res,
|
||||
Signature: nil,
|
||||
}
|
||||
rawMarshalled, _ := raw.Marshal()
|
||||
|
||||
return &treechangeproto.RawTreeChangeWithId{
|
||||
RawChange: rawMarshalled,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *mockChangeCreator) createNewTreeStorage(treeId, aclHeadId string) treestorage.TreeStorage {
|
||||
root := c.createRoot(treeId, aclHeadId)
|
||||
treeStorage, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root})
|
||||
return treeStorage
|
||||
}
|
||||
|
||||
type mockChangeBuilder struct {
|
||||
originalBuilder ChangeBuilder
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) BuildInitialContent(payload InitialContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) SetRootRawChange(rawIdChange *treechangeproto.RawTreeChangeWithId) {
|
||||
c.originalBuilder.SetRootRawChange(rawIdChange)
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) ConvertFromRaw(rawChange *treechangeproto.RawTreeChangeWithId, verify bool) (ch *Change, err error) {
|
||||
return c.originalBuilder.ConvertFromRaw(rawChange, false)
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) BuildContent(payload BuilderContent) (ch *Change, raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *mockChangeBuilder) BuildRaw(ch *Change) (raw *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
return c.originalBuilder.BuildRaw(ch)
|
||||
}
|
||||
|
||||
type mockChangeValidator struct{}
|
||||
|
||||
func (m *mockChangeValidator) ValidateNewChanges(tree *Tree, aclList list.AclList, newChanges []*Change) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockChangeValidator) ValidateFullTree(tree *Tree, aclList list.AclList) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type testTreeContext struct {
|
||||
aclList list.AclList
|
||||
treeStorage treestorage.TreeStorage
|
||||
changeCreator *MockChangeCreator
|
||||
changeBuilder *mockChangeBuilder
|
||||
changeCreator *mockChangeCreator
|
||||
objTree ObjectTree
|
||||
}
|
||||
|
||||
func prepareAclList(t *testing.T) (list.AclList, *accountdata.AccountKeys) {
|
||||
randKeys, err := accountdata.NewRandom()
|
||||
require.NoError(t, err)
|
||||
aclList, err := list.NewTestDerivedAcl("spaceId", randKeys)
|
||||
func prepareAclList(t *testing.T) list.AclList {
|
||||
st, err := acllistbuilder.NewListStorageWithTestName("userjoinexample.yml")
|
||||
require.NoError(t, err, "building storage should not result in error")
|
||||
|
||||
aclList, err := list.BuildAclList(st)
|
||||
require.NoError(t, err, "building acl list should be without error")
|
||||
|
||||
return aclList, randKeys
|
||||
}
|
||||
|
||||
func prepareHistoryTreeDeps(aclList list.AclList) (*MockChangeCreator, objectTreeDeps) {
|
||||
changeCreator := NewMockChangeCreator()
|
||||
treeStorage := changeCreator.CreateNewTreeStorage("0", aclList.Head().Id)
|
||||
root, _ := treeStorage.Root()
|
||||
changeBuilder := &nonVerifiableChangeBuilder{
|
||||
ChangeBuilder: NewChangeBuilder(newMockKeyStorage(), root),
|
||||
}
|
||||
deps := objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: newTreeBuilder(true, treeStorage, changeBuilder),
|
||||
treeStorage: treeStorage,
|
||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||
validator: &noOpTreeValidator{},
|
||||
aclList: aclList,
|
||||
}
|
||||
return changeCreator, deps
|
||||
return aclList
|
||||
}
|
||||
|
||||
func prepareTreeContext(t *testing.T, aclList list.AclList) testTreeContext {
|
||||
return prepareContext(t, aclList, BuildTestableTree, nil)
|
||||
}
|
||||
|
||||
func prepareEmptyDataTreeContext(t *testing.T, aclList list.AclList, additionalChanges func(changeCreator *MockChangeCreator) RawChangesPayload) testTreeContext {
|
||||
return prepareContext(t, aclList, BuildEmptyDataTestableTree, additionalChanges)
|
||||
}
|
||||
|
||||
func prepareContext(
|
||||
t *testing.T,
|
||||
aclList list.AclList,
|
||||
objTreeBuilder BuildObjectTreeFunc,
|
||||
additionalChanges func(changeCreator *MockChangeCreator) RawChangesPayload) testTreeContext {
|
||||
changeCreator := NewMockChangeCreator()
|
||||
treeStorage := changeCreator.CreateNewTreeStorage("0", aclList.Head().Id)
|
||||
if additionalChanges != nil {
|
||||
payload := additionalChanges(changeCreator)
|
||||
err := treeStorage.AddRawChangesSetHeads(payload.RawChanges, payload.NewHeads)
|
||||
require.NoError(t, err)
|
||||
changeCreator := &mockChangeCreator{}
|
||||
treeStorage := changeCreator.createNewTreeStorage("0", aclList.Head().Id)
|
||||
root, _ := treeStorage.Root()
|
||||
changeBuilder := &mockChangeBuilder{
|
||||
originalBuilder: NewChangeBuilder(nil, root),
|
||||
}
|
||||
objTree, err := objTreeBuilder(treeStorage, aclList)
|
||||
deps := objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: newTreeBuilder(treeStorage, changeBuilder),
|
||||
treeStorage: treeStorage,
|
||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||
validator: &mockChangeValidator{},
|
||||
aclList: aclList,
|
||||
}
|
||||
|
||||
// check build
|
||||
objTree, err := buildObjectTree(deps)
|
||||
require.NoError(t, err, "building tree should be without error")
|
||||
|
||||
// check tree iterate
|
||||
@@ -78,69 +138,18 @@ func prepareContext(
|
||||
return true
|
||||
})
|
||||
require.NoError(t, err, "iterate should be without error")
|
||||
if additionalChanges == nil {
|
||||
assert.Equal(t, []string{"0"}, iterChangesId)
|
||||
}
|
||||
assert.Equal(t, []string{"0"}, iterChangesId)
|
||||
return testTreeContext{
|
||||
aclList: aclList,
|
||||
treeStorage: treeStorage,
|
||||
changeBuilder: changeBuilder,
|
||||
changeCreator: changeCreator,
|
||||
objTree: objTree,
|
||||
}
|
||||
}
|
||||
|
||||
func TestObjectTree(t *testing.T) {
|
||||
aclList, keys := prepareAclList(t)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("add content", func(t *testing.T) {
|
||||
root, err := CreateObjectTreeRoot(ObjectTreeCreatePayload{
|
||||
PrivKey: keys.SignKey,
|
||||
ChangeType: "changeType",
|
||||
ChangePayload: nil,
|
||||
SpaceId: "spaceId",
|
||||
IsEncrypted: true,
|
||||
}, aclList)
|
||||
require.NoError(t, err)
|
||||
store, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root})
|
||||
oTree, err := BuildObjectTree(store, aclList)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("0 timestamp is changed to current", func(t *testing.T) {
|
||||
start := time.Now()
|
||||
res, err := oTree.AddContent(ctx, SignableChangeContent{
|
||||
Data: []byte("some"),
|
||||
Key: keys.SignKey,
|
||||
IsSnapshot: false,
|
||||
IsEncrypted: true,
|
||||
Timestamp: 0,
|
||||
})
|
||||
end := time.Now()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, oTree.Heads(), 1)
|
||||
require.Equal(t, res.Added[0].Id, oTree.Heads()[0])
|
||||
ch, err := oTree.(*objectTree).changeBuilder.Unmarshall(res.Added[0], true)
|
||||
require.NoError(t, err)
|
||||
require.GreaterOrEqual(t, start.Unix(), ch.Timestamp)
|
||||
require.LessOrEqual(t, end.Unix(), ch.Timestamp)
|
||||
})
|
||||
t.Run("timestamp is set correctly", func(t *testing.T) {
|
||||
someTs := time.Now().Add(time.Hour).Unix()
|
||||
res, err := oTree.AddContent(ctx, SignableChangeContent{
|
||||
Data: []byte("some"),
|
||||
Key: keys.SignKey,
|
||||
IsSnapshot: false,
|
||||
IsEncrypted: true,
|
||||
Timestamp: someTs,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, oTree.Heads(), 1)
|
||||
require.Equal(t, res.Added[0].Id, oTree.Heads()[0])
|
||||
ch, err := oTree.(*objectTree).changeBuilder.Unmarshall(res.Added[0], true)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ch.Timestamp, someTs)
|
||||
})
|
||||
})
|
||||
aclList := prepareAclList(t)
|
||||
|
||||
t.Run("add simple", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
@@ -149,8 +158,8 @@ func TestObjectTree(t *testing.T) {
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
}
|
||||
payload := RawChangesPayload{
|
||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||
@@ -194,7 +203,7 @@ func TestObjectTree(t *testing.T) {
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("0", aclList.Head().Id, "", true, ""),
|
||||
changeCreator.createRaw("0", aclList.Head().Id, "", true, ""),
|
||||
}
|
||||
payload := RawChangesPayload{
|
||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||
@@ -218,33 +227,7 @@ func TestObjectTree(t *testing.T) {
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
}
|
||||
payload := RawChangesPayload{
|
||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||
RawChanges: rawChanges,
|
||||
}
|
||||
res, err := objTree.AddRawChanges(context.Background(), payload)
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
|
||||
// check result
|
||||
assert.Equal(t, []string{"0"}, res.OldHeads)
|
||||
assert.Equal(t, []string{"0"}, res.Heads)
|
||||
assert.Equal(t, 0, len(res.Added))
|
||||
assert.Equal(t, Nothing, res.Mode)
|
||||
|
||||
// check tree heads
|
||||
assert.Equal(t, []string{"0"}, objTree.Heads())
|
||||
})
|
||||
|
||||
t.Run("add not connected changes", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
// this change could in theory replace current snapshot, we should prevent that
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", true, "1"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
}
|
||||
payload := RawChangesPayload{
|
||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||
@@ -270,10 +253,10 @@ func TestObjectTree(t *testing.T) {
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.CreateRaw("4", aclList.Head().Id, "3", false, "3"),
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.createRaw("4", aclList.Head().Id, "3", false, "3"),
|
||||
}
|
||||
payload := RawChangesPayload{
|
||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||
@@ -320,9 +303,9 @@ func TestObjectTree(t *testing.T) {
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
}
|
||||
payload := RawChangesPayload{
|
||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||
@@ -338,195 +321,18 @@ func TestObjectTree(t *testing.T) {
|
||||
assert.Equal(t, true, objTree.(*objectTree).snapshotPathIsActual())
|
||||
})
|
||||
|
||||
t.Run("test empty data tree", func(t *testing.T) {
|
||||
t.Run("empty tree add", func(t *testing.T) {
|
||||
ctx := prepareEmptyDataTreeContext(t, aclList, nil)
|
||||
changeCreator := ctx.changeCreator
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChangesFirst := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRawWithData("1", aclList.Head().Id, "0", false, []byte("1"), "0"),
|
||||
changeCreator.CreateRawWithData("2", aclList.Head().Id, "0", false, []byte("2"), "1"),
|
||||
changeCreator.CreateRawWithData("3", aclList.Head().Id, "0", false, []byte("3"), "2"),
|
||||
}
|
||||
rawChangesSecond := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRawWithData("4", aclList.Head().Id, "0", false, []byte("4"), "2"),
|
||||
changeCreator.CreateRawWithData("5", aclList.Head().Id, "0", false, []byte("5"), "1"),
|
||||
changeCreator.CreateRawWithData("6", aclList.Head().Id, "0", false, []byte("6"), "3", "4", "5"),
|
||||
}
|
||||
|
||||
// making them to be saved in unattached
|
||||
_, err := objTree.AddRawChanges(context.Background(), RawChangesPayload{
|
||||
NewHeads: []string{"6"},
|
||||
RawChanges: rawChangesSecond,
|
||||
})
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
// attaching them
|
||||
res, err := objTree.AddRawChanges(context.Background(), RawChangesPayload{
|
||||
NewHeads: []string{"3"},
|
||||
RawChanges: rawChangesFirst,
|
||||
})
|
||||
|
||||
require.NoError(t, err, "adding changes should be without error")
|
||||
require.Equal(t, "0", objTree.Root().Id)
|
||||
require.Equal(t, []string{"6"}, objTree.Heads())
|
||||
require.Equal(t, 6, len(res.Added))
|
||||
|
||||
// checking that added changes still have data
|
||||
for _, ch := range res.Added {
|
||||
unmarshallRaw := &treechangeproto.RawTreeChange{}
|
||||
proto.Unmarshal(ch.RawChange, unmarshallRaw)
|
||||
treeCh := &treechangeproto.TreeChange{}
|
||||
proto.Unmarshal(unmarshallRaw.Payload, treeCh)
|
||||
require.Equal(t, ch.Id, string(treeCh.ChangesData))
|
||||
}
|
||||
|
||||
// checking that the tree doesn't have data in memory
|
||||
err = objTree.IterateRoot(nil, func(change *Change) bool {
|
||||
if change.Id == "0" {
|
||||
return true
|
||||
}
|
||||
require.Nil(t, change.Data)
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("empty tree load", func(t *testing.T) {
|
||||
ctx := prepareEmptyDataTreeContext(t, aclList, func(changeCreator *MockChangeCreator) RawChangesPayload {
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRawWithData("1", aclList.Head().Id, "0", false, []byte("1"), "0"),
|
||||
changeCreator.CreateRawWithData("2", aclList.Head().Id, "0", false, []byte("2"), "1"),
|
||||
changeCreator.CreateRawWithData("3", aclList.Head().Id, "0", false, []byte("3"), "2"),
|
||||
changeCreator.CreateRawWithData("4", aclList.Head().Id, "0", false, []byte("4"), "2"),
|
||||
changeCreator.CreateRawWithData("5", aclList.Head().Id, "0", false, []byte("5"), "1"),
|
||||
changeCreator.CreateRawWithData("6", aclList.Head().Id, "0", false, []byte("6"), "3", "4", "5"),
|
||||
}
|
||||
return RawChangesPayload{NewHeads: []string{"6"}, RawChanges: rawChanges}
|
||||
})
|
||||
ctx.objTree.IterateRoot(nil, func(change *Change) bool {
|
||||
if change.Id == "0" {
|
||||
return true
|
||||
}
|
||||
require.Nil(t, change.Data)
|
||||
return true
|
||||
})
|
||||
rawChanges, err := ctx.objTree.ChangesAfterCommonSnapshot([]string{"0"}, []string{"6"})
|
||||
require.NoError(t, err)
|
||||
for _, ch := range rawChanges {
|
||||
unmarshallRaw := &treechangeproto.RawTreeChange{}
|
||||
proto.Unmarshal(ch.RawChange, unmarshallRaw)
|
||||
treeCh := &treechangeproto.TreeChange{}
|
||||
proto.Unmarshal(unmarshallRaw.Payload, treeCh)
|
||||
require.Equal(t, ch.Id, string(treeCh.ChangesData))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.CreateRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
}
|
||||
|
||||
payload := RawChangesPayload{
|
||||
@@ -600,13 +406,13 @@ func TestObjectTree(t *testing.T) {
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.CreateRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
// main difference from tree example
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", true, "3", "4", "5"),
|
||||
changeCreator.createRaw("6", aclList.Head().Id, "0", true, "3", "4", "5"),
|
||||
}
|
||||
|
||||
payload := RawChangesPayload{
|
||||
@@ -681,9 +487,9 @@ func TestObjectTree(t *testing.T) {
|
||||
objTree := ctx.objTree
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
}
|
||||
payload := RawChangesPayload{
|
||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||
@@ -695,9 +501,9 @@ func TestObjectTree(t *testing.T) {
|
||||
require.Equal(t, "3", objTree.Root().Id)
|
||||
|
||||
rawChanges = []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
}
|
||||
payload = RawChangesPayload{
|
||||
NewHeads: []string{rawChanges[len(rawChanges)-1].Id},
|
||||
@@ -736,154 +542,4 @@ func TestObjectTree(t *testing.T) {
|
||||
assert.Equal(t, ch, raw, "the changes in the storage should be the same")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test history tree not include", func(t *testing.T) {
|
||||
changeCreator, deps := prepareHistoryTreeDeps(aclList)
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.CreateRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
}
|
||||
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
|
||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||
BeforeId: "6",
|
||||
IncludeBeforeId: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// check tree heads
|
||||
assert.Equal(t, []string{"3", "4", "5"}, 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"}, iterChangesId)
|
||||
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)
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.CreateRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.CreateRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
}
|
||||
deps.treeStorage.AddRawChangesSetHeads(rawChanges, []string{"6"})
|
||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||
BeforeId: "6",
|
||||
IncludeBeforeId: 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 root", func(t *testing.T) {
|
||||
_, deps := prepareHistoryTreeDeps(aclList)
|
||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||
BeforeId: "0",
|
||||
IncludeBeforeId: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// check tree heads
|
||||
assert.Equal(t, []string{"0"}, 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"}, iterChangesId)
|
||||
assert.Equal(t, "0", hTree.Root().Id)
|
||||
})
|
||||
|
||||
t.Run("validate correct tree", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
ctx.objTree.Header(),
|
||||
changeCreator.CreateRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.CreateRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
}
|
||||
defaultObjectTreeDeps = nonVerifiableTreeDeps
|
||||
err := ValidateRawTree(treestorage.TreeStorageCreatePayload{
|
||||
RootRawChange: ctx.objTree.Header(),
|
||||
Heads: []string{"3"},
|
||||
Changes: rawChanges,
|
||||
}, ctx.aclList)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("fail to validate not connected tree", func(t *testing.T) {
|
||||
ctx := prepareTreeContext(t, aclList)
|
||||
changeCreator := ctx.changeCreator
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
ctx.objTree.Header(),
|
||||
changeCreator.CreateRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
}
|
||||
defaultObjectTreeDeps = nonVerifiableTreeDeps
|
||||
err := ValidateRawTree(treestorage.TreeStorageCreatePayload{
|
||||
RootRawChange: ctx.objTree.Header(),
|
||||
Heads: []string{"3"},
|
||||
Changes: rawChanges,
|
||||
}, ctx.aclList)
|
||||
require.Equal(t, ErrHasInvalidChanges, err)
|
||||
})
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package objecttree
|
||||
|
||||
type objectTreeDebug struct {
|
||||
}
|
||||
|
||||
type DebugInfo struct {
|
||||
TreeLen int
|
||||
TreeString string
|
||||
Graphviz string
|
||||
Heads []string
|
||||
SnapshotPath []string
|
||||
}
|
||||
|
||||
func (o objectTreeDebug) debugInfo(ot *objectTree, parser DescriptionParser) (di DebugInfo, err error) {
|
||||
di = DebugInfo{}
|
||||
di.Graphviz, err = ot.tree.Graph(parser)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
di.TreeString = ot.tree.String()
|
||||
di.TreeLen = ot.tree.Len()
|
||||
di.Heads = ot.Heads()
|
||||
di.SnapshotPath = ot.SnapshotPath()
|
||||
return
|
||||
}
|
||||
@@ -1,132 +1,35 @@
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"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"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/keychain"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
|
||||
"github.com/anytypeio/any-sync/util/keys/symmetric"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ObjectTreeCreatePayload struct {
|
||||
PrivKey crypto.PrivKey
|
||||
ChangeType string
|
||||
ChangePayload []byte
|
||||
SpaceId string
|
||||
IsEncrypted bool
|
||||
Seed []byte
|
||||
Timestamp int64
|
||||
SignKey signingkey.PrivKey
|
||||
ChangeType string
|
||||
SpaceId string
|
||||
Identity []byte
|
||||
IsEncrypted bool
|
||||
}
|
||||
|
||||
type HistoryTreeParams struct {
|
||||
TreeStorage treestorage.TreeStorage
|
||||
AclList list.AclList
|
||||
BeforeId string
|
||||
IncludeBeforeId bool
|
||||
BuildFullTree bool
|
||||
}
|
||||
|
||||
type objectTreeDeps struct {
|
||||
changeBuilder ChangeBuilder
|
||||
treeBuilder *treeBuilder
|
||||
treeStorage treestorage.TreeStorage
|
||||
validator ObjectTreeValidator
|
||||
rawChangeLoader *rawChangeLoader
|
||||
aclList list.AclList
|
||||
}
|
||||
|
||||
type BuildObjectTreeFunc = func(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error)
|
||||
|
||||
var defaultObjectTreeDeps = verifiableTreeDeps
|
||||
|
||||
func verifiableTreeDeps(
|
||||
rootChange *treechangeproto.RawTreeChangeWithId,
|
||||
treeStorage treestorage.TreeStorage,
|
||||
aclList list.AclList) objectTreeDeps {
|
||||
changeBuilder := NewChangeBuilder(crypto.NewKeyStorage(), rootChange)
|
||||
treeBuilder := newTreeBuilder(true, treeStorage, changeBuilder)
|
||||
return objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: treeBuilder,
|
||||
treeStorage: treeStorage,
|
||||
validator: newTreeValidator(),
|
||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||
aclList: aclList,
|
||||
}
|
||||
}
|
||||
|
||||
func emptyDataTreeDeps(
|
||||
rootChange *treechangeproto.RawTreeChangeWithId,
|
||||
treeStorage treestorage.TreeStorage,
|
||||
aclList list.AclList) objectTreeDeps {
|
||||
changeBuilder := NewChangeBuilder(crypto.NewKeyStorage(), rootChange)
|
||||
treeBuilder := newTreeBuilder(false, treeStorage, changeBuilder)
|
||||
return objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: treeBuilder,
|
||||
treeStorage: treeStorage,
|
||||
validator: newTreeValidator(),
|
||||
rawChangeLoader: newStorageLoader(treeStorage, changeBuilder),
|
||||
aclList: aclList,
|
||||
}
|
||||
}
|
||||
|
||||
func nonVerifiableTreeDeps(
|
||||
rootChange *treechangeproto.RawTreeChangeWithId,
|
||||
treeStorage treestorage.TreeStorage,
|
||||
aclList list.AclList) objectTreeDeps {
|
||||
changeBuilder := &nonVerifiableChangeBuilder{NewChangeBuilder(newMockKeyStorage(), rootChange)}
|
||||
treeBuilder := newTreeBuilder(true, treeStorage, changeBuilder)
|
||||
return objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: treeBuilder,
|
||||
treeStorage: treeStorage,
|
||||
validator: &noOpTreeValidator{},
|
||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||
aclList: aclList,
|
||||
}
|
||||
}
|
||||
|
||||
func BuildEmptyDataObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
|
||||
rootChange, err := treeStorage.Root()
|
||||
func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
bytes := make([]byte, 32)
|
||||
_, err = rand.Read(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
deps := emptyDataTreeDeps(rootChange, treeStorage, aclList)
|
||||
return buildObjectTree(deps)
|
||||
return createObjectTreeRoot(payload, time.Now().UnixNano(), bytes, aclList)
|
||||
}
|
||||
|
||||
func BuildTestableTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
|
||||
root, _ := treeStorage.Root()
|
||||
changeBuilder := &nonVerifiableChangeBuilder{
|
||||
ChangeBuilder: NewChangeBuilder(newMockKeyStorage(), root),
|
||||
}
|
||||
deps := objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: newTreeBuilder(true, treeStorage, changeBuilder),
|
||||
treeStorage: treeStorage,
|
||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||
validator: &noOpTreeValidator{},
|
||||
aclList: aclList,
|
||||
}
|
||||
|
||||
return buildObjectTree(deps)
|
||||
}
|
||||
|
||||
func BuildEmptyDataTestableTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
|
||||
root, _ := treeStorage.Root()
|
||||
changeBuilder := &nonVerifiableChangeBuilder{
|
||||
ChangeBuilder: NewChangeBuilder(newMockKeyStorage(), root),
|
||||
}
|
||||
deps := objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: newTreeBuilder(false, treeStorage, changeBuilder),
|
||||
treeStorage: treeStorage,
|
||||
rawChangeLoader: newStorageLoader(treeStorage, changeBuilder),
|
||||
validator: &noOpTreeValidator{},
|
||||
aclList: aclList,
|
||||
}
|
||||
|
||||
return buildObjectTree(deps)
|
||||
func DeriveObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
return createObjectTreeRoot(payload, 0, nil, aclList)
|
||||
}
|
||||
|
||||
func BuildObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
|
||||
@@ -138,25 +41,54 @@ func BuildObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList)
|
||||
return buildObjectTree(deps)
|
||||
}
|
||||
|
||||
func BuildNonVerifiableHistoryTree(params HistoryTreeParams) (HistoryTree, error) {
|
||||
rootChange, err := params.TreeStorage.Root()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deps := nonVerifiableTreeDeps(rootChange, params.TreeStorage, params.AclList)
|
||||
return buildHistoryTree(deps, params)
|
||||
func CreateDerivedObjectTree(
|
||||
payload ObjectTreeCreatePayload,
|
||||
aclList list.AclList,
|
||||
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
||||
return createObjectTree(payload, 0, nil, aclList, createStorage)
|
||||
}
|
||||
|
||||
func BuildHistoryTree(params HistoryTreeParams) (HistoryTree, error) {
|
||||
rootChange, err := params.TreeStorage.Root()
|
||||
func CreateObjectTree(
|
||||
payload ObjectTreeCreatePayload,
|
||||
aclList list.AclList,
|
||||
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
||||
bytes := make([]byte, 32)
|
||||
_, err = rand.Read(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
deps := defaultObjectTreeDeps(rootChange, params.TreeStorage, params.AclList)
|
||||
return buildHistoryTree(deps, params)
|
||||
return createObjectTree(payload, time.Now().UnixNano(), bytes, aclList, createStorage)
|
||||
}
|
||||
|
||||
func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
func createObjectTree(
|
||||
payload ObjectTreeCreatePayload,
|
||||
timestamp int64,
|
||||
seed []byte,
|
||||
aclList list.AclList,
|
||||
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
|
||||
raw, err := createObjectTreeRoot(payload, timestamp, seed, aclList)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// create storage
|
||||
st, err := createStorage(treestorage.TreeStorageCreatePayload{
|
||||
RootRawChange: raw,
|
||||
Changes: []*treechangeproto.RawTreeChangeWithId{raw},
|
||||
Heads: []string{raw.Id},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return BuildObjectTree(st, aclList)
|
||||
}
|
||||
|
||||
func createObjectTreeRoot(
|
||||
payload ObjectTreeCreatePayload,
|
||||
timestamp int64,
|
||||
seed []byte,
|
||||
aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
|
||||
aclList.RLock()
|
||||
aclHeadId := aclList.Head().Id
|
||||
aclList.RUnlock()
|
||||
@@ -165,29 +97,29 @@ func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList)
|
||||
return
|
||||
}
|
||||
cnt := InitialContent{
|
||||
AclHeadId: aclHeadId,
|
||||
PrivKey: payload.PrivKey,
|
||||
SpaceId: payload.SpaceId,
|
||||
ChangeType: payload.ChangeType,
|
||||
ChangePayload: payload.ChangePayload,
|
||||
Timestamp: payload.Timestamp,
|
||||
Seed: payload.Seed,
|
||||
AclHeadId: aclHeadId,
|
||||
Identity: payload.Identity,
|
||||
SigningKey: payload.SignKey,
|
||||
SpaceId: payload.SpaceId,
|
||||
ChangeType: payload.ChangeType,
|
||||
Timestamp: timestamp,
|
||||
Seed: seed,
|
||||
}
|
||||
|
||||
_, root, err = NewChangeBuilder(crypto.NewKeyStorage(), nil).BuildRoot(cnt)
|
||||
_, root, err = NewChangeBuilder(keychain.NewKeychain(), nil).BuildInitialContent(cnt)
|
||||
return
|
||||
}
|
||||
|
||||
func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
|
||||
objTree := &objectTree{
|
||||
id: deps.treeStorage.Id(),
|
||||
treeStorage: deps.treeStorage,
|
||||
treeBuilder: deps.treeBuilder,
|
||||
validator: deps.validator,
|
||||
aclList: deps.aclList,
|
||||
changeBuilder: deps.changeBuilder,
|
||||
rawChangeLoader: deps.rawChangeLoader,
|
||||
keys: make(map[string]crypto.SymKey),
|
||||
tree: nil,
|
||||
keys: make(map[uint64]*symmetric.Key),
|
||||
newChangesBuf: make([]*Change, 0, 10),
|
||||
difSnapshotBuf: make([]*treechangeproto.RawTreeChangeWithId, 0, 10),
|
||||
notSeenIdxBuf: make([]int, 0, 10),
|
||||
@@ -199,13 +131,14 @@ func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objTree.id = objTree.treeStorage.Id()
|
||||
objTree.rawRoot, err = objTree.treeStorage.Root()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// verifying root
|
||||
header, err := objTree.changeBuilder.Unmarshall(objTree.rawRoot, true)
|
||||
header, err := objTree.changeBuilder.ConvertFromRaw(objTree.rawRoot, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -213,38 +146,3 @@ func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
|
||||
|
||||
return objTree, nil
|
||||
}
|
||||
|
||||
func buildHistoryTree(deps objectTreeDeps, params HistoryTreeParams) (ht HistoryTree, err error) {
|
||||
objTree := &objectTree{
|
||||
id: deps.treeStorage.Id(),
|
||||
treeStorage: deps.treeStorage,
|
||||
treeBuilder: deps.treeBuilder,
|
||||
validator: deps.validator,
|
||||
aclList: deps.aclList,
|
||||
changeBuilder: deps.changeBuilder,
|
||||
rawChangeLoader: deps.rawChangeLoader,
|
||||
keys: make(map[string]crypto.SymKey),
|
||||
newChangesBuf: make([]*Change, 0, 10),
|
||||
difSnapshotBuf: make([]*treechangeproto.RawTreeChangeWithId, 0, 10),
|
||||
notSeenIdxBuf: make([]int, 0, 10),
|
||||
newSnapshotsBuf: make([]*Change, 0, 10),
|
||||
}
|
||||
|
||||
hTree := &historyTree{objectTree: objTree}
|
||||
err = hTree.rebuildFromStorage(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objTree.id = objTree.treeStorage.Id()
|
||||
objTree.rawRoot, err = objTree.treeStorage.Root()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
header, err := objTree.changeBuilder.Unmarshall(objTree.rawRoot, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objTree.root = header
|
||||
return hTree, nil
|
||||
}
|
||||
@@ -2,16 +2,15 @@ package objecttree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"time"
|
||||
)
|
||||
|
||||
type rawChangeLoader struct {
|
||||
treeStorage treestorage.TreeStorage
|
||||
changeBuilder ChangeBuilder
|
||||
alwaysFromStorage bool
|
||||
treeStorage treestorage.TreeStorage
|
||||
changeBuilder ChangeBuilder
|
||||
|
||||
// buffers
|
||||
idStack []string
|
||||
@@ -24,12 +23,6 @@ type rawCacheEntry struct {
|
||||
position int
|
||||
}
|
||||
|
||||
func newStorageLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
|
||||
loader := newRawChangeLoader(treeStorage, changeBuilder)
|
||||
loader.alwaysFromStorage = true
|
||||
return loader
|
||||
}
|
||||
|
||||
func newRawChangeLoader(treeStorage treestorage.TreeStorage, changeBuilder ChangeBuilder) *rawChangeLoader {
|
||||
return &rawChangeLoader{
|
||||
treeStorage: treeStorage,
|
||||
@@ -37,15 +30,7 @@ func newRawChangeLoader(treeStorage treestorage.TreeStorage, changeBuilder Chang
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rawChangeLoader) Load(commonSnapshot string, t *Tree, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
||||
if commonSnapshot == t.root.Id && !r.alwaysFromStorage {
|
||||
return r.loadFromTree(t, breakpoints)
|
||||
} else {
|
||||
return r.loadFromStorage(commonSnapshot, t.Heads(), breakpoints)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rawChangeLoader) loadFromTree(t *Tree, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
||||
func (r *rawChangeLoader) LoadFromTree(t *Tree, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
||||
var stack []*Change
|
||||
for _, h := range t.headIds {
|
||||
stack = append(stack, t.attached[h])
|
||||
@@ -54,7 +39,7 @@ func (r *rawChangeLoader) loadFromTree(t *Tree, breakpoints []string) ([]*treech
|
||||
convert := func(chs []*Change) (rawChanges []*treechangeproto.RawTreeChangeWithId, err error) {
|
||||
for _, ch := range chs {
|
||||
var raw *treechangeproto.RawTreeChangeWithId
|
||||
raw, err = r.changeBuilder.Marshall(ch)
|
||||
raw, err = r.changeBuilder.BuildRaw(ch)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -113,7 +98,7 @@ func (r *rawChangeLoader) loadFromTree(t *Tree, breakpoints []string) ([]*treech
|
||||
return convert(results)
|
||||
}
|
||||
|
||||
func (r *rawChangeLoader) loadFromStorage(commonSnapshot string, heads, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
||||
func (r *rawChangeLoader) LoadFromStorage(commonSnapshot string, heads, breakpoints []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
||||
// resetting cache
|
||||
r.cache = make(map[string]rawCacheEntry)
|
||||
defer func() {
|
||||
@@ -241,7 +226,7 @@ func (r *rawChangeLoader) loadEntry(id string) (entry rawCacheEntry, err error)
|
||||
return
|
||||
}
|
||||
|
||||
change, err := r.changeBuilder.Unmarshall(rawChange, false)
|
||||
change, err := r.changeBuilder.ConvertFromRaw(rawChange, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
)
|
||||
|
||||
func ValidateRawTree(payload treestorage.TreeStorageCreatePayload, aclList list.AclList) (err error) {
|
||||
treeStorage, err := treestorage.NewInMemoryTreeStorage(payload.RootRawChange, payload.Heads, payload.Changes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = BuildObjectTree(treeStorage, aclList)
|
||||
return
|
||||
}
|
||||
@@ -1,19 +1,13 @@
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/anytypeio/any-sync/util/keys/asymmetric/signingkey"
|
||||
)
|
||||
|
||||
// SignableChangeContent is a payload to be passed when we are creating change
|
||||
type SignableChangeContent struct {
|
||||
// Data is a data provided by the client
|
||||
Data []byte
|
||||
// Key is the key which will be used to sign the change
|
||||
Key crypto.PrivKey
|
||||
// IsSnapshot tells if the change has snapshot of all previous data
|
||||
IsSnapshot bool
|
||||
// IsEncrypted tells if we encrypt the data with the relevant symmetric key
|
||||
Data []byte
|
||||
Key signingkey.PrivKey
|
||||
Identity []byte
|
||||
IsSnapshot bool
|
||||
IsEncrypted bool
|
||||
// Timestamp is a timestamp of change, if it is <= 0, then we use current timestamp
|
||||
Timestamp int64
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
type mockPubKey struct {
|
||||
}
|
||||
|
||||
const mockKeyValue = "mockKey"
|
||||
|
||||
func (m mockPubKey) Equals(key crypto.Key) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (m mockPubKey) Raw() ([]byte, error) {
|
||||
return []byte(mockKeyValue), nil
|
||||
}
|
||||
|
||||
func (m mockPubKey) Encrypt(message []byte) ([]byte, error) {
|
||||
return message, nil
|
||||
}
|
||||
|
||||
func (m mockPubKey) Verify(data []byte, sig []byte) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (m mockPubKey) Marshall() ([]byte, error) {
|
||||
return []byte(mockKeyValue), nil
|
||||
}
|
||||
|
||||
func (m mockPubKey) Storage() []byte {
|
||||
return []byte(mockKeyValue)
|
||||
}
|
||||
|
||||
func (m mockPubKey) Account() string {
|
||||
return mockKeyValue
|
||||
}
|
||||
|
||||
func (m mockPubKey) Network() string {
|
||||
return mockKeyValue
|
||||
}
|
||||
|
||||
func (m mockPubKey) PeerId() string {
|
||||
return mockKeyValue
|
||||
}
|
||||
|
||||
func (m mockPubKey) LibP2P() (libcrypto.PubKey, error) {
|
||||
return nil, fmt.Errorf("can't be converted in libp2p")
|
||||
}
|
||||
|
||||
type mockKeyStorage struct {
|
||||
}
|
||||
|
||||
func newMockKeyStorage() mockKeyStorage {
|
||||
return mockKeyStorage{}
|
||||
}
|
||||
|
||||
func (m mockKeyStorage) PubKeyFromProto(protoBytes []byte) (crypto.PubKey, error) {
|
||||
return mockPubKey{}, nil
|
||||
}
|
||||
|
||||
type MockChangeCreator struct{}
|
||||
|
||||
func NewMockChangeCreator() *MockChangeCreator {
|
||||
return &MockChangeCreator{}
|
||||
}
|
||||
|
||||
func (c *MockChangeCreator) CreateRoot(id, aclId string) *treechangeproto.RawTreeChangeWithId {
|
||||
aclChange := &treechangeproto.RootChange{
|
||||
AclHeadId: aclId,
|
||||
}
|
||||
res, _ := aclChange.Marshal()
|
||||
|
||||
raw := &treechangeproto.RawTreeChange{
|
||||
Payload: res,
|
||||
Signature: nil,
|
||||
}
|
||||
rawMarshalled, _ := raw.Marshal()
|
||||
|
||||
return &treechangeproto.RawTreeChangeWithId{
|
||||
RawChange: rawMarshalled,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MockChangeCreator) CreateRaw(id, aclId, snapshotId string, isSnapshot bool, prevIds ...string) *treechangeproto.RawTreeChangeWithId {
|
||||
return c.CreateRawWithData(id, aclId, snapshotId, isSnapshot, nil, prevIds...)
|
||||
}
|
||||
|
||||
func (c *MockChangeCreator) CreateRawWithData(id, aclId, snapshotId string, isSnapshot bool, data []byte, prevIds ...string) *treechangeproto.RawTreeChangeWithId {
|
||||
aclChange := &treechangeproto.TreeChange{
|
||||
TreeHeadIds: prevIds,
|
||||
AclHeadId: aclId,
|
||||
SnapshotBaseId: snapshotId,
|
||||
ChangesData: data,
|
||||
IsSnapshot: isSnapshot,
|
||||
}
|
||||
res, _ := aclChange.Marshal()
|
||||
|
||||
raw := &treechangeproto.RawTreeChange{
|
||||
Payload: res,
|
||||
Signature: nil,
|
||||
}
|
||||
rawMarshalled, _ := raw.Marshal()
|
||||
|
||||
return &treechangeproto.RawTreeChangeWithId{
|
||||
RawChange: rawMarshalled,
|
||||
Id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MockChangeCreator) CreateNewTreeStorage(treeId, aclHeadId string) treestorage.TreeStorage {
|
||||
root := c.CreateRoot(treeId, aclHeadId)
|
||||
treeStorage, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root})
|
||||
return treeStorage
|
||||
}
|
||||
@@ -125,7 +125,7 @@ func TestTree_Hash(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTree_AddFuzzy(t *testing.T) {
|
||||
rand.Seed(time.Now().Unix())
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
getChanges := func() []*Change {
|
||||
changes := []*Change{
|
||||
newChange("1", "root", "root"),
|
||||
|
||||
@@ -4,16 +4,15 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anyproto/any-sync/util/slice"
|
||||
"github.com/anytypeio/any-sync/app/logger"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/anytypeio/any-sync/util/slice"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
log = logger.NewNamedSugared("common.commonspace.objecttree")
|
||||
log = logger.NewNamed("acltree").Sugar()
|
||||
ErrEmpty = errors.New("logs empty")
|
||||
)
|
||||
|
||||
@@ -21,20 +20,18 @@ type treeBuilder struct {
|
||||
treeStorage treestorage.TreeStorage
|
||||
builder ChangeBuilder
|
||||
|
||||
cache map[string]*Change
|
||||
tree *Tree
|
||||
keepInMemoryData bool
|
||||
cache map[string]*Change
|
||||
tree *Tree
|
||||
|
||||
// buffers
|
||||
idStack []string
|
||||
loadBuffer []*Change
|
||||
}
|
||||
|
||||
func newTreeBuilder(keepData bool, storage treestorage.TreeStorage, builder ChangeBuilder) *treeBuilder {
|
||||
func newTreeBuilder(storage treestorage.TreeStorage, builder ChangeBuilder) *treeBuilder {
|
||||
return &treeBuilder{
|
||||
treeStorage: storage,
|
||||
builder: builder,
|
||||
keepInMemoryData: keepData,
|
||||
treeStorage: storage,
|
||||
builder: builder,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,40 +41,16 @@ func (tb *treeBuilder) Reset() {
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) Build(theirHeads []string, newChanges []*Change) (*Tree, error) {
|
||||
heads, err := tb.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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)
|
||||
}()
|
||||
|
||||
var proposedHeads []string
|
||||
tb.cache = make(map[string]*Change)
|
||||
heads, err := tb.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: we can actually get this from tree (though not sure, that there would always be
|
||||
// an invariant where the tree has the closest common snapshot of heads)
|
||||
// so if optimization is critical we can change this to inject from tree directly,
|
||||
// so if optimization is critical we can change this to inject from tree directly
|
||||
// but then we have to be sure that invariant stays true
|
||||
oldBreakpoint, err := tb.findBreakpoint(heads, true)
|
||||
if err != nil {
|
||||
@@ -108,7 +81,7 @@ func (tb *treeBuilder) build(heads []string, theirHeads []string, newChanges []*
|
||||
}
|
||||
proposedHeads = append(proposedHeads, heads...)
|
||||
|
||||
log.With(zap.Strings("heads", proposedHeads), zap.String("id", tb.treeStorage.Id())).Debug("building tree")
|
||||
log.With(zap.Strings("heads", proposedHeads)).Debug("building tree")
|
||||
if err = tb.buildTree(proposedHeads, breakpoint); err != nil {
|
||||
return nil, fmt.Errorf("buildTree error: %v", err)
|
||||
}
|
||||
@@ -121,8 +94,8 @@ func (tb *treeBuilder) buildTree(heads []string, breakpoint string) (err error)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
changes := tb.dfs(heads, breakpoint)
|
||||
tb.tree.AddFast(ch)
|
||||
changes := tb.dfs(heads, breakpoint)
|
||||
tb.tree.AddFast(changes...)
|
||||
return
|
||||
}
|
||||
@@ -177,13 +150,10 @@ func (tb *treeBuilder) loadChange(id string) (ch *Change, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch, err = tb.builder.Unmarshall(change, true)
|
||||
ch, err = tb.builder.ConvertFromRaw(change, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !tb.keepInMemoryData {
|
||||
ch.Data = nil
|
||||
}
|
||||
|
||||
tb.cache[id] = ch
|
||||
return ch, nil
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//go:build (!linux && !darwin) || android || ios || nographviz || !cgo || windows
|
||||
// +build !linux,!darwin android ios nographviz !cgo windows
|
||||
//go:build ((!linux && !darwin) || android || ios || nographviz) && !amd64
|
||||
// +build !linux,!darwin android ios nographviz
|
||||
// +build !amd64
|
||||
|
||||
package objecttree
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
//go:build (linux || darwin) && !android && !ios && !nographviz && cgo && (amd64 || arm64)
|
||||
//go:build (linux || darwin) && !android && !ios && !nographviz && (amd64 || arm64)
|
||||
// +build linux darwin
|
||||
// +build !android
|
||||
// +build !ios
|
||||
// +build !nographviz
|
||||
// +build cgo
|
||||
// +build amd64 arm64
|
||||
|
||||
package objecttree
|
||||
@@ -11,11 +10,10 @@ package objecttree
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/goccy/go-graphviz"
|
||||
"github.com/goccy/go-graphviz/cgraph"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (t *Tree) Graph(parser DescriptionParser) (data string, err error) {
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
package objecttree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func commonSnapshotForTwoPaths(ourPath []string, theirPath []string) (string, error) {
|
||||
var i int
|
||||
var j int
|
||||
@@ -33,24 +27,3 @@ OuterLoop:
|
||||
}
|
||||
return ourPath[i+1], nil
|
||||
}
|
||||
|
||||
func deriveTreeKey(key crypto.SymKey, cid string) (crypto.SymKey, error) {
|
||||
raw, err := key.Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return crypto.DeriveSymmetricKey(raw, fmt.Sprintf(crypto.AnysyncTreePath, cid))
|
||||
}
|
||||
|
||||
func DoSnapshot(treeLen int) bool {
|
||||
if treeLen <= 100 {
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
delta = treeLen/50 + 1
|
||||
midPoint = 1000
|
||||
val = rand.Intn(midPoint * 2)
|
||||
)
|
||||
return midPoint-delta <= val && val <= midPoint+delta
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/anyproto/any-sync/commonspace/object/tree/synctree (interfaces: SyncTree,ReceiveQueue,HeadNotifiable,SyncClient,RequestFactory,TreeSyncProtocol)
|
||||
// Source: github.com/anytypeio/any-sync/commonspace/object/tree/synctree (interfaces: SyncClient,SyncTree,ReceiveQueue,HeadNotifiable)
|
||||
|
||||
// Package mock_synctree is a generated GoMock package.
|
||||
package mock_synctree
|
||||
@@ -7,17 +7,137 @@ package mock_synctree
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
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"
|
||||
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"
|
||||
spacesyncproto "github.com/anytypeio/any-sync/commonspace/spacesyncproto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// BroadcastAsync mocks base method.
|
||||
func (m *MockSyncClient) BroadcastAsync(arg0 *treechangeproto.TreeSyncMessage) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BroadcastAsync", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// BroadcastAsync indicates an expected call of BroadcastAsync.
|
||||
func (mr *MockSyncClientMockRecorder) BroadcastAsync(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BroadcastAsync", reflect.TypeOf((*MockSyncClient)(nil).BroadcastAsync), arg0)
|
||||
}
|
||||
|
||||
// BroadcastAsyncOrSendResponsible mocks base method.
|
||||
func (m *MockSyncClient) BroadcastAsyncOrSendResponsible(arg0 *treechangeproto.TreeSyncMessage) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BroadcastAsyncOrSendResponsible", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// BroadcastAsyncOrSendResponsible indicates an expected call of BroadcastAsyncOrSendResponsible.
|
||||
func (mr *MockSyncClientMockRecorder) BroadcastAsyncOrSendResponsible(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BroadcastAsyncOrSendResponsible", reflect.TypeOf((*MockSyncClient)(nil).BroadcastAsyncOrSendResponsible), 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))
|
||||
}
|
||||
|
||||
// SendAsync mocks base method.
|
||||
func (m *MockSyncClient) SendAsync(arg0 string, arg1 *treechangeproto.TreeSyncMessage, arg2 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SendAsync", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SendAsync indicates an expected call of SendAsync.
|
||||
func (mr *MockSyncClientMockRecorder) SendAsync(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAsync", reflect.TypeOf((*MockSyncClient)(nil).SendAsync), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// MockSyncTree is a mock of SyncTree interface.
|
||||
type MockSyncTree struct {
|
||||
ctrl *gomock.Controller
|
||||
@@ -41,20 +161,6 @@ func (m *MockSyncTree) EXPECT() *MockSyncTreeMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AclList mocks base method.
|
||||
func (m *MockSyncTree) AclList() list.AclList {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AclList")
|
||||
ret0, _ := ret[0].(list.AclList)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AclList indicates an expected call of AclList.
|
||||
func (mr *MockSyncTreeMockRecorder) AclList() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AclList", reflect.TypeOf((*MockSyncTree)(nil).AclList))
|
||||
}
|
||||
|
||||
// AddContent mocks base method.
|
||||
func (m *MockSyncTree) AddContent(arg0 context.Context, arg1 objecttree.SignableChangeContent) (objecttree.AddResult, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -85,20 +191,6 @@ func (mr *MockSyncTreeMockRecorder) AddRawChanges(arg0, arg1 interface{}) *gomoc
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChanges", reflect.TypeOf((*MockSyncTree)(nil).AddRawChanges), arg0, arg1)
|
||||
}
|
||||
|
||||
// ChangeInfo mocks base method.
|
||||
func (m *MockSyncTree) ChangeInfo() *treechangeproto.TreeChangeInfo {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ChangeInfo")
|
||||
ret0, _ := ret[0].(*treechangeproto.TreeChangeInfo)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ChangeInfo indicates an expected call of ChangeInfo.
|
||||
func (mr *MockSyncTreeMockRecorder) ChangeInfo() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangeInfo", reflect.TypeOf((*MockSyncTree)(nil).ChangeInfo))
|
||||
}
|
||||
|
||||
// ChangesAfterCommonSnapshot mocks base method.
|
||||
func (m *MockSyncTree) ChangesAfterCommonSnapshot(arg0, arg1 []string) ([]*treechangeproto.RawTreeChangeWithId, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -128,19 +220,19 @@ func (mr *MockSyncTreeMockRecorder) Close() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSyncTree)(nil).Close))
|
||||
}
|
||||
|
||||
// Debug mocks base method.
|
||||
func (m *MockSyncTree) Debug(arg0 objecttree.DescriptionParser) (objecttree.DebugInfo, error) {
|
||||
// DebugDump mocks base method.
|
||||
func (m *MockSyncTree) DebugDump(arg0 objecttree.DescriptionParser) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Debug", arg0)
|
||||
ret0, _ := ret[0].(objecttree.DebugInfo)
|
||||
ret := m.ctrl.Call(m, "DebugDump", arg0)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Debug indicates an expected call of Debug.
|
||||
func (mr *MockSyncTreeMockRecorder) Debug(arg0 interface{}) *gomock.Call {
|
||||
// DebugDump indicates an expected call of DebugDump.
|
||||
func (mr *MockSyncTreeMockRecorder) DebugDump(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockSyncTree)(nil).Debug), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DebugDump", reflect.TypeOf((*MockSyncTree)(nil).DebugDump), arg0)
|
||||
}
|
||||
|
||||
// Delete mocks base method.
|
||||
@@ -157,21 +249,6 @@ func (mr *MockSyncTreeMockRecorder) Delete() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockSyncTree)(nil).Delete))
|
||||
}
|
||||
|
||||
// GetChange mocks base method.
|
||||
func (m *MockSyncTree) GetChange(arg0 string) (*objecttree.Change, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChange", arg0)
|
||||
ret0, _ := ret[0].(*objecttree.Change)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChange indicates an expected call of GetChange.
|
||||
func (mr *MockSyncTreeMockRecorder) GetChange(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChange", reflect.TypeOf((*MockSyncTree)(nil).GetChange), arg0)
|
||||
}
|
||||
|
||||
// HandleMessage mocks base method.
|
||||
func (m *MockSyncTree) HandleMessage(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -186,21 +263,6 @@ 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()
|
||||
@@ -289,20 +351,6 @@ func (mr *MockSyncTreeMockRecorder) IterateRoot(arg0, arg1 interface{}) *gomock.
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateRoot", reflect.TypeOf((*MockSyncTree)(nil).IterateRoot), arg0, arg1)
|
||||
}
|
||||
|
||||
// Len mocks base method.
|
||||
func (m *MockSyncTree) Len() int {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Len")
|
||||
ret0, _ := ret[0].(int)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Len indicates an expected call of Len.
|
||||
func (mr *MockSyncTreeMockRecorder) Len() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Len", reflect.TypeOf((*MockSyncTree)(nil).Len))
|
||||
}
|
||||
|
||||
// Lock mocks base method.
|
||||
func (m *MockSyncTree) Lock() {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -315,19 +363,18 @@ func (mr *MockSyncTreeMockRecorder) Lock() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockSyncTree)(nil).Lock))
|
||||
}
|
||||
|
||||
// PrepareChange mocks base method.
|
||||
func (m *MockSyncTree) PrepareChange(arg0 objecttree.SignableChangeContent) (*treechangeproto.RawTreeChangeWithId, error) {
|
||||
// Ping mocks base method.
|
||||
func (m *MockSyncTree) Ping() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "PrepareChange", arg0)
|
||||
ret0, _ := ret[0].(*treechangeproto.RawTreeChangeWithId)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
ret := m.ctrl.Call(m, "Ping")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// PrepareChange indicates an expected call of PrepareChange.
|
||||
func (mr *MockSyncTreeMockRecorder) PrepareChange(arg0 interface{}) *gomock.Call {
|
||||
// Ping indicates an expected call of Ping.
|
||||
func (mr *MockSyncTreeMockRecorder) Ping() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareChange", reflect.TypeOf((*MockSyncTree)(nil).PrepareChange), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockSyncTree)(nil).Ping))
|
||||
}
|
||||
|
||||
// RLock mocks base method.
|
||||
@@ -368,18 +415,6 @@ func (mr *MockSyncTreeMockRecorder) Root() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Root", reflect.TypeOf((*MockSyncTree)(nil).Root))
|
||||
}
|
||||
|
||||
// SetListener mocks base method.
|
||||
func (m *MockSyncTree) SetListener(arg0 updatelistener.UpdateListener) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetListener", arg0)
|
||||
}
|
||||
|
||||
// SetListener indicates an expected call of SetListener.
|
||||
func (mr *MockSyncTreeMockRecorder) SetListener(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetListener", reflect.TypeOf((*MockSyncTree)(nil).SetListener), arg0)
|
||||
}
|
||||
|
||||
// SnapshotPath mocks base method.
|
||||
func (m *MockSyncTree) SnapshotPath() []string {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -408,63 +443,6 @@ func (mr *MockSyncTreeMockRecorder) Storage() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Storage", reflect.TypeOf((*MockSyncTree)(nil).Storage))
|
||||
}
|
||||
|
||||
// SyncWithPeer mocks base method.
|
||||
func (m *MockSyncTree) SyncWithPeer(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SyncWithPeer", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SyncWithPeer indicates an expected call of SyncWithPeer.
|
||||
func (mr *MockSyncTreeMockRecorder) SyncWithPeer(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncWithPeer", reflect.TypeOf((*MockSyncTree)(nil).SyncWithPeer), arg0, arg1)
|
||||
}
|
||||
|
||||
// TryClose mocks base method.
|
||||
func (m *MockSyncTree) TryClose(arg0 time.Duration) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "TryClose", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// TryClose indicates an expected call of TryClose.
|
||||
func (mr *MockSyncTreeMockRecorder) TryClose(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryClose", reflect.TypeOf((*MockSyncTree)(nil).TryClose), arg0)
|
||||
}
|
||||
|
||||
// TryLock mocks base method.
|
||||
func (m *MockSyncTree) TryLock() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "TryLock")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// TryLock indicates an expected call of TryLock.
|
||||
func (mr *MockSyncTreeMockRecorder) TryLock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryLock", reflect.TypeOf((*MockSyncTree)(nil).TryLock))
|
||||
}
|
||||
|
||||
// TryRLock mocks base method.
|
||||
func (m *MockSyncTree) TryRLock() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "TryRLock")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// TryRLock indicates an expected call of TryRLock.
|
||||
func (mr *MockSyncTreeMockRecorder) TryRLock() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryRLock", reflect.TypeOf((*MockSyncTree)(nil).TryRLock))
|
||||
}
|
||||
|
||||
// Unlock mocks base method.
|
||||
func (m *MockSyncTree) Unlock() {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -491,21 +469,6 @@ func (mr *MockSyncTreeMockRecorder) UnmarshalledHeader() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnmarshalledHeader", reflect.TypeOf((*MockSyncTree)(nil).UnmarshalledHeader))
|
||||
}
|
||||
|
||||
// UnpackChange mocks base method.
|
||||
func (m *MockSyncTree) UnpackChange(arg0 *treechangeproto.RawTreeChangeWithId) ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UnpackChange", arg0)
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UnpackChange indicates an expected call of UnpackChange.
|
||||
func (mr *MockSyncTreeMockRecorder) UnpackChange(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnpackChange", reflect.TypeOf((*MockSyncTree)(nil).UnpackChange), arg0)
|
||||
}
|
||||
|
||||
// MockReceiveQueue is a mock of ReceiveQueue interface.
|
||||
type MockReceiveQueue struct {
|
||||
ctrl *gomock.Controller
|
||||
@@ -605,287 +568,3 @@ 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)
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
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))
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package synctree
|
||||
|
||||
import (
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/objectsync"
|
||||
)
|
||||
|
||||
type queuedClient struct {
|
||||
SyncClient
|
||||
queue objectsync.ActionQueue
|
||||
}
|
||||
|
||||
func newQueuedClient(client SyncClient, queue objectsync.ActionQueue) SyncClient {
|
||||
return &queuedClient{
|
||||
SyncClient: client,
|
||||
queue: queue,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *queuedClient) BroadcastAsync(message *treechangeproto.TreeSyncMessage) (err error) {
|
||||
return q.queue.Send(func() error {
|
||||
return q.SyncClient.BroadcastAsync(message)
|
||||
})
|
||||
}
|
||||
|
||||
func (q *queuedClient) SendAsync(peerId string, message *treechangeproto.TreeSyncMessage, replyId string) (err error) {
|
||||
return q.queue.Send(func() error {
|
||||
return q.SyncClient.SendAsync(peerId, message, replyId)
|
||||
})
|
||||
}
|
||||
|
||||
func (q *queuedClient) BroadcastAsyncOrSendResponsible(message *treechangeproto.TreeSyncMessage) (err error) {
|
||||
return q.queue.Send(func() error {
|
||||
return q.SyncClient.BroadcastAsyncOrSendResponsible(message)
|
||||
})
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package synctree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ package synctree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
"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"
|
||||
)
|
||||
|
||||
type RequestFactory interface {
|
||||
@@ -14,8 +14,10 @@ type RequestFactory interface {
|
||||
CreateFullSyncResponse(t objecttree.ObjectTree, theirHeads, theirSnapshotPath []string) (*treechangeproto.TreeSyncMessage, error)
|
||||
}
|
||||
|
||||
func NewRequestFactory() RequestFactory {
|
||||
return &requestFactory{}
|
||||
var sharedFactory = &requestFactory{}
|
||||
|
||||
func GetRequestFactory() RequestFactory {
|
||||
return sharedFactory
|
||||
}
|
||||
|
||||
type requestFactory struct{}
|
||||
|
||||
Loaded 100 of 294 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user