diff --git a/common/commonspace/diffservice/diffservice.go b/common/commonspace/diffservice/diffservice.go index 113e4e5c..ae3b5cb4 100644 --- a/common/commonspace/diffservice/diffservice.go +++ b/common/commonspace/diffservice/diffservice.go @@ -21,7 +21,7 @@ type DiffService interface { RemoveObjects(ids []string) AllIds() []string - Init(objectIds []string, deletionState *deletionstate.DeletionState) + Init(objectIds []string, deletionState deletionstate.DeletionState) Close() (err error) } @@ -61,7 +61,7 @@ func NewDiffService( } } -func (d *diffService) Init(objectIds []string, deletionState *deletionstate.DeletionState) { +func (d *diffService) Init(objectIds []string, deletionState deletionstate.DeletionState) { d.fillDiff(objectIds) d.syncer.Init(deletionState) d.periodicSync.Run() diff --git a/common/commonspace/diffservice/diffsyncer.go b/common/commonspace/diffservice/diffsyncer.go index 723cd50d..1618ee71 100644 --- a/common/commonspace/diffservice/diffsyncer.go +++ b/common/commonspace/diffservice/diffsyncer.go @@ -19,7 +19,7 @@ type DiffSyncer interface { Sync(ctx context.Context) error RemoveObjects(ids []string) UpdateHeads(id string, heads []string) - Init(deletionState *deletionstate.DeletionState) + Init(deletionState deletionstate.DeletionState) } func newDiffSyncer( @@ -49,10 +49,10 @@ type diffSyncer struct { storage storage.SpaceStorage clientFactory spacesyncproto.ClientFactory log *zap.Logger - deletionState *deletionstate.DeletionState + deletionState deletionstate.DeletionState } -func (d *diffSyncer) Init(deletionState *deletionstate.DeletionState) { +func (d *diffSyncer) Init(deletionState deletionstate.DeletionState) { d.deletionState = deletionState d.deletionState.AddObserver(d.RemoveObjects) } diff --git a/common/commonspace/settingsdocument/deleter.go b/common/commonspace/settingsdocument/deleter.go index 51a8c32b..f1b5ca45 100644 --- a/common/commonspace/settingsdocument/deleter.go +++ b/common/commonspace/settingsdocument/deleter.go @@ -8,17 +8,21 @@ import ( "go.uber.org/zap" ) +type Deleter interface { + Delete() +} + type deleter struct { st storage.SpaceStorage - state *deletionstate.DeletionState + state deletionstate.DeletionState getter treegetter.TreeGetter } -func newDeleter(st storage.SpaceStorage, state *deletionstate.DeletionState, getter treegetter.TreeGetter) *deleter { +func newDeleter(st storage.SpaceStorage, state deletionstate.DeletionState, getter treegetter.TreeGetter) Deleter { return &deleter{st, state, getter} } -func (d *deleter) delete() { +func (d *deleter) Delete() { allQueued := d.state.GetQueued() for _, id := range allQueued { err := d.getter.DeleteTree(context.Background(), d.st.Id(), id) diff --git a/common/commonspace/settingsdocument/deletionstate/deletionstate.go b/common/commonspace/settingsdocument/deletionstate/deletionstate.go index 8a38528c..fe56f0b2 100644 --- a/common/commonspace/settingsdocument/deletionstate/deletionstate.go +++ b/common/commonspace/settingsdocument/deletionstate/deletionstate.go @@ -7,7 +7,16 @@ import ( type StateUpdateObserver func(ids []string) -type DeletionState struct { +type DeletionState interface { + AddObserver(observer StateUpdateObserver) + Add(ids []string) (err error) + GetQueued() (ids []string) + Delete(id string) (err error) + Exists(id string) bool + FilterJoin(ids ...[]string) (filtered []string) +} + +type deletionState struct { sync.RWMutex queued map[string]struct{} deleted map[string]struct{} @@ -15,21 +24,21 @@ type DeletionState struct { storage storage.SpaceStorage } -func NewDeletionState(storage storage.SpaceStorage) *DeletionState { - return &DeletionState{ +func NewDeletionState(storage storage.SpaceStorage) DeletionState { + return &deletionState{ queued: map[string]struct{}{}, deleted: map[string]struct{}{}, storage: storage, } } -func (st *DeletionState) AddObserver(observer StateUpdateObserver) { +func (st *deletionState) AddObserver(observer StateUpdateObserver) { st.Lock() defer st.Unlock() st.stateUpdateObservers = append(st.stateUpdateObservers, observer) } -func (st *DeletionState) Add(ids []string) (err error) { +func (st *deletionState) Add(ids []string) (err error) { st.Lock() defer func() { st.Unlock() @@ -71,7 +80,7 @@ func (st *DeletionState) Add(ids []string) (err error) { return } -func (st *DeletionState) GetQueued() (ids []string) { +func (st *deletionState) GetQueued() (ids []string) { st.RLock() defer st.RUnlock() ids = make([]string, 0, len(st.queued)) @@ -81,7 +90,7 @@ func (st *DeletionState) GetQueued() (ids []string) { return } -func (st *DeletionState) Delete(id string) (err error) { +func (st *deletionState) Delete(id string) (err error) { st.Lock() defer st.Unlock() delete(st.queued, id) @@ -93,13 +102,13 @@ func (st *DeletionState) Delete(id string) (err error) { return } -func (st *DeletionState) Exists(id string) bool { +func (st *deletionState) Exists(id string) bool { st.RLock() defer st.RUnlock() return st.exists(id) } -func (st *DeletionState) FilterJoin(ids ...[]string) (filtered []string) { +func (st *deletionState) FilterJoin(ids ...[]string) (filtered []string) { st.RLock() defer st.RUnlock() filter := func(ids []string) { @@ -115,7 +124,7 @@ func (st *DeletionState) FilterJoin(ids ...[]string) (filtered []string) { return } -func (st *DeletionState) exists(id string) bool { +func (st *deletionState) exists(id string) bool { if _, exists := st.deleted[id]; exists { return true } diff --git a/common/commonspace/settingsdocument/idprovider.go b/common/commonspace/settingsdocument/idprovider.go index 56fccab6..9ced17b3 100644 --- a/common/commonspace/settingsdocument/idprovider.go +++ b/common/commonspace/settingsdocument/idprovider.go @@ -1,4 +1,3 @@ -//go:generate mockgen -destination mock_settingsdocument/mock_settingsdocument.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument DeletedIdsProvider package settingsdocument import ( diff --git a/common/commonspace/settingsdocument/mock_settingsdocument/mock_settingsdocument.go b/common/commonspace/settingsdocument/mock_settingsdocument/mock_settingsdocument.go index 4a8a58d8..7d950895 100644 --- a/common/commonspace/settingsdocument/mock_settingsdocument/mock_settingsdocument.go +++ b/common/commonspace/settingsdocument/mock_settingsdocument/mock_settingsdocument.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument (interfaces: DeletedIdsProvider) +// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument (interfaces: DeletedIdsProvider,Deleter) // Package mock_settingsdocument is a generated GoMock package. package mock_settingsdocument @@ -49,3 +49,38 @@ func (mr *MockDeletedIdsProviderMockRecorder) ProvideIds(arg0, arg1 interface{}) mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProvideIds", reflect.TypeOf((*MockDeletedIdsProvider)(nil).ProvideIds), arg0, arg1) } + +// MockDeleter is a mock of Deleter interface. +type MockDeleter struct { + ctrl *gomock.Controller + recorder *MockDeleterMockRecorder +} + +// MockDeleterMockRecorder is the mock recorder for MockDeleter. +type MockDeleterMockRecorder struct { + mock *MockDeleter +} + +// NewMockDeleter creates a new mock instance. +func NewMockDeleter(ctrl *gomock.Controller) *MockDeleter { + mock := &MockDeleter{ctrl: ctrl} + mock.recorder = &MockDeleterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDeleter) EXPECT() *MockDeleterMockRecorder { + return m.recorder +} + +// Delete mocks base method. +func (m *MockDeleter) Delete() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Delete") +} + +// Delete indicates an expected call of Delete. +func (mr *MockDeleterMockRecorder) Delete() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockDeleter)(nil).Delete)) +} diff --git a/common/commonspace/settingsdocument/settingsdocument.go b/common/commonspace/settingsdocument/settingsdocument.go index 64d07987..7bb034a6 100644 --- a/common/commonspace/settingsdocument/settingsdocument.go +++ b/common/commonspace/settingsdocument/settingsdocument.go @@ -1,3 +1,4 @@ +//go:generate mockgen -destination mock_settingsdocument/mock_settingsdocument.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument DeletedIdsProvider,Deleter package settingsdocument import ( @@ -29,9 +30,10 @@ type Deps struct { Account account.Service TreeGetter treegetter.TreeGetter Store spacestorage.SpaceStorage - DeletionState *deletionstate.DeletionState - // prov exists mainly for the ease of testing + DeletionState deletionstate.DeletionState + // testing dependencies prov DeletedIdsProvider + del Deleter } type settingsDocument struct { @@ -44,14 +46,20 @@ type settingsDocument struct { buildFunc BuildTreeFunc loop *deleteLoop - deletionState *deletionstate.DeletionState + deletionState deletionstate.DeletionState lastChangeId string } -func NewSettingsDocument(deps Deps, spaceId string) (doc SettingsDocument, err error) { - deleter := newDeleter(deps.Store, deps.DeletionState, deps.TreeGetter) +func NewSettingsDocument(deps Deps, spaceId string) (doc SettingsDocument) { + var deleter Deleter + if deps.del == nil { + deleter = newDeleter(deps.Store, deps.DeletionState, deps.TreeGetter) + } else { + deleter = deps.del + } + loop := newDeleteLoop(func() { - deleter.delete() + deleter.Delete() }) deps.DeletionState.AddObserver(func(ids []string) { loop.notify() @@ -89,10 +97,12 @@ func (s *settingsDocument) updateIds(tr tree.ObjectTree, lastChangeId string) { } } +// Update is called as part of UpdateListener interface func (s *settingsDocument) Update(tr tree.ObjectTree) { s.updateIds(tr, s.lastChangeId) } +// Rebuild is called as part of UpdateListener interface (including when the object is built for the first time, e.g. on Init call) func (s *settingsDocument) Rebuild(tr tree.ObjectTree) { // at initial build "s" may not contain the object tree, so it is safer to provide it from the function parameter s.updateIds(tr, "") @@ -128,6 +138,7 @@ func (s *settingsDocument) DeleteObject(id string) (err error) { }, Snapshot: nil, } + // TODO: add snapshot logic res, err := change.Marshal() if err != nil { return diff --git a/common/commonspace/settingsdocument/settingsdocument_test.go b/common/commonspace/settingsdocument/settingsdocument_test.go index 43d929dc..9ee5a9e4 100644 --- a/common/commonspace/settingsdocument/settingsdocument_test.go +++ b/common/commonspace/settingsdocument/settingsdocument_test.go @@ -1,7 +1,108 @@ package settingsdocument -import "testing" +import ( + "context" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/account/mock_account" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/mock_settingsdocument" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage/mock_storage" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree/mock_synctree" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree/updatelistener" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/treegetter/mock_treegetter" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +type settingsFixture struct { + spaceId string + docId string + doc SettingsDocument + ctrl *gomock.Controller + treeGetter *mock_treegetter.MockTreeGetter + spaceStorage *mock_storage.MockSpaceStorage + provider *mock_settingsdocument.MockDeletedIdsProvider + deleter *mock_settingsdocument.MockDeleter + syncTree *mock_synctree.MockSyncTree + delState *deletionstate.DeletionState + account *mock_account.MockService +} + +func newSettingsFixture(t *testing.T) *settingsFixture { + spaceId := "spaceId" + docId := "documentId" + + ctrl := gomock.NewController(t) + acc := mock_account.NewMockService(ctrl) + treeGetter := mock_treegetter.NewMockTreeGetter(ctrl) + st := mock_storage.NewMockSpaceStorage(ctrl) + delState := deletionstate.NewDeletionState(st) + prov := mock_settingsdocument.NewMockDeletedIdsProvider(ctrl) + syncTree := mock_synctree.NewMockSyncTree(ctrl) + del := mock_settingsdocument.NewMockDeleter(ctrl) + + buildFunc := BuildTreeFunc(func(ctx context.Context, id string, listener updatelistener.UpdateListener) (synctree.SyncTree, error) { + require.Equal(t, docId, id) + return syncTree, nil + }) + + deps := Deps{ + BuildFunc: buildFunc, + Account: acc, + TreeGetter: treeGetter, + Store: st, + DeletionState: delState, + prov: prov, + del: del, + } + doc := NewSettingsDocument(deps, spaceId) + return &settingsFixture{ + spaceId: spaceId, + docId: docId, + doc: doc, + ctrl: ctrl, + treeGetter: treeGetter, + spaceStorage: st, + provider: prov, + deleter: del, + syncTree: syncTree, + account: acc, + delState: delState, + } +} + +func (fx *settingsFixture) stop() { + fx.ctrl.Finish() +} func TestSettingsDocument_Init(t *testing.T) { + fx := newSettingsFixture(t) + defer fx.stop() + fx.spaceStorage.EXPECT().SpaceSettingsId().Return(fx.docId) + fx.deleter.EXPECT().Delete() + fx.syncTree.EXPECT().Close().Return(nil) + + err := fx.doc.Init(context.Background()) + require.NoError(t, err) + err = fx.doc.Close() + require.NoError(t, err) +} + +func TestSettingsDocument_DeleteObject(t *testing.T) { + fx := newSettingsFixture(t) + defer fx.stop() + + fx.spaceStorage.EXPECT().SpaceSettingsId().Return(fx.docId) + fx.deleter.EXPECT().Delete() + fx.syncTree.EXPECT().Close().Return(nil) + + err := fx.doc.Init(context.Background()) + require.NoError(t, err) + time.Sleep(10 * time.Millisecond) + + err = fx.doc.Close() + require.NoError(t, err) } diff --git a/common/commonspace/space.go b/common/commonspace/space.go index 78085be4..7401f30c 100644 --- a/common/commonspace/space.go +++ b/common/commonspace/space.go @@ -167,10 +167,7 @@ func (s *space) Init(ctx context.Context) (err error) { Store: s.storage, DeletionState: deletionState, } - s.settingsDocument, err = settingsdocument.NewSettingsDocument(deps, s.id) - if err != nil { - return - } + s.settingsDocument = settingsdocument.NewSettingsDocument(deps, s.id) objectGetter := newCommonSpaceGetter(s.id, s.aclList, s.cache, s.settingsDocument) s.syncService.Init(objectGetter)