diff --git a/Makefile b/Makefile index ad53dc00..15b67f6d 100644 --- a/Makefile +++ b/Makefile @@ -31,12 +31,15 @@ proto: $(GOGO_START) protoc --gogofaster_out=$(PKGMAP):. --go-drpc_out=protolib=github.com/gogo/protobuf:. common/commonspace/spacesyncproto/protos/*.proto $(GOGO_START) protoc --gogofaster_out=:. --go-drpc_out=protolib=github.com/gogo/protobuf:. consensus/consensusproto/protos/*.proto - - build: @$(eval FLAGS := $$(shell govvv -flags -pkg github.com/anytypeio/go-anytype-infrastructure-experiments/app)) go build -v -o bin/anytype-node -ldflags "$(FLAGS)" cmd/node/node.go +test-deps: + @echo 'Generating test mocks...' + @go install github.com/golang/mock/mockgen + @go generate ./... + build-consensus: @$(eval FLAGS := $$(shell govvv -flags -pkg github.com/anytypeio/go-anytype-infrastructure-experiments/app)) - go build -v -o bin/consensus-node -ldflags "$(FLAGS)" cmd/consensusnode/consensusnode.go \ No newline at end of file + go build -v -o bin/consensus-node -ldflags "$(FLAGS)" cmd/consensusnode/consensusnode.go diff --git a/common/commonspace/cache/mock_cache/mock_cache.go b/common/commonspace/cache/mock_cache/mock_cache.go new file mode 100644 index 00000000..84822cdb --- /dev/null +++ b/common/commonspace/cache/mock_cache/mock_cache.go @@ -0,0 +1,108 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache (interfaces: TreeCache) + +// Package mock_cache is a generated GoMock package. +package mock_cache + +import ( + context "context" + reflect "reflect" + + app "github.com/anytypeio/go-anytype-infrastructure-experiments/app" + cache "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache" + gomock "github.com/golang/mock/gomock" +) + +// MockTreeCache is a mock of TreeCache interface. +type MockTreeCache struct { + ctrl *gomock.Controller + recorder *MockTreeCacheMockRecorder +} + +// MockTreeCacheMockRecorder is the mock recorder for MockTreeCache. +type MockTreeCacheMockRecorder struct { + mock *MockTreeCache +} + +// NewMockTreeCache creates a new mock instance. +func NewMockTreeCache(ctrl *gomock.Controller) *MockTreeCache { + mock := &MockTreeCache{ctrl: ctrl} + mock.recorder = &MockTreeCacheMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTreeCache) EXPECT() *MockTreeCacheMockRecorder { + return m.recorder +} + +// Close mocks base method. +func (m *MockTreeCache) Close(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockTreeCacheMockRecorder) Close(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockTreeCache)(nil).Close), arg0) +} + +// GetTree mocks base method. +func (m *MockTreeCache) GetTree(arg0 context.Context, arg1, arg2 string) (cache.TreeResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTree", arg0, arg1, arg2) + ret0, _ := ret[0].(cache.TreeResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTree indicates an expected call of GetTree. +func (mr *MockTreeCacheMockRecorder) GetTree(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTree", reflect.TypeOf((*MockTreeCache)(nil).GetTree), arg0, arg1, arg2) +} + +// Init mocks base method. +func (m *MockTreeCache) 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 *MockTreeCacheMockRecorder) Init(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockTreeCache)(nil).Init), arg0) +} + +// Name mocks base method. +func (m *MockTreeCache) 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 *MockTreeCacheMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockTreeCache)(nil).Name)) +} + +// Run mocks base method. +func (m *MockTreeCache) Run(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Run", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Run indicates an expected call of Run. +func (mr *MockTreeCacheMockRecorder) Run(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockTreeCache)(nil).Run), arg0) +} diff --git a/common/commonspace/cache/treecache.go b/common/commonspace/cache/treecache.go index 9983231f..274f044a 100644 --- a/common/commonspace/cache/treecache.go +++ b/common/commonspace/cache/treecache.go @@ -1,3 +1,4 @@ +//go:generate mockgen -destination mock_cache/mock_cache.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache TreeCache package cache import ( diff --git a/common/commonspace/diffservice/diffservice.go b/common/commonspace/diffservice/diffservice.go index ff376d31..84b0d637 100644 --- a/common/commonspace/diffservice/diffservice.go +++ b/common/commonspace/diffservice/diffservice.go @@ -1,3 +1,4 @@ +//go:generate mockgen -destination mock_diffservice/mock_diffservice.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice DiffSyncer,PeriodicSync package diffservice import ( @@ -6,13 +7,10 @@ import ( "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/remotediff" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" - "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/peer" - "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/rpc/rpcerr" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ldiff" "go.uber.org/zap" "strings" - "time" ) type DiffService interface { @@ -26,11 +24,9 @@ type DiffService interface { type diffService struct { spaceId string - periodicSync *periodicSync + periodicSync PeriodicSync storage storage.SpaceStorage - nconf nodeconf.Configuration diff ldiff.Diff - cache cache.TreeCache log *zap.Logger syncPeriod int @@ -40,23 +36,29 @@ func NewDiffService( spaceId string, syncPeriod int, storage storage.SpaceStorage, - nconf nodeconf.Configuration, + confConnector nodeconf.ConfConnector, cache cache.TreeCache, log *zap.Logger) DiffService { + + diff := ldiff.New(16, 16) + l := log.With(zap.String("spaceId", spaceId)) + factory := spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceClient) + syncer := newDiffSyncer(spaceId, diff, confConnector, cache, storage, factory, l) + periodicSync := newPeriodicSync(syncPeriod, syncer, l) + return &diffService{ - spaceId: spaceId, - storage: storage, - nconf: nconf, - cache: cache, - log: log, - syncPeriod: syncPeriod, + spaceId: spaceId, + storage: storage, + periodicSync: periodicSync, + diff: diff, + log: log, + syncPeriod: syncPeriod, } } func (d *diffService) Init(objectIds []string) { - d.periodicSync = newPeriodicSync(d.syncPeriod, d.sync, d.log.With(zap.String("spaceId", d.spaceId))) - d.diff = ldiff.New(16, 16) d.fillDiff(objectIds) + d.periodicSync.Run() } func (d *diffService) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) { @@ -80,49 +82,6 @@ func (d *diffService) Close() (err error) { return nil } -func (d *diffService) sync(ctx context.Context) error { - st := time.Now() - // diffing with responsible peers according to configuration - peers, err := d.nconf.ResponsiblePeers(ctx, d.spaceId) - if err != nil { - return err - } - for _, p := range peers { - 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.Info("synced", zap.String("spaceId", d.spaceId), zap.Duration("dur", time.Since(st))) - return nil -} - -func (d *diffService) syncWithPeer(ctx context.Context, p peer.Peer) (err error) { - cl := spacesyncproto.NewDRPCSpaceClient(p) - rdiff := remotediff.NewRemoteDiff(d.spaceId, cl) - newIds, changedIds, removedIds, err := d.diff.Diff(ctx, rdiff) - err = rpcerr.Unwrap(err) - if err != nil && err != spacesyncproto.ErrSpaceMissing { - return err - } - if err == spacesyncproto.ErrSpaceMissing { - return d.sendPushSpaceRequest(ctx, cl) - } - - d.pingTreesInCache(ctx, newIds) - d.pingTreesInCache(ctx, changedIds) - - d.log.Info("sync done:", zap.Int("newIds", len(newIds)), - zap.Int("changedIds", len(changedIds)), - zap.Int("removedIds", len(removedIds))) - return -} - -func (d *diffService) pingTreesInCache(ctx context.Context, trees []string) { - for _, tId := range trees { - _, _ = d.cache.GetTree(ctx, d.spaceId, tId) - } -} - func (d *diffService) fillDiff(objectIds []string) { var els = make([]ldiff.Element, 0, len(objectIds)) for _, id := range objectIds { @@ -142,30 +101,6 @@ func (d *diffService) fillDiff(objectIds []string) { d.diff.Set(els...) } -func (d *diffService) sendPushSpaceRequest(ctx context.Context, cl spacesyncproto.DRPCSpaceClient) (err error) { - aclStorage, err := d.storage.ACLStorage() - if err != nil { - return - } - - root, err := aclStorage.Root() - if err != nil { - return - } - - header, err := d.storage.SpaceHeader() - if err != nil { - return - } - - _, err = cl.PushSpace(ctx, &spacesyncproto.PushSpaceRequest{ - SpaceId: d.spaceId, - SpaceHeader: header, - AclRoot: root, - }) - return -} - func concatStrings(strs []string) string { var ( b strings.Builder diff --git a/common/commonspace/diffservice/diffservice_test.go b/common/commonspace/diffservice/diffservice_test.go new file mode 100644 index 00000000..27b4da3b --- /dev/null +++ b/common/commonspace/diffservice/diffservice_test.go @@ -0,0 +1,59 @@ +package diffservice + +import ( + "github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice/mock_diffservice" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage/mock_storage" + mock_storage2 "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage/mock_storage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ldiff" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ldiff/mock_ldiff" + "github.com/golang/mock/gomock" + "testing" +) + +func TestDiffService(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + spaceId := "spaceId" + l := logger.NewNamed("sync") + pSyncMock := mock_diffservice.NewMockPeriodicSync(ctrl) + storageMock := mock_storage.NewMockSpaceStorage(ctrl) + treeStorageMock := mock_storage2.NewMockTreeStorage(ctrl) + diffMock := mock_ldiff.NewMockDiff(ctrl) + syncPeriod := 1 + initId := "initId" + + service := &diffService{ + spaceId: spaceId, + storage: storageMock, + periodicSync: pSyncMock, + diff: diffMock, + log: l, + syncPeriod: syncPeriod, + } + + t.Run("init", func(t *testing.T) { + storageMock.EXPECT().TreeStorage(initId).Return(treeStorageMock, nil) + treeStorageMock.EXPECT().Heads().Return([]string{"h1", "h2"}, nil) + diffMock.EXPECT().Set(ldiff.Element{ + Id: initId, + Head: "h1h2", + }) + pSyncMock.EXPECT().Run() + service.Init([]string{initId}) + }) + + t.Run("update heads", func(t *testing.T) { + diffMock.EXPECT().Set(ldiff.Element{ + Id: initId, + Head: "h1h2", + }) + service.UpdateHeads(initId, []string{"h1", "h2"}) + }) + + t.Run("close", func(t *testing.T) { + pSyncMock.EXPECT().Close() + service.Close() + }) +} diff --git a/common/commonspace/diffservice/diffsyncer.go b/common/commonspace/diffservice/diffsyncer.go new file mode 100644 index 00000000..fe1d5d93 --- /dev/null +++ b/common/commonspace/diffservice/diffsyncer.go @@ -0,0 +1,115 @@ +package diffservice + +import ( + "context" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/remotediff" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/peer" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/rpc/rpcerr" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ldiff" + "go.uber.org/zap" + "time" +) + +type DiffSyncer interface { + Sync(ctx context.Context) error +} + +func newDiffSyncer( + spaceId string, + diff ldiff.Diff, + confConnector nodeconf.ConfConnector, + cache cache.TreeCache, + storage storage.SpaceStorage, + clientFactory spacesyncproto.ClientFactory, + log *zap.Logger) DiffSyncer { + return &diffSyncer{ + diff: diff, + spaceId: spaceId, + cache: cache, + storage: storage, + confConnector: confConnector, + clientFactory: clientFactory, + log: log, + } +} + +type diffSyncer struct { + spaceId string + diff ldiff.Diff + confConnector nodeconf.ConfConnector + cache cache.TreeCache + storage storage.SpaceStorage + clientFactory spacesyncproto.ClientFactory + log *zap.Logger +} + +func (d *diffSyncer) Sync(ctx context.Context) error { + st := time.Now() + // diffing with responsible peers according to configuration + peers, err := d.confConnector.GetResponsiblePeers(ctx, d.spaceId) + if err != nil { + return err + } + for _, p := range peers { + 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.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) { + cl := d.clientFactory.Client(p) + rdiff := remotediff.NewRemoteDiff(d.spaceId, cl) + newIds, changedIds, removedIds, err := d.diff.Diff(ctx, rdiff) + err = rpcerr.Unwrap(err) + if err != nil && err != spacesyncproto.ErrSpaceMissing { + return err + } + if err == spacesyncproto.ErrSpaceMissing { + return d.sendPushSpaceRequest(ctx, cl) + } + + d.pingTreesInCache(ctx, newIds) + d.pingTreesInCache(ctx, changedIds) + + d.log.Info("sync done:", zap.Int("newIds", len(newIds)), + zap.Int("changedIds", len(changedIds)), + zap.Int("removedIds", len(removedIds))) + return +} + +func (d *diffSyncer) pingTreesInCache(ctx context.Context, trees []string) { + for _, tId := range trees { + _, _ = d.cache.GetTree(ctx, d.spaceId, tId) + } +} + +func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, cl spacesyncproto.DRPCSpaceClient) (err error) { + aclStorage, err := d.storage.ACLStorage() + if err != nil { + return + } + + root, err := aclStorage.Root() + if err != nil { + return + } + + header, err := d.storage.SpaceHeader() + if err != nil { + return + } + + _, err = cl.PushSpace(ctx, &spacesyncproto.PushSpaceRequest{ + SpaceId: d.spaceId, + SpaceHeader: header, + AclRoot: root, + }) + return +} diff --git a/common/commonspace/diffservice/diffsyncer_test.go b/common/commonspace/diffservice/diffsyncer_test.go new file mode 100644 index 00000000..444b73fa --- /dev/null +++ b/common/commonspace/diffservice/diffsyncer_test.go @@ -0,0 +1,162 @@ +package diffservice + +import ( + "context" + "fmt" + "github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache/mock_cache" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/remotediff" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto/mock_spacesyncproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage/mock_storage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/peer" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf/mock_nodeconf" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto" + mock_aclstorage "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage/mock_storage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ldiff/mock_ldiff" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "storj.io/drpc" + "testing" + "time" +) + +type pushSpaceRequestMatcher struct { + spaceId string + aclRoot *aclrecordproto.RawACLRecordWithId + spaceHeader *spacesyncproto.SpaceHeader +} + +func (p pushSpaceRequestMatcher) Matches(x interface{}) bool { + res, ok := x.(*spacesyncproto.PushSpaceRequest) + if !ok { + return false + } + + return res.SpaceId == p.spaceId && res.AclRoot == p.aclRoot && res.SpaceHeader == p.spaceHeader +} + +func (p pushSpaceRequestMatcher) String() string { + return "" +} + +type mockPeer struct{} + +func (m mockPeer) Id() string { + return "mockId" +} + +func (m mockPeer) LastUsage() time.Time { + return time.Time{} +} + +func (m mockPeer) UpdateLastUsage() { +} + +func (m mockPeer) Close() error { + return nil +} + +func (m mockPeer) Closed() <-chan struct{} { + return make(chan struct{}) +} + +func (m mockPeer) Invoke(ctx context.Context, rpc string, enc drpc.Encoding, in, out drpc.Message) error { + return nil +} + +func (m mockPeer) NewStream(ctx context.Context, rpc string, enc drpc.Encoding) (drpc.Stream, error) { + return nil, nil +} + +func newPushSpaceRequestMatcher( + spaceId string, + aclRoot *aclrecordproto.RawACLRecordWithId, + spaceHeader *spacesyncproto.SpaceHeader) *pushSpaceRequestMatcher { + return &pushSpaceRequestMatcher{ + spaceId: spaceId, + aclRoot: aclRoot, + spaceHeader: spaceHeader, + } +} + +func TestDiffSyncer_Sync(t *testing.T) { + // setup + ctx := context.Background() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + diffMock := mock_ldiff.NewMockDiff(ctrl) + connectorMock := mock_nodeconf.NewMockConfConnector(ctrl) + cacheMock := mock_cache.NewMockTreeCache(ctrl) + stMock := mock_storage.NewMockSpaceStorage(ctrl) + clientMock := mock_spacesyncproto.NewMockDRPCSpaceClient(ctrl) + factory := spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceClient { + return clientMock + }) + spaceId := "spaceId" + l := logger.NewNamed(spaceId) + diffSyncer := newDiffSyncer(spaceId, diffMock, connectorMock, cacheMock, stMock, factory, l) + + t.Run("diff syncer sync simple", func(t *testing.T) { + connectorMock.EXPECT(). + GetResponsiblePeers(gomock.Any(), spaceId). + Return([]peer.Peer{mockPeer{}}, nil) + diffMock.EXPECT(). + Diff(gomock.Any(), gomock.Eq(remotediff.NewRemoteDiff(spaceId, clientMock))). + Return([]string{"new"}, []string{"changed"}, nil, nil) + for _, arg := range []string{"new", "changed"} { + cacheMock.EXPECT(). + GetTree(gomock.Any(), spaceId, arg). + Return(cache.TreeResult{}, nil) + } + require.NoError(t, diffSyncer.Sync(ctx)) + }) + + t.Run("diff syncer sync conf error", func(t *testing.T) { + connectorMock.EXPECT(). + GetResponsiblePeers(gomock.Any(), spaceId). + Return(nil, fmt.Errorf("some error")) + + require.Error(t, diffSyncer.Sync(ctx)) + }) + + t.Run("diff syncer sync space missing", func(t *testing.T) { + aclStorageMock := mock_aclstorage.NewMockListStorage(ctrl) + aclRoot := &aclrecordproto.RawACLRecordWithId{} + spaceHeader := &spacesyncproto.SpaceHeader{} + + connectorMock.EXPECT(). + GetResponsiblePeers(gomock.Any(), spaceId). + Return([]peer.Peer{mockPeer{}}, nil) + diffMock.EXPECT(). + Diff(gomock.Any(), gomock.Eq(remotediff.NewRemoteDiff(spaceId, clientMock))). + Return(nil, nil, nil, spacesyncproto.ErrSpaceMissing) + stMock.EXPECT(). + ACLStorage(). + Return(aclStorageMock, nil) + stMock.EXPECT(). + SpaceHeader(). + Return(spaceHeader, nil) + aclStorageMock.EXPECT(). + Root(). + Return(aclRoot, nil) + clientMock.EXPECT(). + PushSpace(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRoot, spaceHeader)). + Return(nil, nil) + + require.NoError(t, diffSyncer.Sync(ctx)) + }) + + t.Run("diff syncer sync other error", func(t *testing.T) { + connectorMock.EXPECT(). + GetResponsiblePeers(gomock.Any(), spaceId). + Return([]peer.Peer{mockPeer{}}, nil) + diffMock.EXPECT(). + Diff(gomock.Any(), gomock.Eq(remotediff.NewRemoteDiff(spaceId, clientMock))). + Return(nil, nil, nil, spacesyncproto.ErrUnexpected) + + require.NoError(t, diffSyncer.Sync(ctx)) + }) +} diff --git a/common/commonspace/diffservice/mock_diffservice/mock_diffservice.go b/common/commonspace/diffservice/mock_diffservice/mock_diffservice.go new file mode 100644 index 00000000..966a7cc5 --- /dev/null +++ b/common/commonspace/diffservice/mock_diffservice/mock_diffservice.go @@ -0,0 +1,96 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice (interfaces: DiffSyncer,PeriodicSync) + +// Package mock_diffservice is a generated GoMock package. +package mock_diffservice + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockDiffSyncer is a mock of DiffSyncer interface. +type MockDiffSyncer struct { + ctrl *gomock.Controller + recorder *MockDiffSyncerMockRecorder +} + +// MockDiffSyncerMockRecorder is the mock recorder for MockDiffSyncer. +type MockDiffSyncerMockRecorder struct { + mock *MockDiffSyncer +} + +// NewMockDiffSyncer creates a new mock instance. +func NewMockDiffSyncer(ctrl *gomock.Controller) *MockDiffSyncer { + mock := &MockDiffSyncer{ctrl: ctrl} + mock.recorder = &MockDiffSyncerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDiffSyncer) EXPECT() *MockDiffSyncerMockRecorder { + return m.recorder +} + +// Sync mocks base method. +func (m *MockDiffSyncer) Sync(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Sync", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Sync indicates an expected call of Sync. +func (mr *MockDiffSyncerMockRecorder) Sync(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sync", reflect.TypeOf((*MockDiffSyncer)(nil).Sync), arg0) +} + +// MockPeriodicSync is a mock of PeriodicSync interface. +type MockPeriodicSync struct { + ctrl *gomock.Controller + recorder *MockPeriodicSyncMockRecorder +} + +// MockPeriodicSyncMockRecorder is the mock recorder for MockPeriodicSync. +type MockPeriodicSyncMockRecorder struct { + mock *MockPeriodicSync +} + +// NewMockPeriodicSync creates a new mock instance. +func NewMockPeriodicSync(ctrl *gomock.Controller) *MockPeriodicSync { + mock := &MockPeriodicSync{ctrl: ctrl} + mock.recorder = &MockPeriodicSyncMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPeriodicSync) EXPECT() *MockPeriodicSyncMockRecorder { + return m.recorder +} + +// Close mocks base method. +func (m *MockPeriodicSync) Close() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Close") +} + +// Close indicates an expected call of Close. +func (mr *MockPeriodicSyncMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockPeriodicSync)(nil).Close)) +} + +// Run mocks base method. +func (m *MockPeriodicSync) Run() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Run") +} + +// Run indicates an expected call of Run. +func (mr *MockPeriodicSyncMockRecorder) Run() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockPeriodicSync)(nil).Run)) +} diff --git a/common/commonspace/diffservice/periodicsync.go b/common/commonspace/diffservice/periodicsync.go index 59616eab..a74b25cf 100644 --- a/common/commonspace/diffservice/periodicsync.go +++ b/common/commonspace/diffservice/periodicsync.go @@ -6,25 +6,34 @@ import ( "time" ) -func newPeriodicSync(periodSeconds int, sync func(ctx context.Context) error, l *zap.Logger) *periodicSync { +type PeriodicSync interface { + Run() + Close() +} + +func newPeriodicSync(periodSeconds int, syncer DiffSyncer, l *zap.Logger) *periodicSync { ctx, cancel := context.WithCancel(context.Background()) - ps := &periodicSync{ - log: l, - sync: sync, - syncCtx: ctx, - syncCancel: cancel, - syncLoopDone: make(chan struct{}), + return &periodicSync{ + syncer: syncer, + log: l, + syncCtx: ctx, + syncCancel: cancel, + syncLoopDone: make(chan struct{}), + periodSeconds: periodSeconds, } - go ps.syncLoop(periodSeconds) - return ps } type periodicSync struct { - log *zap.Logger - sync func(ctx context.Context) error - syncCtx context.Context - syncCancel context.CancelFunc - syncLoopDone chan struct{} + log *zap.Logger + syncer DiffSyncer + syncCtx context.Context + syncCancel context.CancelFunc + syncLoopDone chan struct{} + periodSeconds int +} + +func (p *periodicSync) Run() { + go p.syncLoop(p.periodSeconds) } func (p *periodicSync) syncLoop(periodSeconds int) { @@ -33,7 +42,7 @@ func (p *periodicSync) syncLoop(periodSeconds int) { doSync := func() { ctx, cancel := context.WithTimeout(p.syncCtx, time.Minute) defer cancel() - if err := p.sync(ctx); err != nil { + if err := p.syncer.Sync(ctx); err != nil { p.log.Warn("periodic sync error", zap.Error(err)) } } diff --git a/common/commonspace/diffservice/periodicsync_test.go b/common/commonspace/diffservice/periodicsync_test.go new file mode 100644 index 00000000..56d855f2 --- /dev/null +++ b/common/commonspace/diffservice/periodicsync_test.go @@ -0,0 +1,38 @@ +package diffservice + +import ( + "github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice/mock_diffservice" + "github.com/golang/mock/gomock" + "testing" + "time" +) + +func TestPeriodicSync_Run(t *testing.T) { + // setup + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + l := logger.NewNamed("sync") + diffSyncer := mock_diffservice.NewMockDiffSyncer(ctrl) + t.Run("diff syncer 1 time", func(t *testing.T) { + secs := 0 + pSync := newPeriodicSync(secs, diffSyncer, l) + + diffSyncer.EXPECT().Sync(gomock.Any()).Times(1).Return(nil) + + pSync.Run() + pSync.Close() + }) + + t.Run("diff syncer 2 times", func(t *testing.T) { + secs := 1 + + pSync := newPeriodicSync(secs, diffSyncer, l) + diffSyncer.EXPECT().Sync(gomock.Any()).Times(2).Return(nil) + + pSync.Run() + time.Sleep(time.Second * time.Duration(secs)) + pSync.Close() + }) +} diff --git a/common/commonspace/payloads.go b/common/commonspace/payloads.go new file mode 100644 index 00000000..fae285ab --- /dev/null +++ b/common/commonspace/payloads.go @@ -0,0 +1,193 @@ +package commonspace + +import ( + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey" + "hash/fnv" + "math/rand" + "time" +) + +func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload storage.SpaceStorageCreatePayload, err error) { + // unmarshalling signing and encryption keys + identity, err := payload.SigningKey.GetPublic().Raw() + if err != nil { + return + } + encPubKey, err := payload.EncryptionKey.GetPublic().Raw() + if err != nil { + return + } + + // preparing header and space id + bytes := make([]byte, 32) + _, err = rand.Read(bytes) + if err != nil { + return + } + header := &spacesyncproto.SpaceHeader{ + Identity: identity, + Timestamp: time.Now().UnixNano(), + SpaceType: payload.SpaceType, + ReplicationKey: payload.ReplicationKey, + Seed: bytes, + } + marshalled, err := header.Marshal() + if err != nil { + return + } + id, err := cid.NewCIDFromBytes(marshalled) + if err != nil { + return + } + spaceId := NewSpaceId(id, payload.ReplicationKey) + + // encrypting read key + hasher := fnv.New64() + _, err = hasher.Write(payload.ReadKey) + if err != nil { + return + } + readKeyHash := hasher.Sum64() + encReadKey, err := payload.EncryptionKey.GetPublic().Encrypt(payload.ReadKey) + if err != nil { + return + } + + // preparing acl + aclRoot := &aclrecordproto.ACLRoot{ + Identity: identity, + EncryptionKey: encPubKey, + SpaceId: spaceId, + EncryptedReadKey: encReadKey, + DerivationScheme: "", + CurrentReadKeyHash: readKeyHash, + Timestamp: time.Now().UnixNano(), + } + rawWithId, err := marshalACLRoot(aclRoot, payload.SigningKey) + if err != nil { + return + } + + // creating storage + storagePayload = storage.SpaceStorageCreatePayload{ + RecWithId: rawWithId, + SpaceHeader: header, + Id: id, + } + return +} + +func storagePayloadForSpaceDerive(payload SpaceDerivePayload) (storagePayload storage.SpaceStorageCreatePayload, err error) { + // unmarshalling signing and encryption keys + identity, err := payload.SigningKey.GetPublic().Raw() + if err != nil { + return + } + signPrivKey, err := payload.SigningKey.Raw() + if err != nil { + return + } + encPubKey, err := payload.EncryptionKey.GetPublic().Raw() + if err != nil { + return + } + encPrivKey, err := payload.EncryptionKey.Raw() + if err != nil { + return + } + + // preparing replication key + hasher := fnv.New64() + _, err = hasher.Write(identity) + if err != nil { + return + } + repKey := hasher.Sum64() + + // preparing header and space id + header := &spacesyncproto.SpaceHeader{ + Identity: identity, + SpaceType: SpaceTypeDerived, + ReplicationKey: repKey, + } + marshalled, err := header.Marshal() + if err != nil { + return + } + id, err := cid.NewCIDFromBytes(marshalled) + if err != nil { + return + } + spaceId := NewSpaceId(id, repKey) + + // deriving and encrypting read key + readKey, err := aclrecordproto.ACLReadKeyDerive(signPrivKey, encPrivKey) + if err != nil { + return + } + hasher = fnv.New64() + _, err = hasher.Write(readKey.Bytes()) + if err != nil { + return + } + readKeyHash := hasher.Sum64() + encReadKey, err := payload.EncryptionKey.GetPublic().Encrypt(readKey.Bytes()) + if err != nil { + return + } + + // preparing acl + aclRoot := &aclrecordproto.ACLRoot{ + Identity: identity, + EncryptionKey: encPubKey, + SpaceId: spaceId, + EncryptedReadKey: encReadKey, + DerivationScheme: "", + CurrentReadKeyHash: readKeyHash, + Timestamp: time.Now().UnixNano(), + } + rawWithId, err := marshalACLRoot(aclRoot, payload.SigningKey) + if err != nil { + return + } + + // creating storage + storagePayload = storage.SpaceStorageCreatePayload{ + RecWithId: rawWithId, + SpaceHeader: header, + Id: id, + } + return +} + +func marshalACLRoot(aclRoot *aclrecordproto.ACLRoot, key signingkey.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 := cid.NewCIDFromBytes(marshalledRaw) + if err != nil { + return + } + rawWithId = &aclrecordproto.RawACLRecordWithId{ + Payload: marshalledRaw, + Id: aclHeadId, + } + return +} diff --git a/common/commonspace/rpchandler.go b/common/commonspace/rpchandler.go index dbc5b7aa..73698856 100644 --- a/common/commonspace/rpchandler.go +++ b/common/commonspace/rpchandler.go @@ -19,5 +19,5 @@ func (r *rpcHandler) HeadSync(ctx context.Context, req *spacesyncproto.HeadSyncR } func (r *rpcHandler) Stream(stream spacesyncproto.DRPCSpace_StreamStream) (err error) { - return r.s.SyncService().StreamPool().AddAndReadStreamSync(stream) + return r.s.SyncService().SyncClient().AddAndReadStreamSync(stream) } diff --git a/common/commonspace/service.go b/common/commonspace/service.go index 48f4b4cf..adefcf7f 100644 --- a/common/commonspace/service.go +++ b/common/commonspace/service.go @@ -6,17 +6,11 @@ import ( "github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice" - "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/pool" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf" "github.com/anytypeio/go-anytype-infrastructure-experiments/config" - "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto" - "github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid" - "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey" - "hash/fnv" - "math/rand" - "time" ) const CName = "common.commonspace" @@ -39,6 +33,7 @@ type service struct { configurationService nodeconf.Service storageProvider storage.SpaceStorageProvider cache cache.TreeCache + pool pool.Pool } func (s *service) Init(a *app.App) (err error) { @@ -46,6 +41,7 @@ func (s *service) Init(a *app.App) (err error) { s.storageProvider = a.MustComponent(storage.CName).(storage.SpaceStorageProvider) s.configurationService = a.MustComponent(nodeconf.CName).(nodeconf.Service) s.cache = a.MustComponent(cache.CName).(cache.TreeCache) + s.pool = a.MustComponent(pool.CName).(pool.Pool) return nil } @@ -57,171 +53,32 @@ func (s *service) CreateSpace( ctx context.Context, cache cache.TreeCache, payload SpaceCreatePayload) (sp Space, err error) { - - // unmarshalling signing and encryption keys - identity, err := payload.SigningKey.GetPublic().Raw() + storageCreate, err := storagePayloadForSpaceCreate(payload) if err != nil { return } - encPubKey, err := payload.EncryptionKey.GetPublic().Raw() - if err != nil { - return - } - - // preparing header and space id - bytes := make([]byte, 32) - _, err = rand.Read(bytes) - if err != nil { - return - } - header := &spacesyncproto.SpaceHeader{ - Identity: identity, - Timestamp: time.Now().UnixNano(), - SpaceType: payload.SpaceType, - ReplicationKey: payload.ReplicationKey, - Seed: bytes, - } - marshalled, err := header.Marshal() - if err != nil { - return - } - id, err := cid.NewCIDFromBytes(marshalled) - if err != nil { - return - } - spaceId := NewSpaceId(id, payload.ReplicationKey) - - // encrypting read key - hasher := fnv.New64() - _, err = hasher.Write(payload.ReadKey) - if err != nil { - return - } - readKeyHash := hasher.Sum64() - encReadKey, err := payload.EncryptionKey.GetPublic().Encrypt(payload.ReadKey) - if err != nil { - return - } - - // preparing acl - aclRoot := &aclrecordproto.ACLRoot{ - Identity: identity, - EncryptionKey: encPubKey, - SpaceId: spaceId, - EncryptedReadKey: encReadKey, - DerivationScheme: "", - CurrentReadKeyHash: readKeyHash, - Timestamp: time.Now().UnixNano(), - } - rawWithId, err := marshalACLRoot(aclRoot, payload.SigningKey) - if err != nil { - return - } - - // creating storage - storageCreate := storage.SpaceStorageCreatePayload{ - RecWithId: rawWithId, - SpaceHeader: header, - Id: id, - } _, err = s.storageProvider.CreateSpaceStorage(storageCreate) if err != nil { return } - return s.GetSpace(ctx, spaceId) + return s.GetSpace(ctx, storageCreate.Id) } func (s *service) DeriveSpace( ctx context.Context, cache cache.TreeCache, payload SpaceDerivePayload) (sp Space, err error) { - - // unmarshalling signing and encryption keys - identity, err := payload.SigningKey.GetPublic().Raw() + storageCreate, err := storagePayloadForSpaceDerive(payload) if err != nil { return } - signPrivKey, err := payload.SigningKey.Raw() - if err != nil { - return - } - encPubKey, err := payload.EncryptionKey.GetPublic().Raw() - if err != nil { - return - } - encPrivKey, err := payload.EncryptionKey.Raw() - if err != nil { - return - } - - // preparing replication key - hasher := fnv.New64() - _, err = hasher.Write(identity) - if err != nil { - return - } - repKey := hasher.Sum64() - - // preparing header and space id - header := &spacesyncproto.SpaceHeader{ - Identity: identity, - SpaceType: SpaceTypeDerived, - ReplicationKey: repKey, - } - marshalled, err := header.Marshal() - if err != nil { - return - } - id, err := cid.NewCIDFromBytes(marshalled) - if err != nil { - return - } - spaceId := NewSpaceId(id, repKey) - - // deriving and encrypting read key - readKey, err := aclrecordproto.ACLReadKeyDerive(signPrivKey, encPrivKey) - if err != nil { - return - } - hasher = fnv.New64() - _, err = hasher.Write(readKey.Bytes()) - if err != nil { - return - } - readKeyHash := hasher.Sum64() - encReadKey, err := payload.EncryptionKey.GetPublic().Encrypt(readKey.Bytes()) - if err != nil { - return - } - - // preparing acl - aclRoot := &aclrecordproto.ACLRoot{ - Identity: identity, - EncryptionKey: encPubKey, - SpaceId: spaceId, - EncryptedReadKey: encReadKey, - DerivationScheme: "", - CurrentReadKeyHash: readKeyHash, - Timestamp: time.Now().UnixNano(), - } - rawWithId, err := marshalACLRoot(aclRoot, payload.SigningKey) - if err != nil { - return - } - - // creating storage - storageCreate := storage.SpaceStorageCreatePayload{ - RecWithId: rawWithId, - SpaceHeader: header, - Id: id, - } _, err = s.storageProvider.CreateSpaceStorage(storageCreate) if err != nil { return } - return s.GetSpace(ctx, spaceId) + return s.GetSpace(ctx, storageCreate.Id) } func (s *service) GetSpace(ctx context.Context, id string) (Space, error) { @@ -230,8 +87,9 @@ func (s *service) GetSpace(ctx context.Context, id string) (Space, error) { return nil, err } lastConfiguration := s.configurationService.GetLast() - diffService := diffservice.NewDiffService(id, s.config.SyncPeriod, st, lastConfiguration, s.cache, log) - syncService := syncservice.NewSyncService(id, diffService, s.cache, lastConfiguration) + confConnector := nodeconf.NewConfConnector(lastConfiguration, s.pool) + diffService := diffservice.NewDiffService(id, s.config.SyncPeriod, st, confConnector, s.cache, log) + syncService := syncservice.NewSyncService(id, diffService, s.cache, lastConfiguration, confConnector) sp := &space{ id: id, syncService: syncService, @@ -244,31 +102,3 @@ func (s *service) GetSpace(ctx context.Context, id string) (Space, error) { } return sp, nil } - -func marshalACLRoot(aclRoot *aclrecordproto.ACLRoot, key signingkey.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 := cid.NewCIDFromBytes(marshalledRaw) - if err != nil { - return - } - rawWithId = &aclrecordproto.RawACLRecordWithId{ - Payload: marshalledRaw, - Id: aclHeadId, - } - return -} diff --git a/common/commonspace/space.go b/common/commonspace/space.go index 46099da3..8111895a 100644 --- a/common/commonspace/space.go +++ b/common/commonspace/space.go @@ -90,11 +90,11 @@ func (s *space) DiffService() diffservice.DiffService { } func (s *space) DeriveTree(ctx context.Context, payload tree.ObjectTreeCreatePayload, listener updatelistener.UpdateListener) (tree.ObjectTree, error) { - return synctree.DeriveSyncTree(ctx, payload, s.syncService, listener, s.aclList, s.storage.CreateTreeStorage) + return synctree.DeriveSyncTree(ctx, payload, s.syncService.SyncClient(), listener, s.aclList, s.storage.CreateTreeStorage) } func (s *space) CreateTree(ctx context.Context, payload tree.ObjectTreeCreatePayload, listener updatelistener.UpdateListener) (tree.ObjectTree, error) { - return synctree.CreateSyncTree(ctx, payload, s.syncService, listener, s.aclList, s.storage.CreateTreeStorage) + return synctree.CreateSyncTree(ctx, payload, s.syncService.SyncClient(), listener, s.aclList, s.storage.CreateTreeStorage) } func (s *space) BuildTree(ctx context.Context, id string, listener updatelistener.UpdateListener) (t tree.ObjectTree, err error) { @@ -104,10 +104,9 @@ func (s *space) BuildTree(ctx context.Context, id string, listener updatelistene if err != nil { return nil, err } - - return s.syncService.StreamPool().SendSync( + return s.syncService.SyncClient().SendSync( peerId, - spacesyncproto.WrapFullRequest(&spacesyncproto.ObjectFullSyncRequest{}, nil, id, ""), + s.syncService.SyncClient().CreateNewTreeRequest(id), ) } @@ -142,7 +141,7 @@ func (s *space) BuildTree(ctx context.Context, id string, listener updatelistene return } } - return synctree.BuildSyncTree(ctx, s.syncService, store.(treestorage.TreeStorage), listener, s.aclList) + return synctree.BuildSyncTree(ctx, s.syncService.SyncClient(), store.(treestorage.TreeStorage), listener, s.aclList) } func (s *space) Close() error { diff --git a/common/commonspace/spacesyncproto/mock_spacesyncproto/mock_spacesyncproto.go b/common/commonspace/spacesyncproto/mock_spacesyncproto/mock_spacesyncproto.go new file mode 100644 index 00000000..0d0d52c5 --- /dev/null +++ b/common/commonspace/spacesyncproto/mock_spacesyncproto/mock_spacesyncproto.go @@ -0,0 +1,96 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto (interfaces: DRPCSpaceClient) + +// Package mock_spacesyncproto is a generated GoMock package. +package mock_spacesyncproto + +import ( + context "context" + reflect "reflect" + + spacesyncproto "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + gomock "github.com/golang/mock/gomock" + drpc "storj.io/drpc" +) + +// MockDRPCSpaceClient is a mock of DRPCSpaceClient interface. +type MockDRPCSpaceClient struct { + ctrl *gomock.Controller + recorder *MockDRPCSpaceClientMockRecorder +} + +// MockDRPCSpaceClientMockRecorder is the mock recorder for MockDRPCSpaceClient. +type MockDRPCSpaceClientMockRecorder struct { + mock *MockDRPCSpaceClient +} + +// NewMockDRPCSpaceClient creates a new mock instance. +func NewMockDRPCSpaceClient(ctrl *gomock.Controller) *MockDRPCSpaceClient { + mock := &MockDRPCSpaceClient{ctrl: ctrl} + mock.recorder = &MockDRPCSpaceClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDRPCSpaceClient) EXPECT() *MockDRPCSpaceClientMockRecorder { + return m.recorder +} + +// DRPCConn mocks base method. +func (m *MockDRPCSpaceClient) DRPCConn() drpc.Conn { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DRPCConn") + ret0, _ := ret[0].(drpc.Conn) + return ret0 +} + +// DRPCConn indicates an expected call of DRPCConn. +func (mr *MockDRPCSpaceClientMockRecorder) DRPCConn() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DRPCConn", reflect.TypeOf((*MockDRPCSpaceClient)(nil).DRPCConn)) +} + +// HeadSync mocks base method. +func (m *MockDRPCSpaceClient) HeadSync(arg0 context.Context, arg1 *spacesyncproto.HeadSyncRequest) (*spacesyncproto.HeadSyncResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeadSync", arg0, arg1) + ret0, _ := ret[0].(*spacesyncproto.HeadSyncResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HeadSync indicates an expected call of HeadSync. +func (mr *MockDRPCSpaceClientMockRecorder) HeadSync(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadSync", reflect.TypeOf((*MockDRPCSpaceClient)(nil).HeadSync), arg0, arg1) +} + +// PushSpace mocks base method. +func (m *MockDRPCSpaceClient) PushSpace(arg0 context.Context, arg1 *spacesyncproto.PushSpaceRequest) (*spacesyncproto.PushSpaceResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PushSpace", arg0, arg1) + ret0, _ := ret[0].(*spacesyncproto.PushSpaceResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PushSpace indicates an expected call of PushSpace. +func (mr *MockDRPCSpaceClientMockRecorder) PushSpace(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushSpace", reflect.TypeOf((*MockDRPCSpaceClient)(nil).PushSpace), arg0, arg1) +} + +// Stream mocks base method. +func (m *MockDRPCSpaceClient) Stream(arg0 context.Context) (spacesyncproto.DRPCSpace_StreamClient, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Stream", arg0) + ret0, _ := ret[0].(spacesyncproto.DRPCSpace_StreamClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Stream indicates an expected call of Stream. +func (mr *MockDRPCSpaceClientMockRecorder) Stream(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stream", reflect.TypeOf((*MockDRPCSpaceClient)(nil).Stream), arg0) +} diff --git a/common/commonspace/spacesyncproto/spacesync.go b/common/commonspace/spacesyncproto/spacesync.go index 2e22737a..068642fe 100644 --- a/common/commonspace/spacesyncproto/spacesync.go +++ b/common/commonspace/spacesyncproto/spacesync.go @@ -1,9 +1,23 @@ +//go:generate mockgen -destination mock_spacesyncproto/mock_spacesyncproto.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto DRPCSpaceClient package spacesyncproto -import "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto" +import ( + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto" + "storj.io/drpc" +) type SpaceStream = DRPCSpace_StreamStream +type ClientFactoryFunc func(cc drpc.Conn) DRPCSpaceClient + +func (c ClientFactoryFunc) Client(cc drpc.Conn) DRPCSpaceClient { + return c(cc) +} + +type ClientFactory interface { + Client(cc drpc.Conn) DRPCSpaceClient +} + func WrapHeadUpdate(update *ObjectHeadUpdate, rootChange *treechangeproto.RawTreeChangeWithId, treeId, trackingId string) *ObjectSyncMessage { return &ObjectSyncMessage{ Content: &ObjectSyncContentValue{ diff --git a/common/commonspace/storage/mock_storage/mock_storage.go b/common/commonspace/storage/mock_storage/mock_storage.go new file mode 100644 index 00000000..14b6003f --- /dev/null +++ b/common/commonspace/storage/mock_storage/mock_storage.go @@ -0,0 +1,194 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage (interfaces: SpaceStorageProvider,SpaceStorage) + +// Package mock_storage is a generated GoMock package. +package mock_storage + +import ( + reflect "reflect" + + app "github.com/anytypeio/go-anytype-infrastructure-experiments/app" + spacesyncproto "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + storage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" + storage0 "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage" + gomock "github.com/golang/mock/gomock" +) + +// MockSpaceStorageProvider is a mock of SpaceStorageProvider interface. +type MockSpaceStorageProvider struct { + ctrl *gomock.Controller + recorder *MockSpaceStorageProviderMockRecorder +} + +// MockSpaceStorageProviderMockRecorder is the mock recorder for MockSpaceStorageProvider. +type MockSpaceStorageProviderMockRecorder struct { + mock *MockSpaceStorageProvider +} + +// NewMockSpaceStorageProvider creates a new mock instance. +func NewMockSpaceStorageProvider(ctrl *gomock.Controller) *MockSpaceStorageProvider { + mock := &MockSpaceStorageProvider{ctrl: ctrl} + mock.recorder = &MockSpaceStorageProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSpaceStorageProvider) EXPECT() *MockSpaceStorageProviderMockRecorder { + return m.recorder +} + +// CreateSpaceStorage mocks base method. +func (m *MockSpaceStorageProvider) CreateSpaceStorage(arg0 storage.SpaceStorageCreatePayload) (storage.SpaceStorage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateSpaceStorage", arg0) + ret0, _ := ret[0].(storage.SpaceStorage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateSpaceStorage indicates an expected call of CreateSpaceStorage. +func (mr *MockSpaceStorageProviderMockRecorder) CreateSpaceStorage(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSpaceStorage", reflect.TypeOf((*MockSpaceStorageProvider)(nil).CreateSpaceStorage), arg0) +} + +// Init mocks base method. +func (m *MockSpaceStorageProvider) 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 *MockSpaceStorageProviderMockRecorder) Init(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockSpaceStorageProvider)(nil).Init), arg0) +} + +// Name mocks base method. +func (m *MockSpaceStorageProvider) 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 *MockSpaceStorageProviderMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockSpaceStorageProvider)(nil).Name)) +} + +// SpaceStorage mocks base method. +func (m *MockSpaceStorageProvider) SpaceStorage(arg0 string) (storage.SpaceStorage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SpaceStorage", arg0) + ret0, _ := ret[0].(storage.SpaceStorage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SpaceStorage indicates an expected call of SpaceStorage. +func (mr *MockSpaceStorageProviderMockRecorder) SpaceStorage(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpaceStorage", reflect.TypeOf((*MockSpaceStorageProvider)(nil).SpaceStorage), arg0) +} + +// MockSpaceStorage is a mock of SpaceStorage interface. +type MockSpaceStorage struct { + ctrl *gomock.Controller + recorder *MockSpaceStorageMockRecorder +} + +// MockSpaceStorageMockRecorder is the mock recorder for MockSpaceStorage. +type MockSpaceStorageMockRecorder struct { + mock *MockSpaceStorage +} + +// NewMockSpaceStorage creates a new mock instance. +func NewMockSpaceStorage(ctrl *gomock.Controller) *MockSpaceStorage { + mock := &MockSpaceStorage{ctrl: ctrl} + mock.recorder = &MockSpaceStorageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSpaceStorage) EXPECT() *MockSpaceStorageMockRecorder { + return m.recorder +} + +// ACLStorage mocks base method. +func (m *MockSpaceStorage) ACLStorage() (storage0.ListStorage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ACLStorage") + ret0, _ := ret[0].(storage0.ListStorage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ACLStorage indicates an expected call of ACLStorage. +func (mr *MockSpaceStorageMockRecorder) ACLStorage() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ACLStorage", reflect.TypeOf((*MockSpaceStorage)(nil).ACLStorage)) +} + +// CreateTreeStorage mocks base method. +func (m *MockSpaceStorage) CreateTreeStorage(arg0 storage0.TreeStorageCreatePayload) (storage0.TreeStorage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateTreeStorage", arg0) + ret0, _ := ret[0].(storage0.TreeStorage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateTreeStorage indicates an expected call of CreateTreeStorage. +func (mr *MockSpaceStorageMockRecorder) CreateTreeStorage(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTreeStorage", reflect.TypeOf((*MockSpaceStorage)(nil).CreateTreeStorage), arg0) +} + +// SpaceHeader mocks base method. +func (m *MockSpaceStorage) SpaceHeader() (*spacesyncproto.SpaceHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SpaceHeader") + ret0, _ := ret[0].(*spacesyncproto.SpaceHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SpaceHeader indicates an expected call of SpaceHeader. +func (mr *MockSpaceStorageMockRecorder) SpaceHeader() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpaceHeader", reflect.TypeOf((*MockSpaceStorage)(nil).SpaceHeader)) +} + +// StoredIds mocks base method. +func (m *MockSpaceStorage) StoredIds() ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StoredIds") + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StoredIds indicates an expected call of StoredIds. +func (mr *MockSpaceStorageMockRecorder) StoredIds() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoredIds", reflect.TypeOf((*MockSpaceStorage)(nil).StoredIds)) +} + +// TreeStorage mocks base method. +func (m *MockSpaceStorage) TreeStorage(arg0 string) (storage0.TreeStorage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TreeStorage", arg0) + ret0, _ := ret[0].(storage0.TreeStorage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// TreeStorage indicates an expected call of TreeStorage. +func (mr *MockSpaceStorageMockRecorder) TreeStorage(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TreeStorage", reflect.TypeOf((*MockSpaceStorage)(nil).TreeStorage), arg0) +} diff --git a/common/commonspace/storage/storage.go b/common/commonspace/storage/storage.go index add06e08..fc1cfa8d 100644 --- a/common/commonspace/storage/storage.go +++ b/common/commonspace/storage/storage.go @@ -1,3 +1,4 @@ +//go:generate mockgen -destination mock_storage/mock_storage.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage SpaceStorageProvider,SpaceStorage package storage import ( diff --git a/common/commonspace/syncservice/mock_syncservice/mock_syncservice.go b/common/commonspace/syncservice/mock_syncservice/mock_syncservice.go new file mode 100644 index 00000000..6f1a272b --- /dev/null +++ b/common/commonspace/syncservice/mock_syncservice/mock_syncservice.go @@ -0,0 +1,206 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice (interfaces: SyncClient) + +// Package mock_syncservice is a generated GoMock package. +package mock_syncservice + +import ( + reflect "reflect" + + spacesyncproto "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + tree "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree" + treechangeproto "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto" + 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 +} + +// AddAndReadStreamAsync mocks base method. +func (m *MockSyncClient) AddAndReadStreamAsync(arg0 spacesyncproto.DRPCSpace_StreamStream) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddAndReadStreamAsync", arg0) +} + +// AddAndReadStreamAsync indicates an expected call of AddAndReadStreamAsync. +func (mr *MockSyncClientMockRecorder) AddAndReadStreamAsync(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAndReadStreamAsync", reflect.TypeOf((*MockSyncClient)(nil).AddAndReadStreamAsync), arg0) +} + +// AddAndReadStreamSync mocks base method. +func (m *MockSyncClient) AddAndReadStreamSync(arg0 spacesyncproto.DRPCSpace_StreamStream) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddAndReadStreamSync", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddAndReadStreamSync indicates an expected call of AddAndReadStreamSync. +func (mr *MockSyncClientMockRecorder) AddAndReadStreamSync(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAndReadStreamSync", reflect.TypeOf((*MockSyncClient)(nil).AddAndReadStreamSync), arg0) +} + +// BroadcastAsync mocks base method. +func (m *MockSyncClient) BroadcastAsync(arg0 *spacesyncproto.ObjectSyncMessage) 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 *spacesyncproto.ObjectSyncMessage) 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) +} + +// Close mocks base method. +func (m *MockSyncClient) 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 *MockSyncClientMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSyncClient)(nil).Close)) +} + +// CreateFullSyncRequest mocks base method. +func (m *MockSyncClient) CreateFullSyncRequest(arg0 tree.ObjectTree, arg1, arg2 []string, arg3 string) (*spacesyncproto.ObjectSyncMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateFullSyncRequest", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateFullSyncRequest indicates an expected call of CreateFullSyncRequest. +func (mr *MockSyncClientMockRecorder) CreateFullSyncRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncRequest), arg0, arg1, arg2, arg3) +} + +// CreateFullSyncResponse mocks base method. +func (m *MockSyncClient) CreateFullSyncResponse(arg0 tree.ObjectTree, arg1, arg2 []string, arg3 string) (*spacesyncproto.ObjectSyncMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateFullSyncResponse", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateFullSyncResponse indicates an expected call of CreateFullSyncResponse. +func (mr *MockSyncClientMockRecorder) CreateFullSyncResponse(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFullSyncResponse", reflect.TypeOf((*MockSyncClient)(nil).CreateFullSyncResponse), arg0, arg1, arg2, arg3) +} + +// CreateHeadUpdate mocks base method. +func (m *MockSyncClient) CreateHeadUpdate(arg0 tree.ObjectTree, arg1 []*treechangeproto.RawTreeChangeWithId) *spacesyncproto.ObjectSyncMessage { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateHeadUpdate", arg0, arg1) + ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage) + 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(arg0 string) *spacesyncproto.ObjectSyncMessage { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNewTreeRequest", arg0) + ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage) + return ret0 +} + +// CreateNewTreeRequest indicates an expected call of CreateNewTreeRequest. +func (mr *MockSyncClientMockRecorder) CreateNewTreeRequest(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNewTreeRequest", reflect.TypeOf((*MockSyncClient)(nil).CreateNewTreeRequest), arg0) +} + +// HasActiveStream mocks base method. +func (m *MockSyncClient) HasActiveStream(arg0 string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasActiveStream", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// HasActiveStream indicates an expected call of HasActiveStream. +func (mr *MockSyncClientMockRecorder) HasActiveStream(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasActiveStream", reflect.TypeOf((*MockSyncClient)(nil).HasActiveStream), arg0) +} + +// SendAsync mocks base method. +func (m *MockSyncClient) SendAsync(arg0 []string, arg1 *spacesyncproto.ObjectSyncMessage) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendAsync", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendAsync indicates an expected call of SendAsync. +func (mr *MockSyncClientMockRecorder) SendAsync(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAsync", reflect.TypeOf((*MockSyncClient)(nil).SendAsync), arg0, arg1) +} + +// SendSync mocks base method. +func (m *MockSyncClient) SendSync(arg0 string, arg1 *spacesyncproto.ObjectSyncMessage) (*spacesyncproto.ObjectSyncMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendSync", arg0, arg1) + ret0, _ := ret[0].(*spacesyncproto.ObjectSyncMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SendSync indicates an expected call of SendSync. +func (mr *MockSyncClientMockRecorder) SendSync(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendSync", reflect.TypeOf((*MockSyncClient)(nil).SendSync), arg0, arg1) +} diff --git a/common/commonspace/syncservice/requestfactory.go b/common/commonspace/syncservice/requestfactory.go new file mode 100644 index 00000000..1237653b --- /dev/null +++ b/common/commonspace/syncservice/requestfactory.go @@ -0,0 +1,73 @@ +package syncservice + +import ( + "fmt" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice" +) + +type RequestFactory interface { + CreateHeadUpdate(t tree.ObjectTree, added []*treechangeproto.RawTreeChangeWithId) (msg *spacesyncproto.ObjectSyncMessage) + CreateNewTreeRequest(id string) (msg *spacesyncproto.ObjectSyncMessage) + CreateFullSyncRequest(t tree.ObjectTree, theirHeads, theirSnapshotPath []string, trackingId string) (req *spacesyncproto.ObjectSyncMessage, err error) + CreateFullSyncResponse(t tree.ObjectTree, theirHeads, theirSnapshotPath []string, trackingId string) (*spacesyncproto.ObjectSyncMessage, error) +} + +func newRequestFactory() RequestFactory { + return &requestFactory{} +} + +type requestFactory struct{} + +func (r *requestFactory) CreateHeadUpdate(t tree.ObjectTree, added []*treechangeproto.RawTreeChangeWithId) (msg *spacesyncproto.ObjectSyncMessage) { + return spacesyncproto.WrapHeadUpdate(&spacesyncproto.ObjectHeadUpdate{ + Heads: t.Heads(), + Changes: added, + SnapshotPath: t.SnapshotPath(), + }, t.Header(), t.ID(), "") +} + +func (r *requestFactory) CreateNewTreeRequest(id string) (msg *spacesyncproto.ObjectSyncMessage) { + return spacesyncproto.WrapFullRequest(&spacesyncproto.ObjectFullSyncRequest{}, nil, id, "") +} + +func (r *requestFactory) CreateFullSyncRequest(t tree.ObjectTree, theirHeads, theirSnapshotPath []string, trackingId string) (msg *spacesyncproto.ObjectSyncMessage, err error) { + req := &spacesyncproto.ObjectFullSyncRequest{} + if t == nil { + return nil, fmt.Errorf("tree should not be empty") + } + + req.Heads = t.Heads() + req.SnapshotPath = t.SnapshotPath() + + var changesAfterSnapshot []*treechangeproto.RawTreeChangeWithId + changesAfterSnapshot, err = t.ChangesAfterCommonSnapshot(theirSnapshotPath, theirHeads) + if err != nil { + return + } + + req.Changes = changesAfterSnapshot + msg = spacesyncproto.WrapFullRequest(req, t.Header(), t.ID(), trackingId) + return +} + +func (r *requestFactory) CreateFullSyncResponse(t tree.ObjectTree, theirHeads, theirSnapshotPath []string, trackingId string) (msg *spacesyncproto.ObjectSyncMessage, err error) { + resp := &spacesyncproto.ObjectFullSyncResponse{ + Heads: t.Heads(), + SnapshotPath: t.SnapshotPath(), + } + if slice.UnsortedEquals(theirHeads, t.Heads()) { + msg = spacesyncproto.WrapFullResponse(resp, t.Header(), t.ID(), trackingId) + return + } + + ourChanges, err := t.ChangesAfterCommonSnapshot(theirSnapshotPath, theirHeads) + if err != nil { + return + } + resp.Changes = ourChanges + msg = spacesyncproto.WrapFullResponse(resp, t.Header(), t.ID(), trackingId) + return +} diff --git a/common/commonspace/syncservice/streampool.go b/common/commonspace/syncservice/streampool.go index 5eb97170..bf457374 100644 --- a/common/commonspace/syncservice/streampool.go +++ b/common/commonspace/syncservice/streampool.go @@ -18,16 +18,16 @@ const maxSimultaneousOperationsPerStream = 10 // StreamPool can be made generic to work with different streams type StreamPool interface { - SyncClient + Sender AddAndReadStreamSync(stream spacesyncproto.SpaceStream) (err error) AddAndReadStreamAsync(stream spacesyncproto.SpaceStream) HasActiveStream(peerId string) bool Close() (err error) } -type SyncClient interface { +type Sender interface { SendSync(peerId string, message *spacesyncproto.ObjectSyncMessage) (reply *spacesyncproto.ObjectSyncMessage, err error) - SendAsync(peerId string, message *spacesyncproto.ObjectSyncMessage) (err error) + SendAsync(peers []string, message *spacesyncproto.ObjectSyncMessage) (err error) BroadcastAsync(message *spacesyncproto.ObjectSyncMessage) (err error) } @@ -56,6 +56,8 @@ func newStreamPool(messageHandler MessageHandler) StreamPool { } func (s *streamPool) HasActiveStream(peerId string) (res bool) { + s.Lock() + defer s.Unlock() _, err := s.getOrDeleteStream(peerId) return err == nil } @@ -73,7 +75,7 @@ func (s *streamPool) SendSync( s.waiters[msg.TrackingId] = waiter s.waitersMx.Unlock() - err = s.SendAsync(peerId, msg) + err = s.SendAsync([]string{peerId}, msg) if err != nil { return } @@ -82,18 +84,31 @@ func (s *streamPool) SendSync( return } -func (s *streamPool) SendAsync(peerId string, message *spacesyncproto.ObjectSyncMessage) (err error) { - stream, err := s.getOrDeleteStream(peerId) - if err != nil { - return +func (s *streamPool) SendAsync(peers []string, message *spacesyncproto.ObjectSyncMessage) (err error) { + getStreams := func() (streams []spacesyncproto.SpaceStream) { + for _, pId := range peers { + stream, err := s.getOrDeleteStream(pId) + if err != nil { + continue + } + streams = append(streams, stream) + } + return streams } - return stream.Send(message) + s.Lock() + streams := getStreams() + s.Unlock() + + for _, s := range streams { + if len(peers) == 1 { + err = s.Send(message) + } + } + return err } func (s *streamPool) getOrDeleteStream(id string) (stream spacesyncproto.SpaceStream, err error) { - s.Lock() - defer s.Unlock() stream, exists := s.peerStreams[id] if !exists { err = ErrEmptyPeer diff --git a/common/commonspace/syncservice/syncclient.go b/common/commonspace/syncservice/syncclient.go new file mode 100644 index 00000000..12c25164 --- /dev/null +++ b/common/commonspace/syncservice/syncclient.go @@ -0,0 +1,49 @@ +package syncservice + +import ( + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf" +) + +type SyncClient interface { + StreamPool + RequestFactory + BroadcastAsyncOrSendResponsible(message *spacesyncproto.ObjectSyncMessage) (err error) +} + +type syncClient struct { + StreamPool + RequestFactory + spaceId string + notifiable HeadNotifiable + configuration nodeconf.Configuration +} + +func newSyncClient(spaceId string, pool StreamPool, notifiable HeadNotifiable, factory RequestFactory, configuration nodeconf.Configuration) SyncClient { + return &syncClient{ + StreamPool: pool, + RequestFactory: factory, + notifiable: notifiable, + configuration: configuration, + spaceId: spaceId, + } +} + +func (s *syncClient) BroadcastAsync(message *spacesyncproto.ObjectSyncMessage) (err error) { + s.notifyIfNeeded(message) + return s.BroadcastAsync(message) +} + +func (s *syncClient) BroadcastAsyncOrSendResponsible(message *spacesyncproto.ObjectSyncMessage) (err error) { + if s.configuration.IsResponsible(s.spaceId) { + return s.SendAsync(s.configuration.NodeIds(s.spaceId), message) + } + return s.BroadcastAsync(message) +} + +func (s *syncClient) notifyIfNeeded(message *spacesyncproto.ObjectSyncMessage) { + if message.GetContent().GetHeadUpdate() != nil { + update := message.GetContent().GetHeadUpdate() + s.notifiable.UpdateHeads(message.TreeId, update.Heads) + } +} diff --git a/common/commonspace/syncservice/synchandler.go b/common/commonspace/syncservice/synchandler.go index 3a0da9fd..d74645c4 100644 --- a/common/commonspace/syncservice/synchandler.go +++ b/common/commonspace/syncservice/synchandler.go @@ -5,7 +5,6 @@ import ( "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree" - "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice" ) @@ -31,24 +30,24 @@ func (s *syncHandler) HandleMessage(ctx context.Context, senderId string, msg *s content := msg.GetContent() switch { case content.GetFullSyncRequest() != nil: - return s.HandleFullSyncRequest(ctx, senderId, content.GetFullSyncRequest(), msg) + return s.handleFullSyncRequest(ctx, senderId, content.GetFullSyncRequest(), msg) case content.GetFullSyncResponse() != nil: - return s.HandleFullSyncResponse(ctx, senderId, content.GetFullSyncResponse(), msg) + return s.handleFullSyncResponse(ctx, senderId, content.GetFullSyncResponse(), msg) case content.GetHeadUpdate() != nil: - return s.HandleHeadUpdate(ctx, senderId, content.GetHeadUpdate(), msg) + return s.handleHeadUpdate(ctx, senderId, content.GetHeadUpdate(), msg) } return nil } -func (s *syncHandler) HandleHeadUpdate( +func (s *syncHandler) handleHeadUpdate( ctx context.Context, senderId string, update *spacesyncproto.ObjectHeadUpdate, msg *spacesyncproto.ObjectSyncMessage) (err error) { var ( - fullRequest *spacesyncproto.ObjectFullSyncRequest - result tree.AddResult + fullRequest *spacesyncproto.ObjectSyncMessage + isEmptyUpdate = len(update.Changes) == 0 ) res, err := s.treeCache.GetTree(ctx, s.spaceId, msg.TreeId) if err != nil { @@ -61,44 +60,51 @@ func (s *syncHandler) HandleHeadUpdate( defer res.Release() defer objTree.Unlock() - if slice.UnsortedEquals(update.Heads, objTree.Heads()) { + // isEmptyUpdate is sent when the tree is brought up from cache + if isEmptyUpdate { + if slice.UnsortedEquals(objTree.Heads(), update.Heads) { + return nil + } + // we need to sync in any case + fullRequest, err = s.syncClient.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath, msg.TrackingId) + return err + } + + if s.alreadyHasHeads(objTree, update.Heads) { return nil } - result, err = objTree.AddRawChanges(ctx, update.Changes...) + _, err = objTree.AddRawChanges(ctx, update.Changes...) if err != nil { return err } - // if we couldn't add all the changes - if len(update.Changes) != len(result.Added) { - fullRequest, err = s.prepareFullSyncRequest(objTree, update) - if err != nil { - return err - } + if s.alreadyHasHeads(objTree, update.Heads) { + return nil } - return nil + + fullRequest, err = s.syncClient.CreateFullSyncRequest(objTree, update.Heads, update.SnapshotPath, msg.TrackingId) + return err }() if fullRequest != nil { - return s.syncClient.SendAsync(senderId, - spacesyncproto.WrapFullRequest(fullRequest, msg.RootChange, msg.TreeId, msg.TrackingId)) + return s.syncClient.SendAsync([]string{senderId}, fullRequest) } return } -func (s *syncHandler) HandleFullSyncRequest( +func (s *syncHandler) handleFullSyncRequest( ctx context.Context, senderId string, request *spacesyncproto.ObjectFullSyncRequest, msg *spacesyncproto.ObjectSyncMessage) (err error) { var ( - fullResponse *spacesyncproto.ObjectFullSyncResponse + fullResponse *spacesyncproto.ObjectSyncMessage header = msg.RootChange ) defer func() { if err != nil { - s.syncClient.SendAsync(senderId, spacesyncproto.WrapError(err, header, msg.TreeId, msg.TrackingId)) + s.syncClient.SendAsync([]string{senderId}, spacesyncproto.WrapError(err, header, msg.TreeId, msg.TrackingId)) } }() @@ -117,23 +123,24 @@ func (s *syncHandler) HandleFullSyncRequest( header = objTree.Header() } - _, err = objTree.AddRawChanges(ctx, request.Changes...) - if err != nil { - return err + if len(request.Changes) != 0 && !s.alreadyHasHeads(objTree, request.Heads) { + _, err = objTree.AddRawChanges(ctx, request.Changes...) + if err != nil { + return err + } } - fullResponse, err = s.prepareFullSyncResponse(request.SnapshotPath, request.Heads, objTree) + fullResponse, err = s.syncClient.CreateFullSyncResponse(objTree, request.Heads, request.SnapshotPath, msg.TrackingId) return err }() if err != nil { return } - return s.syncClient.SendAsync(senderId, - spacesyncproto.WrapFullResponse(fullResponse, header, msg.TreeId, msg.TrackingId)) + return s.syncClient.SendAsync([]string{senderId}, fullResponse) } -func (s *syncHandler) HandleFullSyncResponse( +func (s *syncHandler) handleFullSyncResponse( ctx context.Context, senderId string, response *spacesyncproto.ObjectFullSyncResponse, @@ -149,8 +156,7 @@ func (s *syncHandler) HandleFullSyncResponse( defer res.Release() defer objTree.Unlock() - // if we already have the heads for whatever reason - if slice.UnsortedEquals(response.Heads, objTree.Heads()) { + if s.alreadyHasHeads(objTree, response.Heads) { return nil } @@ -161,39 +167,6 @@ func (s *syncHandler) HandleFullSyncResponse( return } -func (s *syncHandler) prepareFullSyncRequest( - t tree.ObjectTree, - update *spacesyncproto.ObjectHeadUpdate) (req *spacesyncproto.ObjectFullSyncRequest, err error) { - req = &spacesyncproto.ObjectFullSyncRequest{ - Heads: t.Heads(), - SnapshotPath: t.SnapshotPath(), - } - if len(update.Changes) != 0 { - var changesAfterSnapshot []*treechangeproto.RawTreeChangeWithId - changesAfterSnapshot, err = t.ChangesAfterCommonSnapshot(update.SnapshotPath, update.Heads) - if err != nil { - return - } - req.Changes = changesAfterSnapshot - } - return &spacesyncproto.ObjectFullSyncRequest{ - Heads: t.Heads(), - SnapshotPath: t.SnapshotPath(), - }, nil -} - -func (s *syncHandler) prepareFullSyncResponse( - theirPath, - theirHeads []string, - t tree.ObjectTree) (*spacesyncproto.ObjectFullSyncResponse, error) { - ourChanges, err := t.ChangesAfterCommonSnapshot(theirPath, theirHeads) - if err != nil { - return nil, err - } - - return &spacesyncproto.ObjectFullSyncResponse{ - Heads: t.Heads(), - Changes: ourChanges, - SnapshotPath: t.SnapshotPath(), - }, nil +func (s *syncHandler) alreadyHasHeads(t tree.ObjectTree, heads []string) bool { + return slice.UnsortedEquals(t.Heads(), heads) || t.HasChanges(heads...) } diff --git a/common/commonspace/syncservice/synchandler_test.go b/common/commonspace/syncservice/synchandler_test.go new file mode 100644 index 00000000..3b16725e --- /dev/null +++ b/common/commonspace/syncservice/synchandler_test.go @@ -0,0 +1,397 @@ +package syncservice + +import ( + "context" + "fmt" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/cache/mock_cache" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice/mock_syncservice" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree" + mock_tree "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree/mock_objecttree" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "sync" + "testing" +) + +type treeContainer struct { + objTree tree.ObjectTree +} + +func (t treeContainer) Tree() tree.ObjectTree { + return t.objTree +} + +type testObjTreeMock struct { + *mock_tree.MockObjectTree + m sync.Mutex +} + +func newTestObjMock(mockTree *mock_tree.MockObjectTree) *testObjTreeMock { + return &testObjTreeMock{ + MockObjectTree: mockTree, + } +} + +func (t *testObjTreeMock) Lock() { + t.m.Lock() +} + +func (t *testObjTreeMock) Unlock() { + t.m.Unlock() +} + +func TestSyncHandler_HandleHeadUpdate(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := context.Background() + spaceId := "spaceId" + cacheMock := mock_cache.NewMockTreeCache(ctrl) + syncClientMock := mock_syncservice.NewMockSyncClient(ctrl) + objectTreeMock := newTestObjMock(mock_tree.NewMockObjectTree(ctrl)) + + syncHandler := newSyncHandler(spaceId, cacheMock, syncClientMock) + t.Run("head update non empty all heads added", func(t *testing.T) { + treeId := "treeId" + senderId := "senderId" + chWithId := &treechangeproto.RawTreeChangeWithId{} + headUpdate := &spacesyncproto.ObjectHeadUpdate{ + Heads: []string{"h1"}, + Changes: []*treechangeproto.RawTreeChangeWithId{chWithId}, + SnapshotPath: []string{"h1"}, + } + msg := spacesyncproto.WrapHeadUpdate(headUpdate, chWithId, treeId, "") + cacheMock.EXPECT(). + GetTree(gomock.Any(), spaceId, treeId). + Return(cache.TreeResult{ + Release: func() {}, + TreeContainer: treeContainer{objectTreeMock}, + }, nil) + objectTreeMock.EXPECT(). + Heads(). + Return([]string{"h2"}) + objectTreeMock.EXPECT(). + HasChanges(gomock.Eq([]string{"h1"})). + Return(false) + objectTreeMock.EXPECT(). + AddRawChanges(gomock.Any(), gomock.Eq([]*treechangeproto.RawTreeChangeWithId{chWithId})). + Return(tree.AddResult{}, nil) + objectTreeMock.EXPECT(). + Heads(). + Return([]string{"h2", "h1"}) + objectTreeMock.EXPECT(). + HasChanges(gomock.Eq([]string{"h1"})). + Return(true) + + err := syncHandler.HandleMessage(ctx, senderId, msg) + require.NoError(t, err) + }) + + t.Run("head update non empty heads not added", func(t *testing.T) { + treeId := "treeId" + senderId := "senderId" + chWithId := &treechangeproto.RawTreeChangeWithId{} + headUpdate := &spacesyncproto.ObjectHeadUpdate{ + Heads: []string{"h1"}, + Changes: []*treechangeproto.RawTreeChangeWithId{chWithId}, + SnapshotPath: []string{"h1"}, + } + fullRequest := &spacesyncproto.ObjectSyncMessage{} + msg := spacesyncproto.WrapHeadUpdate(headUpdate, chWithId, treeId, "") + cacheMock.EXPECT(). + GetTree(gomock.Any(), spaceId, treeId). + Return(cache.TreeResult{ + Release: func() {}, + TreeContainer: treeContainer{objectTreeMock}, + }, nil) + objectTreeMock.EXPECT(). + Heads(). + Return([]string{"h2"}) + objectTreeMock.EXPECT(). + HasChanges(gomock.Eq([]string{"h1"})). + Return(false) + objectTreeMock.EXPECT(). + AddRawChanges(gomock.Any(), gomock.Eq([]*treechangeproto.RawTreeChangeWithId{chWithId})). + Return(tree.AddResult{}, nil) + objectTreeMock.EXPECT(). + Heads(). + Return([]string{"h2"}) + objectTreeMock.EXPECT(). + HasChanges(gomock.Eq([]string{"h1"})). + Return(false) + syncClientMock.EXPECT(). + CreateFullSyncRequest(gomock.Eq(objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"}), gomock.Eq("")). + Return(fullRequest, nil) + + syncClientMock.EXPECT().SendAsync(gomock.Eq([]string{senderId}), gomock.Eq(fullRequest)) + err := syncHandler.HandleMessage(ctx, senderId, msg) + require.NoError(t, err) + }) + + t.Run("head update non empty equal heads", func(t *testing.T) { + treeId := "treeId" + senderId := "senderId" + chWithId := &treechangeproto.RawTreeChangeWithId{} + headUpdate := &spacesyncproto.ObjectHeadUpdate{ + Heads: []string{"h1"}, + Changes: []*treechangeproto.RawTreeChangeWithId{chWithId}, + SnapshotPath: []string{"h1"}, + } + msg := spacesyncproto.WrapHeadUpdate(headUpdate, chWithId, treeId, "") + cacheMock.EXPECT(). + GetTree(gomock.Any(), spaceId, treeId). + Return(cache.TreeResult{ + Release: func() {}, + TreeContainer: treeContainer{objectTreeMock}, + }, nil) + objectTreeMock.EXPECT(). + Heads(). + Return([]string{"h1"}) + + err := syncHandler.HandleMessage(ctx, senderId, msg) + require.NoError(t, err) + }) + + t.Run("head update empty", func(t *testing.T) { + treeId := "treeId" + senderId := "senderId" + chWithId := &treechangeproto.RawTreeChangeWithId{} + headUpdate := &spacesyncproto.ObjectHeadUpdate{ + Heads: []string{"h1"}, + Changes: nil, + SnapshotPath: []string{"h1"}, + } + fullRequest := &spacesyncproto.ObjectSyncMessage{} + msg := spacesyncproto.WrapHeadUpdate(headUpdate, chWithId, treeId, "") + cacheMock.EXPECT(). + GetTree(gomock.Any(), spaceId, treeId). + Return(cache.TreeResult{ + Release: func() {}, + TreeContainer: treeContainer{objectTreeMock}, + }, nil) + objectTreeMock.EXPECT(). + Heads(). + Return([]string{"h2"}) + syncClientMock.EXPECT(). + CreateFullSyncRequest(gomock.Eq(objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"}), gomock.Eq("")). + Return(fullRequest, nil) + + syncClientMock.EXPECT().SendAsync(gomock.Eq([]string{senderId}), gomock.Eq(fullRequest)) + err := syncHandler.HandleMessage(ctx, senderId, msg) + require.NoError(t, err) + }) + + t.Run("head update empty equal heads", func(t *testing.T) { + treeId := "treeId" + senderId := "senderId" + chWithId := &treechangeproto.RawTreeChangeWithId{} + headUpdate := &spacesyncproto.ObjectHeadUpdate{ + Heads: []string{"h1"}, + Changes: nil, + SnapshotPath: []string{"h1"}, + } + msg := spacesyncproto.WrapHeadUpdate(headUpdate, chWithId, treeId, "") + cacheMock.EXPECT(). + GetTree(gomock.Any(), spaceId, treeId). + Return(cache.TreeResult{ + Release: func() {}, + TreeContainer: treeContainer{objectTreeMock}, + }, nil) + + objectTreeMock.EXPECT(). + Heads(). + Return([]string{"h1"}) + + err := syncHandler.HandleMessage(ctx, senderId, msg) + require.NoError(t, err) + }) +} + +func TestSyncHandler_HandleFullSyncRequest(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := context.Background() + spaceId := "spaceId" + cacheMock := mock_cache.NewMockTreeCache(ctrl) + syncClientMock := mock_syncservice.NewMockSyncClient(ctrl) + objectTreeMock := newTestObjMock(mock_tree.NewMockObjectTree(ctrl)) + + syncHandler := newSyncHandler(spaceId, cacheMock, syncClientMock) + t.Run("full sync request with change", func(t *testing.T) { + treeId := "treeId" + senderId := "senderId" + chWithId := &treechangeproto.RawTreeChangeWithId{} + msg := spacesyncproto.WrapFullRequest(&spacesyncproto.ObjectFullSyncRequest{ + Heads: []string{"h1"}, + Changes: []*treechangeproto.RawTreeChangeWithId{chWithId}, + SnapshotPath: []string{"h1"}, + }, chWithId, treeId, "") + fullRequest := &spacesyncproto.ObjectSyncMessage{} + + cacheMock.EXPECT(). + GetTree(gomock.Any(), spaceId, treeId). + Return(cache.TreeResult{ + Release: func() {}, + TreeContainer: treeContainer{objectTreeMock}, + }, nil) + objectTreeMock.EXPECT(). + Heads(). + Return([]string{"h2"}) + objectTreeMock.EXPECT(). + HasChanges(gomock.Eq([]string{"h1"})). + Return(false) + objectTreeMock.EXPECT(). + AddRawChanges(gomock.Any(), gomock.Eq([]*treechangeproto.RawTreeChangeWithId{chWithId})). + Return(tree.AddResult{}, nil) + syncClientMock.EXPECT(). + CreateFullSyncResponse(gomock.Eq(objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"}), gomock.Eq("")). + Return(fullRequest, nil) + + syncClientMock.EXPECT().SendAsync(gomock.Eq([]string{senderId}), gomock.Eq(fullRequest)) + err := syncHandler.HandleMessage(ctx, senderId, msg) + require.NoError(t, err) + }) + + t.Run("full sync request with change same heads", func(t *testing.T) { + treeId := "treeId" + senderId := "senderId" + chWithId := &treechangeproto.RawTreeChangeWithId{} + msg := spacesyncproto.WrapFullRequest(&spacesyncproto.ObjectFullSyncRequest{ + Heads: []string{"h2"}, + Changes: []*treechangeproto.RawTreeChangeWithId{chWithId}, + SnapshotPath: []string{"h2"}, + }, chWithId, treeId, "") + fullRequest := &spacesyncproto.ObjectSyncMessage{} + + cacheMock.EXPECT(). + GetTree(gomock.Any(), spaceId, treeId). + Return(cache.TreeResult{ + Release: func() {}, + TreeContainer: treeContainer{objectTreeMock}, + }, nil) + objectTreeMock.EXPECT(). + Heads(). + Return([]string{"h2"}) + syncClientMock.EXPECT(). + CreateFullSyncResponse(gomock.Eq(objectTreeMock), gomock.Eq([]string{"h2"}), gomock.Eq([]string{"h2"}), gomock.Eq("")). + Return(fullRequest, nil) + + syncClientMock.EXPECT().SendAsync(gomock.Eq([]string{senderId}), gomock.Eq(fullRequest)) + err := syncHandler.HandleMessage(ctx, senderId, msg) + require.NoError(t, err) + }) + + t.Run("full sync request without change", func(t *testing.T) { + treeId := "treeId" + senderId := "senderId" + chWithId := &treechangeproto.RawTreeChangeWithId{} + msg := spacesyncproto.WrapFullRequest(&spacesyncproto.ObjectFullSyncRequest{ + Heads: []string{"h1"}, + SnapshotPath: []string{"h1"}, + }, chWithId, treeId, "") + fullRequest := &spacesyncproto.ObjectSyncMessage{} + + cacheMock.EXPECT(). + GetTree(gomock.Any(), spaceId, treeId). + Return(cache.TreeResult{ + Release: func() {}, + TreeContainer: treeContainer{objectTreeMock}, + }, nil) + syncClientMock.EXPECT(). + CreateFullSyncResponse(gomock.Eq(objectTreeMock), gomock.Eq([]string{"h1"}), gomock.Eq([]string{"h1"}), gomock.Eq("")). + Return(fullRequest, nil) + + syncClientMock.EXPECT().SendAsync(gomock.Eq([]string{senderId}), gomock.Eq(fullRequest)) + err := syncHandler.HandleMessage(ctx, senderId, msg) + require.NoError(t, err) + }) + + t.Run("full sync request with get tree error", func(t *testing.T) { + treeId := "treeId" + senderId := "senderId" + chWithId := &treechangeproto.RawTreeChangeWithId{} + msg := spacesyncproto.WrapFullRequest(&spacesyncproto.ObjectFullSyncRequest{ + Heads: []string{"h1"}, + SnapshotPath: []string{"h1"}, + }, chWithId, treeId, "") + + cacheMock.EXPECT(). + GetTree(gomock.Any(), spaceId, treeId). + Return(cache.TreeResult{}, fmt.Errorf("some")) + + syncClientMock.EXPECT(). + SendAsync(gomock.Eq([]string{senderId}), gomock.Any()) + err := syncHandler.HandleMessage(ctx, senderId, msg) + require.Error(t, err) + }) +} + +func TestSyncHandler_HandleFullSyncResponse(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ctx := context.Background() + spaceId := "spaceId" + cacheMock := mock_cache.NewMockTreeCache(ctrl) + syncClientMock := mock_syncservice.NewMockSyncClient(ctrl) + objectTreeMock := newTestObjMock(mock_tree.NewMockObjectTree(ctrl)) + + syncHandler := newSyncHandler(spaceId, cacheMock, syncClientMock) + t.Run("full sync response with change", func(t *testing.T) { + treeId := "treeId" + senderId := "senderId" + chWithId := &treechangeproto.RawTreeChangeWithId{} + msg := spacesyncproto.WrapFullResponse(&spacesyncproto.ObjectFullSyncResponse{ + Heads: []string{"h1"}, + Changes: []*treechangeproto.RawTreeChangeWithId{chWithId}, + SnapshotPath: []string{"h1"}, + }, chWithId, treeId, "") + + cacheMock.EXPECT(). + GetTree(gomock.Any(), spaceId, treeId). + Return(cache.TreeResult{ + Release: func() {}, + TreeContainer: treeContainer{objectTreeMock}, + }, nil) + objectTreeMock.EXPECT(). + Heads(). + Return([]string{"h2"}) + objectTreeMock.EXPECT(). + HasChanges(gomock.Eq([]string{"h1"})). + Return(false) + objectTreeMock.EXPECT(). + AddRawChanges(gomock.Any(), gomock.Eq([]*treechangeproto.RawTreeChangeWithId{chWithId})). + Return(tree.AddResult{}, nil) + + err := syncHandler.HandleMessage(ctx, senderId, msg) + require.NoError(t, err) + }) + + t.Run("full sync response same heads", func(t *testing.T) { + treeId := "treeId" + senderId := "senderId" + chWithId := &treechangeproto.RawTreeChangeWithId{} + msg := spacesyncproto.WrapFullResponse(&spacesyncproto.ObjectFullSyncResponse{ + Heads: []string{"h1"}, + Changes: []*treechangeproto.RawTreeChangeWithId{chWithId}, + SnapshotPath: []string{"h1"}, + }, chWithId, treeId, "") + + cacheMock.EXPECT(). + GetTree(gomock.Any(), spaceId, treeId). + Return(cache.TreeResult{ + Release: func() {}, + TreeContainer: treeContainer{objectTreeMock}, + }, nil) + objectTreeMock.EXPECT(). + Heads(). + Return([]string{"h1"}) + + err := syncHandler.HandleMessage(ctx, senderId, msg) + require.NoError(t, err) + }) +} diff --git a/common/commonspace/syncservice/syncservice.go b/common/commonspace/syncservice/syncservice.go index 59aa3621..ae4173b2 100644 --- a/common/commonspace/syncservice/syncservice.go +++ b/common/commonspace/syncservice/syncservice.go @@ -1,3 +1,4 @@ +//go:generate mockgen -destination mock_syncservice/mock_syncservice.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice SyncClient package syncservice import ( @@ -7,19 +8,13 @@ import ( "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/rpc/rpcerr" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf" - "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto" "time" ) var log = logger.NewNamed("syncservice").Sugar() type SyncService interface { - NotifyHeadUpdate( - ctx context.Context, - treeId string, - root *treechangeproto.RawTreeChangeWithId, - update *spacesyncproto.ObjectHeadUpdate) (err error) - StreamPool() StreamPool + SyncClient() SyncClient Init() Close() (err error) @@ -34,36 +29,44 @@ const respPeersStreamCheckInterval = time.Second * 10 type syncService struct { spaceId string - syncHandler SyncHandler - streamPool StreamPool - headNotifiable HeadNotifiable - configuration nodeconf.Configuration + syncClient SyncClient + clientFactory spacesyncproto.ClientFactory streamLoopCtx context.Context stopStreamLoop context.CancelFunc + connector nodeconf.ConfConnector streamLoopDone chan struct{} } -func NewSyncService(spaceId string, headNotifiable HeadNotifiable, cache cache.TreeCache, configuration nodeconf.Configuration) SyncService { +func NewSyncService( + spaceId string, + headNotifiable HeadNotifiable, + cache cache.TreeCache, + configuration nodeconf.Configuration, + confConnector nodeconf.ConfConnector) SyncService { var syncHandler SyncHandler streamPool := newStreamPool(func(ctx context.Context, senderId string, message *spacesyncproto.ObjectSyncMessage) (err error) { return syncHandler.HandleMessage(ctx, senderId, message) }) - syncHandler = newSyncHandler(spaceId, cache, streamPool) - return newSyncService(spaceId, headNotifiable, syncHandler, streamPool, configuration) + factory := newRequestFactory() + syncClient := newSyncClient(spaceId, streamPool, headNotifiable, factory, configuration) + syncHandler = newSyncHandler(spaceId, cache, syncClient) + return newSyncService( + spaceId, + syncClient, + spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceClient), + confConnector) } func newSyncService( spaceId string, - headNotifiable HeadNotifiable, - syncHandler SyncHandler, - streamPool StreamPool, - configuration nodeconf.Configuration) *syncService { + syncClient SyncClient, + clientFactory spacesyncproto.ClientFactory, + connector nodeconf.ConfConnector) *syncService { return &syncService{ - syncHandler: syncHandler, - streamPool: streamPool, - headNotifiable: headNotifiable, - configuration: configuration, + syncClient: syncClient, + connector: connector, + clientFactory: clientFactory, spaceId: spaceId, streamLoopDone: make(chan struct{}), } @@ -77,32 +80,24 @@ func (s *syncService) Init() { func (s *syncService) Close() (err error) { s.stopStreamLoop() <-s.streamLoopDone - return s.streamPool.Close() -} - -func (s *syncService) NotifyHeadUpdate( - ctx context.Context, - treeId string, - header *treechangeproto.RawTreeChangeWithId, - update *spacesyncproto.ObjectHeadUpdate) (err error) { - s.headNotifiable.UpdateHeads(treeId, update.Heads) - return s.streamPool.BroadcastAsync(spacesyncproto.WrapHeadUpdate(update, header, treeId, "")) + return s.syncClient.Close() } func (s *syncService) responsibleStreamCheckLoop(ctx context.Context) { defer close(s.streamLoopDone) checkResponsiblePeers := func() { - respPeers, err := s.configuration.ResponsiblePeers(ctx, s.spaceId) + respPeers, err := s.connector.DialResponsiblePeers(ctx, s.spaceId) if err != nil { return } for _, peer := range respPeers { - if s.streamPool.HasActiveStream(peer.Id()) { + if s.syncClient.HasActiveStream(peer.Id()) { continue } - cl := spacesyncproto.NewDRPCSpaceClient(peer) - stream, err := cl.Stream(ctx) + stream, err := s.clientFactory.Client(peer).Stream(ctx) if err != nil { + err = rpcerr.Unwrap(err) + log.With("spaceId", s.spaceId).Errorf("failed to open stream: %v", err) // so here probably the request is failed because there is no such space, // but diffService should handle such cases by sending pushSpace continue @@ -111,10 +106,10 @@ func (s *syncService) responsibleStreamCheckLoop(ctx context.Context) { err = stream.Send(&spacesyncproto.ObjectSyncMessage{SpaceId: s.spaceId}) if err != nil { err = rpcerr.Unwrap(err) - log.With("spaceId", s.spaceId).Errorf("failed to open stream: %v", err) + log.With("spaceId", s.spaceId).Errorf("failed to send first message to stream: %v", err) continue } - s.streamPool.AddAndReadStreamAsync(stream) + s.syncClient.AddAndReadStreamAsync(stream) } } @@ -131,6 +126,6 @@ func (s *syncService) responsibleStreamCheckLoop(ctx context.Context) { } } -func (s *syncService) StreamPool() StreamPool { - return s.streamPool +func (s *syncService) SyncClient() SyncClient { + return s.syncClient } diff --git a/common/commonspace/synctree/synctree.go b/common/commonspace/synctree/synctree.go index f3075825..14e05a3e 100644 --- a/common/commonspace/synctree/synctree.go +++ b/common/commonspace/synctree/synctree.go @@ -2,7 +2,6 @@ package synctree import ( "context" - "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree/updatelistener" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list" @@ -14,105 +13,101 @@ import ( // SyncTree sends head updates to sync service and also sends new changes to update listener type SyncTree struct { tree.ObjectTree - syncService syncservice.SyncService - listener updatelistener.UpdateListener + syncClient syncservice.SyncClient + listener updatelistener.UpdateListener } +var createDerivedObjectTree = tree.CreateDerivedObjectTree +var createObjectTree = tree.CreateObjectTree +var buildObjectTree = tree.BuildObjectTree + func DeriveSyncTree( ctx context.Context, payload tree.ObjectTreeCreatePayload, - syncService syncservice.SyncService, + syncClient syncservice.SyncClient, listener updatelistener.UpdateListener, aclList list.ACLList, createStorage storage.TreeStorageCreatorFunc) (t tree.ObjectTree, err error) { - t, err = tree.CreateDerivedObjectTree(payload, aclList, createStorage) + t, err = createDerivedObjectTree(payload, aclList, createStorage) if err != nil { return } t = &SyncTree{ - ObjectTree: t, - syncService: syncService, - listener: listener, + ObjectTree: t, + syncClient: syncClient, + listener: listener, } - err = syncService.NotifyHeadUpdate(ctx, t.ID(), t.Header(), &spacesyncproto.ObjectHeadUpdate{ - Heads: t.Heads(), - SnapshotPath: t.SnapshotPath(), - }) + headUpdate := syncClient.CreateHeadUpdate(t, nil) + err = syncClient.BroadcastAsync(headUpdate) return } func CreateSyncTree( ctx context.Context, payload tree.ObjectTreeCreatePayload, - syncService syncservice.SyncService, + syncClient syncservice.SyncClient, listener updatelistener.UpdateListener, aclList list.ACLList, createStorage storage.TreeStorageCreatorFunc) (t tree.ObjectTree, err error) { - t, err = tree.CreateObjectTree(payload, aclList, createStorage) + t, err = createObjectTree(payload, aclList, createStorage) if err != nil { return } t = &SyncTree{ - ObjectTree: t, - syncService: syncService, - listener: listener, + ObjectTree: t, + syncClient: syncClient, + listener: listener, } - err = syncService.NotifyHeadUpdate(ctx, t.ID(), t.Header(), &spacesyncproto.ObjectHeadUpdate{ - Heads: t.Heads(), - SnapshotPath: t.SnapshotPath(), - }) + headUpdate := syncClient.CreateHeadUpdate(t, nil) + err = syncClient.BroadcastAsync(headUpdate) return } func BuildSyncTree( ctx context.Context, - syncService syncservice.SyncService, + syncClient syncservice.SyncClient, treeStorage storage.TreeStorage, listener updatelistener.UpdateListener, aclList list.ACLList) (t tree.ObjectTree, err error) { - return buildSyncTree(ctx, syncService, treeStorage, listener, aclList) + return buildSyncTree(ctx, syncClient, treeStorage, listener, aclList) } func buildSyncTree( ctx context.Context, - syncService syncservice.SyncService, + syncClient syncservice.SyncClient, treeStorage storage.TreeStorage, listener updatelistener.UpdateListener, aclList list.ACLList) (t tree.ObjectTree, err error) { - t, err = tree.BuildObjectTree(treeStorage, aclList) + t, err = buildObjectTree(treeStorage, aclList) if err != nil { return } t = &SyncTree{ - ObjectTree: t, - syncService: syncService, - listener: listener, + ObjectTree: t, + syncClient: syncClient, + listener: listener, } - err = syncService.NotifyHeadUpdate(ctx, t.ID(), t.Header(), &spacesyncproto.ObjectHeadUpdate{ - Heads: t.Heads(), - SnapshotPath: t.SnapshotPath(), - }) + headUpdate := syncClient.CreateHeadUpdate(t, nil) + // here we will have different behaviour based on who is sending this update + err = syncClient.BroadcastAsyncOrSendResponsible(headUpdate) return } func (s *SyncTree) AddContent(ctx context.Context, content tree.SignableChangeContent) (res tree.AddResult, err error) { - res, err = s.AddContent(ctx, content) + res, err = s.ObjectTree.AddContent(ctx, content) if err != nil { return } - err = s.syncService.NotifyHeadUpdate(ctx, s.ID(), s.Header(), &spacesyncproto.ObjectHeadUpdate{ - Heads: res.Heads, - Changes: res.Added, - SnapshotPath: s.SnapshotPath(), - }) + headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added) + err = s.syncClient.BroadcastAsync(headUpdate) return } func (s *SyncTree) AddRawChanges(ctx context.Context, changes ...*treechangeproto.RawTreeChangeWithId) (res tree.AddResult, err error) { - res, err = s.AddRawChanges(ctx, changes...) + res, err = s.ObjectTree.AddRawChanges(ctx, changes...) if err != nil { return } @@ -125,11 +120,8 @@ func (s *SyncTree) AddRawChanges(ctx context.Context, changes ...*treechangeprot s.listener.Rebuild(s) } - err = s.syncService.NotifyHeadUpdate(ctx, s.ID(), s.Header(), &spacesyncproto.ObjectHeadUpdate{ - Heads: res.Heads, - Changes: res.Added, - SnapshotPath: s.SnapshotPath(), - }) + headUpdate := s.syncClient.CreateHeadUpdate(s, res.Added) + err = s.syncClient.BroadcastAsync(headUpdate) return } diff --git a/common/commonspace/synctree/synctree_test.go b/common/commonspace/synctree/synctree_test.go new file mode 100644 index 00000000..ef1bd99f --- /dev/null +++ b/common/commonspace/synctree/synctree_test.go @@ -0,0 +1,176 @@ +package synctree + +import ( + "context" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice/mock_syncservice" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree/updatelistener" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree/updatelistener/mock_updatelistener" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list/mock_list" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage/mock_storage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree" + mock_tree "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree/mock_objecttree" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "testing" +) + +type syncTreeMatcher struct { + objTree tree.ObjectTree + client syncservice.SyncClient + listener updatelistener.UpdateListener +} + +func (s syncTreeMatcher) Matches(x interface{}) bool { + t, ok := x.(*SyncTree) + if !ok { + return false + } + return s.objTree == t.ObjectTree && t.syncClient == s.client && t.listener == s.listener +} + +func (s syncTreeMatcher) String() string { + return "" +} + +func Test_DeriveSyncTree(t *testing.T) { + ctx := context.Background() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl) + syncClientMock := mock_syncservice.NewMockSyncClient(ctrl) + aclListMock := mock_list.NewMockACLList(ctrl) + createStorage := storage.TreeStorageCreatorFunc(func(payload storage.TreeStorageCreatePayload) (storage.TreeStorage, error) { + return nil, nil + }) + objTreeMock := mock_tree.NewMockObjectTree(ctrl) + createDerivedObjectTree = func(payload tree.ObjectTreeCreatePayload, l list.ACLList, create storage.TreeStorageCreatorFunc) (objTree tree.ObjectTree, err error) { + require.Equal(t, l, aclListMock) + return objTreeMock, nil + } + headUpdate := &spacesyncproto.ObjectSyncMessage{} + syncClientMock.EXPECT().CreateHeadUpdate(syncTreeMatcher{objTreeMock, syncClientMock, updateListenerMock}, gomock.Nil()).Return(headUpdate) + syncClientMock.EXPECT().BroadcastAsync(gomock.Eq(headUpdate)).Return(nil) + + _, err := DeriveSyncTree(ctx, tree.ObjectTreeCreatePayload{}, syncClientMock, updateListenerMock, aclListMock, createStorage) + require.NoError(t, err) +} + +func Test_CreateSyncTree(t *testing.T) { + ctx := context.Background() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl) + syncClientMock := mock_syncservice.NewMockSyncClient(ctrl) + aclListMock := mock_list.NewMockACLList(ctrl) + createStorage := storage.TreeStorageCreatorFunc(func(payload storage.TreeStorageCreatePayload) (storage.TreeStorage, error) { + return nil, nil + }) + objTreeMock := mock_tree.NewMockObjectTree(ctrl) + createObjectTree = func(payload tree.ObjectTreeCreatePayload, l list.ACLList, create storage.TreeStorageCreatorFunc) (objTree tree.ObjectTree, err error) { + require.Equal(t, l, aclListMock) + return objTreeMock, nil + } + headUpdate := &spacesyncproto.ObjectSyncMessage{} + syncClientMock.EXPECT().CreateHeadUpdate(syncTreeMatcher{objTreeMock, syncClientMock, updateListenerMock}, gomock.Nil()).Return(headUpdate) + syncClientMock.EXPECT().BroadcastAsync(gomock.Eq(headUpdate)).Return(nil) + + _, err := CreateSyncTree(ctx, tree.ObjectTreeCreatePayload{}, syncClientMock, updateListenerMock, aclListMock, createStorage) + require.NoError(t, err) +} + +func Test_BuildSyncTree(t *testing.T) { + ctx := context.Background() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl) + syncClientMock := mock_syncservice.NewMockSyncClient(ctrl) + aclListMock := mock_list.NewMockACLList(ctrl) + storageMock := mock_storage.NewMockTreeStorage(ctrl) + objTreeMock := mock_tree.NewMockObjectTree(ctrl) + buildObjectTree = func(store storage.TreeStorage, l list.ACLList) (objTree tree.ObjectTree, err error) { + require.Equal(t, aclListMock, l) + require.Equal(t, store, storageMock) + return objTreeMock, nil + } + headUpdate := &spacesyncproto.ObjectSyncMessage{} + syncClientMock.EXPECT().CreateHeadUpdate(syncTreeMatcher{objTreeMock, syncClientMock, updateListenerMock}, gomock.Nil()).Return(headUpdate) + syncClientMock.EXPECT().BroadcastAsyncOrSendResponsible(gomock.Eq(headUpdate)).Return(nil) + + tr, err := BuildSyncTree(ctx, syncClientMock, storageMock, updateListenerMock, aclListMock) + require.NoError(t, err) + + t.Run("AddRawChanges update", func(t *testing.T) { + changes := []*treechangeproto.RawTreeChangeWithId{{Id: "some"}} + expectedRes := tree.AddResult{ + Added: changes, + Mode: tree.Append, + } + objTreeMock.EXPECT().AddRawChanges(gomock.Any(), gomock.Eq(changes)). + Return(expectedRes, nil) + updateListenerMock.EXPECT().Update(tr) + + syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate) + syncClientMock.EXPECT().BroadcastAsync(gomock.Eq(headUpdate)).Return(nil) + res, err := tr.AddRawChanges(ctx, changes...) + require.NoError(t, err) + require.Equal(t, expectedRes, res) + }) + + t.Run("AddRawChanges rebuild", func(t *testing.T) { + changes := []*treechangeproto.RawTreeChangeWithId{{Id: "some"}} + expectedRes := tree.AddResult{ + Added: changes, + Mode: tree.Rebuild, + } + objTreeMock.EXPECT().AddRawChanges(gomock.Any(), gomock.Eq(changes)). + Return(expectedRes, nil) + updateListenerMock.EXPECT().Rebuild(tr) + + syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate) + syncClientMock.EXPECT().BroadcastAsync(gomock.Eq(headUpdate)).Return(nil) + res, err := tr.AddRawChanges(ctx, changes...) + require.NoError(t, err) + require.Equal(t, expectedRes, res) + }) + + t.Run("AddRawChanges nothing", func(t *testing.T) { + changes := []*treechangeproto.RawTreeChangeWithId{{Id: "some"}} + expectedRes := tree.AddResult{ + Added: changes, + Mode: tree.Nothing, + } + objTreeMock.EXPECT().AddRawChanges(gomock.Any(), gomock.Eq(changes)). + Return(expectedRes, nil) + + res, err := tr.AddRawChanges(ctx, changes...) + require.NoError(t, err) + require.Equal(t, expectedRes, res) + }) + + t.Run("AddContent", func(t *testing.T) { + changes := []*treechangeproto.RawTreeChangeWithId{{Id: "some"}} + content := tree.SignableChangeContent{ + Data: []byte("abcde"), + } + expectedRes := tree.AddResult{ + Mode: tree.Append, + Added: changes, + } + objTreeMock.EXPECT().AddContent(gomock.Any(), gomock.Eq(content)). + Return(expectedRes, nil) + + syncClientMock.EXPECT().CreateHeadUpdate(gomock.Eq(tr), gomock.Eq(changes)).Return(headUpdate) + syncClientMock.EXPECT().BroadcastAsync(gomock.Eq(headUpdate)).Return(nil) + res, err := tr.AddContent(ctx, content) + require.NoError(t, err) + require.Equal(t, expectedRes, res) + }) +} diff --git a/common/commonspace/synctree/updatelistener/mock_updatelistener/mock_updatelistener.go b/common/commonspace/synctree/updatelistener/mock_updatelistener/mock_updatelistener.go new file mode 100644 index 00000000..cb88dd6a --- /dev/null +++ b/common/commonspace/synctree/updatelistener/mock_updatelistener/mock_updatelistener.go @@ -0,0 +1,59 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree/updatelistener (interfaces: UpdateListener) + +// Package mock_updatelistener is a generated GoMock package. +package mock_updatelistener + +import ( + reflect "reflect" + + tree "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree" + gomock "github.com/golang/mock/gomock" +) + +// MockUpdateListener is a mock of UpdateListener interface. +type MockUpdateListener struct { + ctrl *gomock.Controller + recorder *MockUpdateListenerMockRecorder +} + +// MockUpdateListenerMockRecorder is the mock recorder for MockUpdateListener. +type MockUpdateListenerMockRecorder struct { + mock *MockUpdateListener +} + +// NewMockUpdateListener creates a new mock instance. +func NewMockUpdateListener(ctrl *gomock.Controller) *MockUpdateListener { + mock := &MockUpdateListener{ctrl: ctrl} + mock.recorder = &MockUpdateListenerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUpdateListener) EXPECT() *MockUpdateListenerMockRecorder { + return m.recorder +} + +// Rebuild mocks base method. +func (m *MockUpdateListener) Rebuild(arg0 tree.ObjectTree) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Rebuild", arg0) +} + +// Rebuild indicates an expected call of Rebuild. +func (mr *MockUpdateListenerMockRecorder) Rebuild(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Rebuild", reflect.TypeOf((*MockUpdateListener)(nil).Rebuild), arg0) +} + +// Update mocks base method. +func (m *MockUpdateListener) Update(arg0 tree.ObjectTree) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Update", arg0) +} + +// Update indicates an expected call of Update. +func (mr *MockUpdateListenerMockRecorder) Update(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUpdateListener)(nil).Update), arg0) +} diff --git a/common/commonspace/synctree/updatelistener/updatelistener.go b/common/commonspace/synctree/updatelistener/updatelistener.go index 0a1a6659..49f6e0d5 100644 --- a/common/commonspace/synctree/updatelistener/updatelistener.go +++ b/common/commonspace/synctree/updatelistener/updatelistener.go @@ -1,3 +1,4 @@ +//go:generate mockgen -destination mock_updatelistener/mock_updatelistener.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree/updatelistener UpdateListener package updatelistener import "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree" diff --git a/common/nodeconf/confconnector.go b/common/nodeconf/confconnector.go new file mode 100644 index 00000000..e62be926 --- /dev/null +++ b/common/nodeconf/confconnector.go @@ -0,0 +1,54 @@ +package nodeconf + +import ( + "context" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/peer" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/pool" +) + +type ConfConnector interface { + GetResponsiblePeers(ctx context.Context, spaceId string) ([]peer.Peer, error) + DialResponsiblePeers(ctx context.Context, spaceId string) ([]peer.Peer, error) +} + +type confConnector struct { + conf Configuration + pool pool.Pool +} + +func NewConfConnector(conf Configuration, pool pool.Pool) ConfConnector { + return &confConnector{conf: conf, pool: pool} +} + +func (s *confConnector) GetResponsiblePeers(ctx context.Context, spaceId string) ([]peer.Peer, error) { + return s.dialOrConnect(ctx, spaceId, s.pool.Get, s.pool.GetOneOf) +} + +func (s *confConnector) DialResponsiblePeers(ctx context.Context, spaceId string) ([]peer.Peer, error) { + return s.dialOrConnect(ctx, spaceId, s.pool.Dial, s.pool.DialOneOf) +} + +func (s *confConnector) dialOrConnect( + ctx context.Context, spaceId string, + connectOne func(context.Context, string) (peer.Peer, error), + connectOneOf func(context.Context, []string) (peer.Peer, error)) (peers []peer.Peer, err error) { + allNodes := s.conf.NodeIds(spaceId) + if !s.conf.IsResponsible(spaceId) { + for _, id := range allNodes { + var p peer.Peer + p, err = connectOne(ctx, id) + if err != nil { + continue + } + peers = append(peers, p) + } + } else { + var p peer.Peer + p, err = connectOneOf(ctx, allNodes) + if err != nil { + return + } + peers = []peer.Peer{p} + } + return +} diff --git a/common/nodeconf/configuration.go b/common/nodeconf/configuration.go index 597d1018..add0647e 100644 --- a/common/nodeconf/configuration.go +++ b/common/nodeconf/configuration.go @@ -1,10 +1,7 @@ +//go:generate mockgen -destination mock_nodeconf/mock_nodeconf.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf Configuration,ConfConnector package nodeconf import ( - "context" - "fmt" - "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/peer" - "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/pool" "github.com/anytypeio/go-chash" ) @@ -15,12 +12,6 @@ func New() Service { type Configuration interface { // Id returns current nodeconf id Id() string - // AllPeers returns all peers by spaceId except current account - AllPeers(ctx context.Context, spaceId string) (peers []peer.Peer, err error) - // OnePeer returns one of peer for spaceId - OnePeer(ctx context.Context, spaceId string) (p peer.Peer, err error) - // ResponsiblePeers returns peers for the space id that are responsible for the space - ResponsiblePeers(ctx context.Context, spaceId string) (peers []peer.Peer, err error) // NodeIds returns list of peerId for given spaceId NodeIds(spaceId string) []string // IsResponsible checks if current account responsible for given spaceId @@ -30,7 +21,6 @@ type Configuration interface { type configuration struct { id string accountId string - pool pool.Pool chash chash.CHash } @@ -38,40 +28,6 @@ func (c *configuration) Id() string { return c.id } -func (c *configuration) AllPeers(ctx context.Context, spaceId string) (peers []peer.Peer, err error) { - nodeIds := c.NodeIds(spaceId) - peers = make([]peer.Peer, 0, len(nodeIds)) - for _, id := range nodeIds { - p, e := c.pool.Get(ctx, id) - if e == nil { - peers = append(peers, p) - } - } - if len(peers) == 0 { - return nil, fmt.Errorf("unable to connect to any node") - } - return -} - -func (c *configuration) ResponsiblePeers(ctx context.Context, spaceId string) (peers []peer.Peer, err error) { - if c.IsResponsible(spaceId) { - return c.AllPeers(ctx, spaceId) - } else { - var one peer.Peer - one, err = c.OnePeer(ctx, spaceId) - if err != nil { - return - } - peers = []peer.Peer{one} - return - } -} - -func (c *configuration) OnePeer(ctx context.Context, spaceId string) (p peer.Peer, err error) { - nodeIds := c.NodeIds(spaceId) - return c.pool.GetOneOf(ctx, nodeIds) -} - func (c *configuration) NodeIds(spaceId string) []string { members := c.chash.GetMembers(spaceId) res := make([]string, 0, len(members)) diff --git a/common/nodeconf/mock_nodeconf/mock_nodeconf.go b/common/nodeconf/mock_nodeconf/mock_nodeconf.go new file mode 100644 index 00000000..a22e4a6e --- /dev/null +++ b/common/nodeconf/mock_nodeconf/mock_nodeconf.go @@ -0,0 +1,131 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf (interfaces: Configuration,ConfConnector) + +// Package mock_nodeconf is a generated GoMock package. +package mock_nodeconf + +import ( + context "context" + reflect "reflect" + + peer "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/peer" + gomock "github.com/golang/mock/gomock" +) + +// MockConfiguration is a mock of Configuration interface. +type MockConfiguration struct { + ctrl *gomock.Controller + recorder *MockConfigurationMockRecorder +} + +// MockConfigurationMockRecorder is the mock recorder for MockConfiguration. +type MockConfigurationMockRecorder struct { + mock *MockConfiguration +} + +// NewMockConfiguration creates a new mock instance. +func NewMockConfiguration(ctrl *gomock.Controller) *MockConfiguration { + mock := &MockConfiguration{ctrl: ctrl} + mock.recorder = &MockConfigurationMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConfiguration) EXPECT() *MockConfigurationMockRecorder { + return m.recorder +} + +// Id mocks base method. +func (m *MockConfiguration) Id() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Id") + ret0, _ := ret[0].(string) + return ret0 +} + +// Id indicates an expected call of Id. +func (mr *MockConfigurationMockRecorder) Id() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockConfiguration)(nil).Id)) +} + +// IsResponsible mocks base method. +func (m *MockConfiguration) IsResponsible(arg0 string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsResponsible", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsResponsible indicates an expected call of IsResponsible. +func (mr *MockConfigurationMockRecorder) IsResponsible(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsResponsible", reflect.TypeOf((*MockConfiguration)(nil).IsResponsible), arg0) +} + +// NodeIds mocks base method. +func (m *MockConfiguration) NodeIds(arg0 string) []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NodeIds", arg0) + ret0, _ := ret[0].([]string) + return ret0 +} + +// NodeIds indicates an expected call of NodeIds. +func (mr *MockConfigurationMockRecorder) NodeIds(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeIds", reflect.TypeOf((*MockConfiguration)(nil).NodeIds), arg0) +} + +// 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 +} + +// DialResponsiblePeers mocks base method. +func (m *MockConfConnector) DialResponsiblePeers(arg0 context.Context, arg1 string) ([]peer.Peer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DialResponsiblePeers", arg0, arg1) + ret0, _ := ret[0].([]peer.Peer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DialResponsiblePeers indicates an expected call of DialResponsiblePeers. +func (mr *MockConfConnectorMockRecorder) DialResponsiblePeers(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DialResponsiblePeers", reflect.TypeOf((*MockConfConnector)(nil).DialResponsiblePeers), arg0, arg1) +} + +// 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) +} diff --git a/common/nodeconf/service.go b/common/nodeconf/service.go index cc80e288..66f6b6b3 100644 --- a/common/nodeconf/service.go +++ b/common/nodeconf/service.go @@ -3,7 +3,6 @@ package nodeconf import ( "github.com/anytypeio/go-anytype-infrastructure-experiments/app" "github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger" - "github.com/anytypeio/go-anytype-infrastructure-experiments/common/net/pool" "github.com/anytypeio/go-anytype-infrastructure-experiments/config" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey" @@ -29,7 +28,6 @@ type Service interface { type service struct { accountId string - pool pool.Pool consensusPeers []string last Configuration @@ -53,12 +51,10 @@ func (n *Node) Capacity() float64 { func (s *service) Init(a *app.App) (err error) { conf := a.MustComponent(config.CName).(*config.Config) s.accountId = conf.Account.PeerId - s.pool = a.MustComponent(pool.CName).(pool.Pool) config := &configuration{ id: "config", accountId: s.accountId, - pool: s.pool, } if config.chash, err = chash.New(chash.Config{ PartitionCount: partitionCount, diff --git a/go.mod b/go.mod index 17ad3f10..c25e1b35 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,16 @@ require ( github.com/anytypeio/go-chash v0.0.0-20220629194632-4ad1154fe232 github.com/awalterschulze/gographviz v0.0.0-20190522210029-fa59802746ab github.com/cespare/xxhash v1.1.0 + github.com/cheggaaa/mb/v2 v2.0.1 github.com/goccy/go-graphviz v0.0.9 github.com/gogo/protobuf v1.3.2 + github.com/golang/mock v1.6.0 github.com/huandu/skiplist v1.2.0 github.com/ipfs/go-cid v0.1.0 github.com/libp2p/go-libp2p v0.20.3 github.com/libp2p/go-libp2p-core v0.16.1 github.com/minio/sha256-simd v1.0.0 + github.com/mr-tron/base58 v1.2.0 github.com/multiformats/go-multibase v0.0.3 github.com/multiformats/go-multihash v0.1.0 github.com/stretchr/testify v1.8.0 @@ -30,7 +33,6 @@ require ( github.com/btcsuite/btcd v0.22.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect - github.com/cheggaaa/mb/v2 v2.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/fogleman/gg v1.3.0 // indirect @@ -42,7 +44,6 @@ require ( github.com/libp2p/go-openssl v0.0.7 // indirect github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect - github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.0.3 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect github.com/multiformats/go-multiaddr v0.5.0 // indirect @@ -60,9 +61,12 @@ require ( go.uber.org/multierr v1.8.0 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/image v0.0.0-20200119044424-58c23975cae1 // indirect + golang.org/x/mod v0.4.2 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect golang.org/x/text v0.3.7 // indirect + golang.org/x/tools v0.1.5 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect lukechampine.com/blake3 v1.1.6 // indirect diff --git a/go.sum b/go.sum index bf139393..6805b56c 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,6 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOF github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cheggaaa/mb v1.0.3 h1:03ksWum+6kHclB+kjwKMaBtgl5gtNYUwNpxsHQciKe8= -github.com/cheggaaa/mb v1.0.3/go.mod h1:NUl0GBtFLlfg2o6iZwxzcG7Lslc2wV/ADTFbLXtVPE4= github.com/cheggaaa/mb/v2 v2.0.1 h1:gn0khbEbKlw3i5VOYi0VnHEHayjZKfUDOyGSpHAybBs= github.com/cheggaaa/mb/v2 v2.0.1/go.mod h1:XGeZw20Iqgjky26KL0mvCwk3+4NyZCUbshSo6ALne+c= github.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4rUs6kA= @@ -36,6 +34,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= @@ -167,6 +167,7 @@ golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+o golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -206,6 +207,8 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/acl/list/list.go b/pkg/acl/list/list.go index 46a48681..dae887e6 100644 --- a/pkg/acl/list/list.go +++ b/pkg/acl/list/list.go @@ -1,3 +1,4 @@ +//go:generate mockgen -destination mock_list/mock_list.go github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list ACLList package list import ( diff --git a/pkg/acl/list/mock_list/mock_list.go b/pkg/acl/list/mock_list/mock_list.go new file mode 100644 index 00000000..e4c7cc6e --- /dev/null +++ b/pkg/acl/list/mock_list/mock_list.go @@ -0,0 +1,222 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list (interfaces: ACLList) + +// Package mock_list is a generated GoMock package. +package mock_list + +import ( + reflect "reflect" + + aclrecordproto "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto" + list "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list" + gomock "github.com/golang/mock/gomock" +) + +// MockACLList is a mock of ACLList interface. +type MockACLList struct { + ctrl *gomock.Controller + recorder *MockACLListMockRecorder +} + +// MockACLListMockRecorder is the mock recorder for MockACLList. +type MockACLListMockRecorder struct { + mock *MockACLList +} + +// NewMockACLList creates a new mock instance. +func NewMockACLList(ctrl *gomock.Controller) *MockACLList { + mock := &MockACLList{ctrl: ctrl} + mock.recorder = &MockACLListMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockACLList) EXPECT() *MockACLListMockRecorder { + return m.recorder +} + +// ACLState mocks base method. +func (m *MockACLList) ACLState() *list.ACLState { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ACLState") + ret0, _ := ret[0].(*list.ACLState) + return ret0 +} + +// ACLState indicates an expected call of ACLState. +func (mr *MockACLListMockRecorder) ACLState() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ACLState", reflect.TypeOf((*MockACLList)(nil).ACLState)) +} + +// Close mocks base method. +func (m *MockACLList) 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 *MockACLListMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockACLList)(nil).Close)) +} + +// Get mocks base method. +func (m *MockACLList) Get(arg0 string) (*list.ACLRecord, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0) + ret0, _ := ret[0].(*list.ACLRecord) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockACLListMockRecorder) Get(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockACLList)(nil).Get), arg0) +} + +// Head mocks base method. +func (m *MockACLList) Head() *list.ACLRecord { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Head") + ret0, _ := ret[0].(*list.ACLRecord) + return ret0 +} + +// Head indicates an expected call of Head. +func (mr *MockACLListMockRecorder) Head() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Head", reflect.TypeOf((*MockACLList)(nil).Head)) +} + +// ID mocks base method. +func (m *MockACLList) ID() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ID") + ret0, _ := ret[0].(string) + return ret0 +} + +// ID indicates an expected call of ID. +func (mr *MockACLListMockRecorder) ID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockACLList)(nil).ID)) +} + +// IsAfter mocks base method. +func (m *MockACLList) IsAfter(arg0, arg1 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsAfter", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsAfter indicates an expected call of IsAfter. +func (mr *MockACLListMockRecorder) IsAfter(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAfter", reflect.TypeOf((*MockACLList)(nil).IsAfter), arg0, arg1) +} + +// Iterate mocks base method. +func (m *MockACLList) Iterate(arg0 func(*list.ACLRecord) bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Iterate", arg0) +} + +// Iterate indicates an expected call of Iterate. +func (mr *MockACLListMockRecorder) Iterate(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterate", reflect.TypeOf((*MockACLList)(nil).Iterate), arg0) +} + +// IterateFrom mocks base method. +func (m *MockACLList) IterateFrom(arg0 string, arg1 func(*list.ACLRecord) bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "IterateFrom", arg0, arg1) +} + +// IterateFrom indicates an expected call of IterateFrom. +func (mr *MockACLListMockRecorder) IterateFrom(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateFrom", reflect.TypeOf((*MockACLList)(nil).IterateFrom), arg0, arg1) +} + +// Lock mocks base method. +func (m *MockACLList) Lock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Lock") +} + +// Lock indicates an expected call of Lock. +func (mr *MockACLListMockRecorder) Lock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockACLList)(nil).Lock)) +} + +// RLock mocks base method. +func (m *MockACLList) RLock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RLock") +} + +// RLock indicates an expected call of RLock. +func (mr *MockACLListMockRecorder) RLock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RLock", reflect.TypeOf((*MockACLList)(nil).RLock)) +} + +// RUnlock mocks base method. +func (m *MockACLList) RUnlock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RUnlock") +} + +// RUnlock indicates an expected call of RUnlock. +func (mr *MockACLListMockRecorder) RUnlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockACLList)(nil).RUnlock)) +} + +// Records mocks base method. +func (m *MockACLList) Records() []*list.ACLRecord { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Records") + ret0, _ := ret[0].([]*list.ACLRecord) + return ret0 +} + +// Records indicates an expected call of Records. +func (mr *MockACLListMockRecorder) Records() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Records", reflect.TypeOf((*MockACLList)(nil).Records)) +} + +// Root mocks base method. +func (m *MockACLList) Root() *aclrecordproto.ACLRoot { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Root") + ret0, _ := ret[0].(*aclrecordproto.ACLRoot) + return ret0 +} + +// Root indicates an expected call of Root. +func (mr *MockACLListMockRecorder) Root() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Root", reflect.TypeOf((*MockACLList)(nil).Root)) +} + +// Unlock mocks base method. +func (m *MockACLList) Unlock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Unlock") +} + +// Unlock indicates an expected call of Unlock. +func (mr *MockACLListMockRecorder) Unlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockACLList)(nil).Unlock)) +} diff --git a/pkg/acl/storage/inmemory.go b/pkg/acl/storage/inmemory.go index 6ddbea57..487caa04 100644 --- a/pkg/acl/storage/inmemory.go +++ b/pkg/acl/storage/inmemory.go @@ -87,6 +87,11 @@ func NewInMemoryTreeStorage( }, nil } +func (t *inMemoryTreeStorage) HasChange(ctx context.Context, id string) (bool, error) { + _, exists := t.changes[id] + return exists, nil +} + func (t *inMemoryTreeStorage) ID() (string, error) { t.RLock() defer t.RUnlock() diff --git a/pkg/acl/storage/liststorage.go b/pkg/acl/storage/liststorage.go index c4fe7d6e..222fd8d8 100644 --- a/pkg/acl/storage/liststorage.go +++ b/pkg/acl/storage/liststorage.go @@ -1,3 +1,4 @@ +//go:generate mockgen -destination mock_storage/mock_storage.go github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage ListStorage,TreeStorage package storage import ( diff --git a/pkg/acl/storage/mock_storage/mock_storage.go b/pkg/acl/storage/mock_storage/mock_storage.go new file mode 100644 index 00000000..f20bf468 --- /dev/null +++ b/pkg/acl/storage/mock_storage/mock_storage.go @@ -0,0 +1,237 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage (interfaces: ListStorage,TreeStorage) + +// Package mock_storage is a generated GoMock package. +package mock_storage + +import ( + context "context" + reflect "reflect" + + aclrecordproto "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclrecordproto" + treechangeproto "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto" + gomock "github.com/golang/mock/gomock" +) + +// MockListStorage is a mock of ListStorage interface. +type MockListStorage struct { + ctrl *gomock.Controller + recorder *MockListStorageMockRecorder +} + +// MockListStorageMockRecorder is the mock recorder for MockListStorage. +type MockListStorageMockRecorder struct { + mock *MockListStorage +} + +// NewMockListStorage creates a new mock instance. +func NewMockListStorage(ctrl *gomock.Controller) *MockListStorage { + mock := &MockListStorage{ctrl: ctrl} + mock.recorder = &MockListStorageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockListStorage) EXPECT() *MockListStorageMockRecorder { + return m.recorder +} + +// AddRawRecord mocks base method. +func (m *MockListStorage) AddRawRecord(arg0 context.Context, arg1 *aclrecordproto.RawACLRecordWithId) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddRawRecord", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddRawRecord indicates an expected call of AddRawRecord. +func (mr *MockListStorageMockRecorder) AddRawRecord(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawRecord", reflect.TypeOf((*MockListStorage)(nil).AddRawRecord), arg0, arg1) +} + +// GetRawRecord mocks base method. +func (m *MockListStorage) GetRawRecord(arg0 context.Context, arg1 string) (*aclrecordproto.RawACLRecordWithId, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRawRecord", arg0, arg1) + ret0, _ := ret[0].(*aclrecordproto.RawACLRecordWithId) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRawRecord indicates an expected call of GetRawRecord. +func (mr *MockListStorageMockRecorder) GetRawRecord(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRawRecord", reflect.TypeOf((*MockListStorage)(nil).GetRawRecord), arg0, arg1) +} + +// Head mocks base method. +func (m *MockListStorage) Head() (*aclrecordproto.RawACLRecordWithId, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Head") + ret0, _ := ret[0].(*aclrecordproto.RawACLRecordWithId) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Head indicates an expected call of Head. +func (mr *MockListStorageMockRecorder) Head() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Head", reflect.TypeOf((*MockListStorage)(nil).Head)) +} + +// ID mocks base method. +func (m *MockListStorage) ID() (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ID") + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ID indicates an expected call of ID. +func (mr *MockListStorageMockRecorder) ID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockListStorage)(nil).ID)) +} + +// Root mocks base method. +func (m *MockListStorage) Root() (*aclrecordproto.RawACLRecordWithId, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Root") + ret0, _ := ret[0].(*aclrecordproto.RawACLRecordWithId) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Root indicates an expected call of Root. +func (mr *MockListStorageMockRecorder) Root() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Root", reflect.TypeOf((*MockListStorage)(nil).Root)) +} + +// MockTreeStorage is a mock of TreeStorage interface. +type MockTreeStorage struct { + ctrl *gomock.Controller + recorder *MockTreeStorageMockRecorder +} + +// MockTreeStorageMockRecorder is the mock recorder for MockTreeStorage. +type MockTreeStorageMockRecorder struct { + mock *MockTreeStorage +} + +// NewMockTreeStorage creates a new mock instance. +func NewMockTreeStorage(ctrl *gomock.Controller) *MockTreeStorage { + mock := &MockTreeStorage{ctrl: ctrl} + mock.recorder = &MockTreeStorageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTreeStorage) EXPECT() *MockTreeStorageMockRecorder { + return m.recorder +} + +// AddRawChange mocks base method. +func (m *MockTreeStorage) AddRawChange(arg0 *treechangeproto.RawTreeChangeWithId) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddRawChange", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddRawChange indicates an expected call of AddRawChange. +func (mr *MockTreeStorageMockRecorder) AddRawChange(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChange", reflect.TypeOf((*MockTreeStorage)(nil).AddRawChange), arg0) +} + +// GetRawChange mocks base method. +func (m *MockTreeStorage) GetRawChange(arg0 context.Context, arg1 string) (*treechangeproto.RawTreeChangeWithId, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRawChange", arg0, arg1) + ret0, _ := ret[0].(*treechangeproto.RawTreeChangeWithId) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRawChange indicates an expected call of GetRawChange. +func (mr *MockTreeStorageMockRecorder) GetRawChange(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRawChange", reflect.TypeOf((*MockTreeStorage)(nil).GetRawChange), arg0, arg1) +} + +// HasChange mocks base method. +func (m *MockTreeStorage) HasChange(arg0 context.Context, arg1 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasChange", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasChange indicates an expected call of HasChange. +func (mr *MockTreeStorageMockRecorder) HasChange(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasChange", reflect.TypeOf((*MockTreeStorage)(nil).HasChange), arg0, arg1) +} + +// Heads mocks base method. +func (m *MockTreeStorage) Heads() ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Heads") + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Heads indicates an expected call of Heads. +func (mr *MockTreeStorageMockRecorder) Heads() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Heads", reflect.TypeOf((*MockTreeStorage)(nil).Heads)) +} + +// ID mocks base method. +func (m *MockTreeStorage) ID() (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ID") + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ID indicates an expected call of ID. +func (mr *MockTreeStorageMockRecorder) ID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockTreeStorage)(nil).ID)) +} + +// Root mocks base method. +func (m *MockTreeStorage) Root() (*treechangeproto.RawTreeChangeWithId, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Root") + ret0, _ := ret[0].(*treechangeproto.RawTreeChangeWithId) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Root indicates an expected call of Root. +func (mr *MockTreeStorageMockRecorder) Root() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Root", reflect.TypeOf((*MockTreeStorage)(nil).Root)) +} + +// SetHeads mocks base method. +func (m *MockTreeStorage) SetHeads(arg0 []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetHeads", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetHeads indicates an expected call of SetHeads. +func (mr *MockTreeStorageMockRecorder) SetHeads(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHeads", reflect.TypeOf((*MockTreeStorage)(nil).SetHeads), arg0) +} diff --git a/pkg/acl/storage/treestorage.go b/pkg/acl/storage/treestorage.go index a9c98f1d..25b91e46 100644 --- a/pkg/acl/storage/treestorage.go +++ b/pkg/acl/storage/treestorage.go @@ -13,6 +13,7 @@ type TreeStorage interface { AddRawChange(change *treechangeproto.RawTreeChangeWithId) error GetRawChange(ctx context.Context, id string) (*treechangeproto.RawTreeChangeWithId, error) + HasChange(ctx context.Context, id string) (bool, error) } type TreeStorageCreatorFunc = func(payload TreeStorageCreatePayload) (TreeStorage, error) diff --git a/pkg/acl/tree/mock_objecttree/mock_objecttree.go b/pkg/acl/tree/mock_objecttree/mock_objecttree.go new file mode 100644 index 00000000..70afa9d4 --- /dev/null +++ b/pkg/acl/tree/mock_objecttree/mock_objecttree.go @@ -0,0 +1,295 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree (interfaces: ObjectTree) + +// Package mock_tree is a generated GoMock package. +package mock_tree + +import ( + context "context" + reflect "reflect" + + storage "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage" + tree "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree" + treechangeproto "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treechangeproto" + gomock "github.com/golang/mock/gomock" +) + +// MockObjectTree is a mock of ObjectTree interface. +type MockObjectTree struct { + ctrl *gomock.Controller + recorder *MockObjectTreeMockRecorder +} + +// MockObjectTreeMockRecorder is the mock recorder for MockObjectTree. +type MockObjectTreeMockRecorder struct { + mock *MockObjectTree +} + +// NewMockObjectTree creates a new mock instance. +func NewMockObjectTree(ctrl *gomock.Controller) *MockObjectTree { + mock := &MockObjectTree{ctrl: ctrl} + mock.recorder = &MockObjectTreeMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockObjectTree) EXPECT() *MockObjectTreeMockRecorder { + return m.recorder +} + +// AddContent mocks base method. +func (m *MockObjectTree) AddContent(arg0 context.Context, arg1 tree.SignableChangeContent) (tree.AddResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddContent", arg0, arg1) + ret0, _ := ret[0].(tree.AddResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddContent indicates an expected call of AddContent. +func (mr *MockObjectTreeMockRecorder) AddContent(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddContent", reflect.TypeOf((*MockObjectTree)(nil).AddContent), arg0, arg1) +} + +// AddRawChanges mocks base method. +func (m *MockObjectTree) AddRawChanges(arg0 context.Context, arg1 ...*treechangeproto.RawTreeChangeWithId) (tree.AddResult, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddRawChanges", varargs...) + ret0, _ := ret[0].(tree.AddResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddRawChanges indicates an expected call of AddRawChanges. +func (mr *MockObjectTreeMockRecorder) AddRawChanges(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChanges", reflect.TypeOf((*MockObjectTree)(nil).AddRawChanges), varargs...) +} + +// ChangesAfterCommonSnapshot mocks base method. +func (m *MockObjectTree) ChangesAfterCommonSnapshot(arg0, arg1 []string) ([]*treechangeproto.RawTreeChangeWithId, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChangesAfterCommonSnapshot", arg0, arg1) + ret0, _ := ret[0].([]*treechangeproto.RawTreeChangeWithId) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChangesAfterCommonSnapshot indicates an expected call of ChangesAfterCommonSnapshot. +func (mr *MockObjectTreeMockRecorder) ChangesAfterCommonSnapshot(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangesAfterCommonSnapshot", reflect.TypeOf((*MockObjectTree)(nil).ChangesAfterCommonSnapshot), arg0, arg1) +} + +// Close mocks base method. +func (m *MockObjectTree) 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 *MockObjectTreeMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockObjectTree)(nil).Close)) +} + +// DebugDump mocks base method. +func (m *MockObjectTree) DebugDump() (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DebugDump") + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DebugDump indicates an expected call of DebugDump. +func (mr *MockObjectTreeMockRecorder) DebugDump() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DebugDump", reflect.TypeOf((*MockObjectTree)(nil).DebugDump)) +} + +// HasChanges mocks base method. +func (m *MockObjectTree) HasChanges(arg0 ...string) bool { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "HasChanges", varargs...) + ret0, _ := ret[0].(bool) + return ret0 +} + +// HasChanges indicates an expected call of HasChanges. +func (mr *MockObjectTreeMockRecorder) HasChanges(arg0 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasChanges", reflect.TypeOf((*MockObjectTree)(nil).HasChanges), arg0...) +} + +// Header mocks base method. +func (m *MockObjectTree) Header() *treechangeproto.RawTreeChangeWithId { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Header") + ret0, _ := ret[0].(*treechangeproto.RawTreeChangeWithId) + return ret0 +} + +// Header indicates an expected call of Header. +func (mr *MockObjectTreeMockRecorder) Header() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockObjectTree)(nil).Header)) +} + +// Heads mocks base method. +func (m *MockObjectTree) Heads() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Heads") + ret0, _ := ret[0].([]string) + return ret0 +} + +// Heads indicates an expected call of Heads. +func (mr *MockObjectTreeMockRecorder) Heads() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Heads", reflect.TypeOf((*MockObjectTree)(nil).Heads)) +} + +// ID mocks base method. +func (m *MockObjectTree) ID() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ID") + ret0, _ := ret[0].(string) + return ret0 +} + +// ID indicates an expected call of ID. +func (mr *MockObjectTreeMockRecorder) ID() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockObjectTree)(nil).ID)) +} + +// Iterate mocks base method. +func (m *MockObjectTree) Iterate(arg0 func([]byte) (interface{}, error), arg1 func(*tree.Change) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Iterate", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Iterate indicates an expected call of Iterate. +func (mr *MockObjectTreeMockRecorder) Iterate(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterate", reflect.TypeOf((*MockObjectTree)(nil).Iterate), arg0, arg1) +} + +// IterateFrom mocks base method. +func (m *MockObjectTree) IterateFrom(arg0 string, arg1 func([]byte) (interface{}, error), arg2 func(*tree.Change) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IterateFrom", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// IterateFrom indicates an expected call of IterateFrom. +func (mr *MockObjectTreeMockRecorder) IterateFrom(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateFrom", reflect.TypeOf((*MockObjectTree)(nil).IterateFrom), arg0, arg1, arg2) +} + +// Lock mocks base method. +func (m *MockObjectTree) Lock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Lock") +} + +// Lock indicates an expected call of Lock. +func (mr *MockObjectTreeMockRecorder) Lock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockObjectTree)(nil).Lock)) +} + +// RLock mocks base method. +func (m *MockObjectTree) RLock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RLock") +} + +// RLock indicates an expected call of RLock. +func (mr *MockObjectTreeMockRecorder) RLock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RLock", reflect.TypeOf((*MockObjectTree)(nil).RLock)) +} + +// RUnlock mocks base method. +func (m *MockObjectTree) RUnlock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RUnlock") +} + +// RUnlock indicates an expected call of RUnlock. +func (mr *MockObjectTreeMockRecorder) RUnlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockObjectTree)(nil).RUnlock)) +} + +// Root mocks base method. +func (m *MockObjectTree) Root() *tree.Change { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Root") + ret0, _ := ret[0].(*tree.Change) + return ret0 +} + +// Root indicates an expected call of Root. +func (mr *MockObjectTreeMockRecorder) Root() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Root", reflect.TypeOf((*MockObjectTree)(nil).Root)) +} + +// SnapshotPath mocks base method. +func (m *MockObjectTree) SnapshotPath() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SnapshotPath") + ret0, _ := ret[0].([]string) + return ret0 +} + +// SnapshotPath indicates an expected call of SnapshotPath. +func (mr *MockObjectTreeMockRecorder) SnapshotPath() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SnapshotPath", reflect.TypeOf((*MockObjectTree)(nil).SnapshotPath)) +} + +// Storage mocks base method. +func (m *MockObjectTree) Storage() storage.TreeStorage { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Storage") + ret0, _ := ret[0].(storage.TreeStorage) + return ret0 +} + +// Storage indicates an expected call of Storage. +func (mr *MockObjectTreeMockRecorder) Storage() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Storage", reflect.TypeOf((*MockObjectTree)(nil).Storage)) +} + +// Unlock mocks base method. +func (m *MockObjectTree) Unlock() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Unlock") +} + +// Unlock indicates an expected call of Unlock. +func (mr *MockObjectTreeMockRecorder) Unlock() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockObjectTree)(nil).Unlock)) +} diff --git a/pkg/acl/tree/objecttree.go b/pkg/acl/tree/objecttree.go index bb342070..9e6323aa 100644 --- a/pkg/acl/tree/objecttree.go +++ b/pkg/acl/tree/objecttree.go @@ -1,3 +1,4 @@ +//go:generate mockgen -destination mock_objecttree/mock_objecttree.go github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree ObjectTree package tree import ( @@ -42,7 +43,7 @@ type ObjectTree interface { Header() *treechangeproto.RawTreeChangeWithId Heads() []string Root() *Change - HasChange(string) bool + HasChanges(...string) bool DebugDump() (string, error) Iterate(convert ChangeConvertFunc, iterate ChangeIterateFunc) error @@ -75,7 +76,7 @@ type objectTree struct { // buffers difSnapshotBuf []*treechangeproto.RawTreeChangeWithId - tmpChangesBuf []*Change + newChangesBuf []*Change newSnapshotsBuf []*Change notSeenIdxBuf []int @@ -226,7 +227,7 @@ func (ot *objectTree) AddRawChanges(ctx context.Context, rawChanges ...*treechan func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*treechangeproto.RawTreeChangeWithId) (addResult AddResult, err error) { // resetting buffers - ot.tmpChangesBuf = ot.tmpChangesBuf[:0] + ot.newChangesBuf = ot.newChangesBuf[:0] ot.notSeenIdxBuf = ot.notSeenIdxBuf[:0] ot.difSnapshotBuf = ot.difSnapshotBuf[:0] ot.newSnapshotsBuf = ot.newSnapshotsBuf[:0] @@ -246,20 +247,21 @@ func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*treechan if _, exists := ot.tree.attached[ch.Id]; exists { continue } - if _, exists := ot.tree.unAttached[ch.Id]; exists { - continue - } var change *Change - change, err = ot.changeBuilder.ConvertFromRaw(ch, true) - if err != nil { - return + if unAttached, exists := ot.tree.unAttached[ch.Id]; exists { + change = unAttached + } else { + change, err = ot.changeBuilder.ConvertFromRaw(ch, true) + if err != nil { + return + } } if change.IsSnapshot { ot.newSnapshotsBuf = append(ot.newSnapshotsBuf, change) } - ot.tmpChangesBuf = append(ot.tmpChangesBuf, change) + ot.newChangesBuf = append(ot.newChangesBuf, change) ot.notSeenIdxBuf = append(ot.notSeenIdxBuf, idx) } @@ -273,6 +275,106 @@ func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*treechan return } + rollback := func(changes []*Change) { + for _, ch := range changes { + if _, exists := ot.tree.attached[ch.Id]; exists { + delete(ot.tree.attached, ch.Id) + } + } + } + + // checks if we need to go to database + isOldSnapshot := func(ch *Change) bool { + if ch.SnapshotId == ot.tree.RootId() { + return false + } + for _, sn := range ot.newSnapshotsBuf { + // if change refers to newly received snapshot + if ch.SnapshotId == sn.Id { + return false + } + } + return true + } + + shouldRebuildFromStorage := false + // checking if we have some changes with different snapshot and then rebuilding + for idx, ch := range ot.newChangesBuf { + if isOldSnapshot(ch) { + var exists bool + // checking if it exists in the storage, if yes, then at some point it was added to the tree + // thus we don't need to look at this change + exists, err = ot.treeStorage.HasChange(ctx, ch.Id) + if err != nil { + return + } + if exists { + // marking as nil to delete after + ot.newChangesBuf[idx] = nil + continue + } + // we haven't seen the change, and it refers to old snapshot, so we should rebuild + shouldRebuildFromStorage = true + } + } + // discarding all previously seen changes + ot.newChangesBuf = discardFromSlice(ot.newChangesBuf, func(ch *Change) bool { return ch == nil }) + + if shouldRebuildFromStorage { + err = ot.rebuildFromStorage(ot.newChangesBuf) + if err != nil { + // rebuilding without new changes + ot.rebuildFromStorage(nil) + return + } + addResult, err = ot.createAddResult(prevHeadsCopy, Rebuild, nil, rawChanges) + if err != nil { + // that means that some unattached changes were somehow corrupted in memory + // this shouldn't happen but if that happens, then rebuilding from storage + ot.rebuildFromStorage(nil) + return + } + return + } + + // normal mode of operation, where we don't need to rebuild from database + mode, treeChangesAdded := ot.tree.Add(ot.newChangesBuf...) + switch mode { + case Nothing: + addResult = AddResult{ + OldHeads: prevHeadsCopy, + Heads: prevHeadsCopy, + Mode: mode, + } + return + + default: + // we need to validate only newly added changes + err = ot.validateTree(treeChangesAdded) + if err != nil { + rollback(treeChangesAdded) + err = ErrHasInvalidChanges + return + } + addResult, err = ot.createAddResult(prevHeadsCopy, mode, treeChangesAdded, rawChanges) + if err != nil { + // that means that some unattached changes were somehow corrupted in memory + // this shouldn't happen but if that happens, then rebuilding from storage + ot.rebuildFromStorage(nil) + return + } + return + } + return +} + +func (ot *objectTree) createAddResult(oldHeads []string, mode Mode, treeChangesAdded []*Change, rawChanges []*treechangeproto.RawTreeChangeWithId) (addResult AddResult, err error) { + headsCopy := func() []string { + newHeads := make([]string, 0, len(ot.tree.Heads())) + newHeads = append(newHeads, ot.tree.Heads()...) + return newHeads + } + // returns changes that we added to the tree as attached this round // they can include not only the changes that were added now, // but also the changes that were previously in the tree @@ -312,88 +414,16 @@ func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*treechan return } - rollback := func(changes []*Change) { - for _, ch := range changes { - if _, exists := ot.tree.attached[ch.Id]; exists { - delete(ot.tree.attached, ch.Id) - } - } - } - - // checks if we need to go to database - isOldSnapshot := func(ch *Change) bool { - if ch.SnapshotId == ot.tree.RootId() { - return false - } - for _, sn := range ot.newSnapshotsBuf { - // if change refers to newly received snapshot - if ch.SnapshotId == sn.Id { - return false - } - } - return true - } - - // checking if we have some changes with different snapshot and then rebuilding - for _, ch := range ot.tmpChangesBuf { - if isOldSnapshot(ch) { - err = ot.rebuildFromStorage(ot.tmpChangesBuf) - if err != nil { - // rebuilding without new changes - ot.rebuildFromStorage(nil) - return - } - var added []*treechangeproto.RawTreeChangeWithId - added, err = getAddedChanges(nil) - // we shouldn't get any error in this case - if err != nil { - panic(err) - } - - addResult = AddResult{ - OldHeads: prevHeadsCopy, - Heads: headsCopy(), - Added: added, - Mode: Rebuild, - } - return - } - } - - // normal mode of operation, where we don't need to rebuild from database - mode, treeChangesAdded := ot.tree.Add(ot.tmpChangesBuf...) - switch mode { - case Nothing: - addResult = AddResult{ - OldHeads: prevHeadsCopy, - Heads: prevHeadsCopy, - Mode: mode, - } + var added []*treechangeproto.RawTreeChangeWithId + added, err = getAddedChanges(treeChangesAdded) + if err != nil { return - - default: - // we need to validate only newly added changes - err = ot.validateTree(treeChangesAdded) - if err != nil { - rollback(treeChangesAdded) - err = ErrHasInvalidChanges - return - } - var added []*treechangeproto.RawTreeChangeWithId - added, err = getAddedChanges(treeChangesAdded) - if err != nil { - // that means that some unattached changes were somehow corrupted in memory - // this shouldn't happen but if that happens, then rebuilding from storage - ot.rebuildFromStorage(nil) - return - } - - addResult = AddResult{ - OldHeads: prevHeadsCopy, - Heads: headsCopy(), - Added: added, - Mode: mode, - } + } + addResult = AddResult{ + OldHeads: oldHeads, + Heads: headsCopy(), + Added: added, + Mode: mode, } return } @@ -440,9 +470,28 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate return } -func (ot *objectTree) HasChange(s string) bool { - _, attachedExists := ot.tree.attached[s] - return attachedExists +func (ot *objectTree) HasChanges(chs ...string) bool { + hasChange := func(s string) bool { + _, attachedExists := ot.tree.attached[s] + if attachedExists { + return attachedExists + } + + has, err := ot.treeStorage.HasChange(context.Background(), s) + if err != nil { + return false + } + + return has + } + + for _, ch := range chs { + if !hasChange(ch) { + return false + } + } + + return true } func (ot *objectTree) Heads() []string { diff --git a/pkg/acl/tree/objecttreefactory.go b/pkg/acl/tree/objecttreefactory.go index 61631412..1db77908 100644 --- a/pkg/acl/tree/objecttreefactory.go +++ b/pkg/acl/tree/objecttreefactory.go @@ -104,7 +104,7 @@ func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) { rawChangeLoader: deps.rawChangeLoader, tree: nil, keys: make(map[uint64]*symmetric.Key), - tmpChangesBuf: make([]*Change, 0, 10), + newChangesBuf: make([]*Change, 0, 10), difSnapshotBuf: make([]*treechangeproto.RawTreeChangeWithId, 0, 10), notSeenIdxBuf: make([]int, 0, 10), newSnapshotsBuf: make([]*Change, 0, 10), diff --git a/pkg/ldiff/diff.go b/pkg/ldiff/diff.go index 6f276afd..2f9a5161 100644 --- a/pkg/ldiff/diff.go +++ b/pkg/ldiff/diff.go @@ -1,3 +1,4 @@ +//go:generate mockgen -destination mock_ldiff/mock_ldiff.go github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ldiff Diff,Remote // 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. package ldiff diff --git a/pkg/ldiff/mock_ldiff/mock_ldiff.go b/pkg/ldiff/mock_ldiff/mock_ldiff.go new file mode 100644 index 00000000..183e7bef --- /dev/null +++ b/pkg/ldiff/mock_ldiff/mock_ldiff.go @@ -0,0 +1,136 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ldiff (interfaces: Diff,Remote) + +// Package mock_ldiff is a generated GoMock package. +package mock_ldiff + +import ( + context "context" + reflect "reflect" + + ldiff "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ldiff" + gomock "github.com/golang/mock/gomock" +) + +// MockDiff is a mock of Diff interface. +type MockDiff struct { + ctrl *gomock.Controller + recorder *MockDiffMockRecorder +} + +// MockDiffMockRecorder is the mock recorder for MockDiff. +type MockDiffMockRecorder struct { + mock *MockDiff +} + +// NewMockDiff creates a new mock instance. +func NewMockDiff(ctrl *gomock.Controller) *MockDiff { + mock := &MockDiff{ctrl: ctrl} + mock.recorder = &MockDiffMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDiff) EXPECT() *MockDiffMockRecorder { + return m.recorder +} + +// Diff mocks base method. +func (m *MockDiff) Diff(arg0 context.Context, arg1 ldiff.Remote) ([]string, []string, []string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Diff", arg0, arg1) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].([]string) + ret2, _ := ret[2].([]string) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 +} + +// Diff indicates an expected call of Diff. +func (mr *MockDiffMockRecorder) Diff(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Diff", reflect.TypeOf((*MockDiff)(nil).Diff), arg0, arg1) +} + +// Ranges mocks base method. +func (m *MockDiff) Ranges(arg0 context.Context, arg1 []ldiff.Range, arg2 []ldiff.RangeResult) ([]ldiff.RangeResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Ranges", arg0, arg1, arg2) + ret0, _ := ret[0].([]ldiff.RangeResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Ranges indicates an expected call of Ranges. +func (mr *MockDiffMockRecorder) Ranges(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ranges", reflect.TypeOf((*MockDiff)(nil).Ranges), arg0, arg1, arg2) +} + +// RemoveId mocks base method. +func (m *MockDiff) RemoveId(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveId", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveId indicates an expected call of RemoveId. +func (mr *MockDiffMockRecorder) RemoveId(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveId", reflect.TypeOf((*MockDiff)(nil).RemoveId), arg0) +} + +// Set mocks base method. +func (m *MockDiff) Set(arg0 ...ldiff.Element) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range arg0 { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Set", varargs...) +} + +// Set indicates an expected call of Set. +func (mr *MockDiffMockRecorder) Set(arg0 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockDiff)(nil).Set), arg0...) +} + +// MockRemote is a mock of Remote interface. +type MockRemote struct { + ctrl *gomock.Controller + recorder *MockRemoteMockRecorder +} + +// MockRemoteMockRecorder is the mock recorder for MockRemote. +type MockRemoteMockRecorder struct { + mock *MockRemote +} + +// NewMockRemote creates a new mock instance. +func NewMockRemote(ctrl *gomock.Controller) *MockRemote { + mock := &MockRemote{ctrl: ctrl} + mock.recorder = &MockRemoteMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRemote) EXPECT() *MockRemoteMockRecorder { + return m.recorder +} + +// Ranges mocks base method. +func (m *MockRemote) Ranges(arg0 context.Context, arg1 []ldiff.Range, arg2 []ldiff.RangeResult) ([]ldiff.RangeResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Ranges", arg0, arg1, arg2) + ret0, _ := ret[0].([]ldiff.RangeResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Ranges indicates an expected call of Ranges. +func (mr *MockRemoteMockRecorder) Ranges(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ranges", reflect.TypeOf((*MockRemote)(nil).Ranges), arg0, arg1, arg2) +}