Merge pull request #17 from anytypeio/object-delete

This commit is contained in:
Mikhail Rakhmanov 2022-11-30 18:59:11 +01:00 committed by GitHub
commit f749e967f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 4049 additions and 545 deletions

View File

@ -23,6 +23,8 @@ type Controller interface {
// CreateDocument creates new document in space
CreateDocument(spaceId string) (id string, err error)
// DeleteDocument deletes a document from space
DeleteDocument(spaceId, documentId string) (err error)
// AllDocumentIds gets all ids of documents in space
AllDocumentIds(spaceId string) (ids []string, err error)
// AddText adds text to space document
@ -97,6 +99,10 @@ func (c *controller) CreateDocument(spaceId string) (id string, err error) {
return c.docService.CreateDocument(spaceId)
}
func (c *controller) DeleteDocument(spaceId, documentId string) (err error) {
return c.docService.DeleteDocument(spaceId, documentId)
}
func (c *controller) AllDocumentIds(spaceId string) (ids []string, err error) {
return c.docService.AllDocumentIds(spaceId)
}

View File

@ -65,6 +65,7 @@ func (s *service) Run(ctx context.Context) (err error) {
mux.HandleFunc("/loadSpace", s.loadSpace)
mux.HandleFunc("/allSpaceIds", s.allSpaceIds)
mux.HandleFunc("/createDocument", s.createDocument)
mux.HandleFunc("/deleteDocument", s.deleteDocument)
mux.HandleFunc("/allDocumentIds", s.allDocumentIds)
mux.HandleFunc("/addText", s.addText)
mux.HandleFunc("/dumpDocumentTree", s.dumpDocumentTree)
@ -134,6 +135,18 @@ func (s *service) createDocument(w http.ResponseWriter, req *http.Request) {
sendText(w, http.StatusOK, id)
}
func (s *service) deleteDocument(w http.ResponseWriter, req *http.Request) {
query := req.URL.Query()
spaceId := query.Get("spaceId")
documentId := query.Get("documentId")
err := s.controller.DeleteDocument(spaceId, documentId)
if err != nil {
sendText(w, http.StatusInternalServerError, err.Error())
return
}
sendText(w, http.StatusOK, documentId)
}
func (s *service) allDocumentIds(w http.ResponseWriter, req *http.Request) {
query := req.URL.Query()
spaceId := query.Get("spaceId")

View File

@ -103,6 +103,20 @@ func (c *treeCache) GetTree(ctx context.Context, spaceId, id string) (tr tree.Ob
if err != nil {
return
}
tr = doc.Tree()
// we have to do this trick, otherwise the compiler won't understand that TextDocument conforms to SyncHandler interface
tr = doc.InnerTree()
return
}
func (c *treeCache) DeleteTree(ctx context.Context, spaceId, treeId string) (err error) {
tr, err := c.GetTree(ctx, spaceId, treeId)
if err != nil {
return
}
err = tr.Delete()
if err != nil {
return
}
_, err = c.cache.Remove(treeId)
return
}

View File

@ -19,22 +19,34 @@ func (r *rpcHandler) PullSpace(ctx context.Context, request *spacesyncproto.Pull
return
}
description := sp.Description()
spaceDesc, err := sp.Description()
if err != nil {
err = spacesyncproto.ErrUnexpected
return
}
resp = &spacesyncproto.PullSpaceResponse{
SpaceHeader: description.SpaceHeader,
AclPayload: description.AclPayload,
AclPayloadId: description.AclId,
Payload: &spacesyncproto.SpacePayload{
SpaceHeader: spaceDesc.SpaceHeader,
AclPayloadId: spaceDesc.AclId,
AclPayload: spaceDesc.AclPayload,
SpaceSettingsPayload: spaceDesc.SpaceSettingsPayload,
SpaceSettingsPayloadId: spaceDesc.SpaceSettingsId,
},
}
return
}
func (r *rpcHandler) PushSpace(ctx context.Context, req *spacesyncproto.PushSpaceRequest) (resp *spacesyncproto.PushSpaceResponse, err error) {
description := commonspace.SpaceDescription{
SpaceHeader: req.SpaceHeader,
AclId: req.AclPayloadId,
AclPayload: req.AclPayload,
SpaceHeader: req.Payload.SpaceHeader,
AclId: req.Payload.AclPayloadId,
AclPayload: req.Payload.AclPayload,
SpaceSettingsPayload: req.Payload.SpaceSettingsPayload,
SpaceSettingsId: req.Payload.SpaceSettingsPayloadId,
}
err = r.s.AddSpace(ctx, description)
ctx = context.WithValue(ctx, commonspace.AddSpaceCtxKey, description)
_, err = r.s.GetSpace(ctx, description.SpaceHeader.GetId())
if err != nil {
return
}

View File

@ -23,7 +23,6 @@ func New() Service {
type Service interface {
GetSpace(ctx context.Context, id string) (commonspace.Space, error)
AddSpace(ctx context.Context, description commonspace.SpaceDescription) (err error)
CreateSpace(ctx context.Context, payload commonspace.SpaceCreatePayload) (commonspace.Space, error)
DeriveSpace(ctx context.Context, payload commonspace.SpaceDerivePayload) (commonspace.Space, error)
app.ComponentRunnable
@ -91,10 +90,6 @@ func (s *service) GetSpace(ctx context.Context, id string) (container commonspac
return v.(commonspace.Space), nil
}
func (s *service) AddSpace(ctx context.Context, description commonspace.SpaceDescription) (err error) {
return s.commonSpace.AddSpace(ctx, description)
}
func (s *service) loadSpace(ctx context.Context, id string) (value ocache.Object, err error) {
cc, err := s.commonSpace.NewSpace(ctx, id)
if err != nil {

View File

@ -14,6 +14,7 @@ import (
type Service interface {
app.Component
CreateDocument(spaceId string) (id string, err error)
DeleteDocument(spaceId, documentId string) (err error)
AllDocumentIds(spaceId string) (ids []string, err error)
AddText(spaceId, documentId, text string) (err error)
DumpDocumentTree(spaceId, documentId string) (dump string, err error)
@ -53,10 +54,18 @@ func (s *service) CreateDocument(spaceId string) (id string, err error) {
if err != nil {
return
}
id = doc.Tree().ID()
id = doc.ID()
return
}
func (s *service) DeleteDocument(spaceId, documentId string) (err error) {
space, err := s.spaceService.GetSpace(context.Background(), spaceId)
if err != nil {
return
}
return space.DeleteTree(context.Background(), documentId)
}
func (s *service) AllDocumentIds(spaceId string) (ids []string, err error) {
space, err := s.spaceService.GetSpace(context.Background(), spaceId)
if err != nil {
@ -79,5 +88,5 @@ func (s *service) DumpDocumentTree(spaceId, documentId string) (dump string, err
if err != nil {
return
}
return doc.Tree().DebugDump()
return doc.DebugDump()
}

View File

@ -11,7 +11,8 @@ import (
)
type TextDocument interface {
Tree() tree.ObjectTree
tree.ObjectTree
InnerTree() tree.ObjectTree
AddText(text string) error
Text() (string, error)
TreeDump() string
@ -19,7 +20,7 @@ type TextDocument interface {
}
type textDocument struct {
objTree tree.ObjectTree
tree.ObjectTree
account account.Service
}
@ -39,8 +40,8 @@ func CreateTextDocument(
}
return &textDocument{
objTree: t,
account: account,
ObjectTree: t,
account: account,
}, nil
}
@ -50,13 +51,13 @@ func NewTextDocument(ctx context.Context, space commonspace.Space, id string, li
return
}
return &textDocument{
objTree: t,
account: account,
ObjectTree: t,
account: account,
}, nil
}
func (t *textDocument) Tree() tree.ObjectTree {
return t.objTree
func (t *textDocument) InnerTree() tree.ObjectTree {
return t.ObjectTree
}
func (t *textDocument) AddText(text string) (err error) {
@ -73,9 +74,9 @@ func (t *textDocument) AddText(text string) (err error) {
if err != nil {
return
}
t.objTree.Lock()
defer t.objTree.Unlock()
_, err = t.objTree.AddContent(context.Background(), tree.SignableChangeContent{
t.Lock()
defer t.Unlock()
_, err = t.AddContent(context.Background(), tree.SignableChangeContent{
Data: res,
Key: t.account.Account().SignKey,
Identity: t.account.Account().Identity,
@ -85,10 +86,10 @@ func (t *textDocument) AddText(text string) (err error) {
}
func (t *textDocument) Text() (text string, err error) {
t.objTree.RLock()
defer t.objTree.RUnlock()
t.RLock()
defer t.RUnlock()
err = t.objTree.Iterate(
err = t.Iterate(
func(decrypted []byte) (any, error) {
textChange := &testchanges.TextData{}
err = proto.Unmarshal(decrypted, textChange)
@ -110,7 +111,3 @@ func (t *textDocument) Text() (text string, err error) {
func (t *textDocument) TreeDump() string {
return t.TreeDump()
}
func (t *textDocument) Close() error {
return nil
}

View File

@ -31,18 +31,20 @@ func (a aclKeys) RawRecordKey(id string) []byte {
}
type treeKeys struct {
id string
spaceId string
headsKey []byte
rootKey []byte
id string
spaceId string
headsKey []byte
rootKey []byte
rawChangePrefix []byte
}
func newTreeKeys(spaceId, id string) treeKeys {
return treeKeys{
id: id,
spaceId: spaceId,
headsKey: storage.JoinStringsToBytes("space", spaceId, "t", id, "heads"),
rootKey: storage.JoinStringsToBytes("space", spaceId, "t", "rootId", id),
id: id,
spaceId: spaceId,
headsKey: storage.JoinStringsToBytes("space", spaceId, "t", id, "heads"),
rootKey: storage.JoinStringsToBytes("space", spaceId, "t", "rootId", id),
rawChangePrefix: storage.JoinStringsToBytes("space", spaceId, "t", id),
}
}
@ -58,15 +60,23 @@ func (t treeKeys) RawChangeKey(id string) []byte {
return storage.JoinStringsToBytes("space", t.spaceId, "t", t.id, id)
}
func (t treeKeys) RawChangePrefix() []byte {
return t.rawChangePrefix
}
type spaceKeys struct {
headerKey []byte
treePrefixKey []byte
spaceId string
headerKey []byte
treePrefixKey []byte
spaceSettingsIdKey []byte
}
func newSpaceKeys(spaceId string) spaceKeys {
return spaceKeys{
headerKey: storage.JoinStringsToBytes("space", "header", spaceId),
treePrefixKey: storage.JoinStringsToBytes("space", spaceId, "t", "rootId"),
spaceId: spaceId,
headerKey: storage.JoinStringsToBytes("space", "header", spaceId),
treePrefixKey: storage.JoinStringsToBytes("space", spaceId, "t", "rootId"),
spaceSettingsIdKey: storage.JoinStringsToBytes("space", spaceId, "spaceSettingsId"),
}
}
@ -78,6 +88,14 @@ func (s spaceKeys) TreeRootPrefix() []byte {
return s.treePrefixKey
}
func (s spaceKeys) SpaceSettingsId() []byte {
return s.spaceSettingsIdKey
}
func (s spaceKeys) TreeDeletedKey(id string) []byte {
return storage.JoinStringsToBytes("space", s.spaceId, "deleted", id)
}
type storageServiceKeys struct {
spacePrefix []byte
}

View File

@ -4,19 +4,21 @@ import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
storage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
"github.com/dgraph-io/badger/v3"
"sync"
)
type spaceStorage struct {
spaceId string
objDb *badger.DB
keys spaceKeys
aclStorage storage.ListStorage
header *spacesyncproto.RawSpaceHeaderWithId
mx sync.Mutex
spaceId string
spaceSettingsId string
objDb *badger.DB
keys spaceKeys
aclStorage storage.ListStorage
header *spacesyncproto.RawSpaceHeaderWithId
}
var spaceValidationFunc = spacestorage.ValidateSpaceStorageCreatePayload
func newSpaceStorage(objDb *badger.DB, spaceId string) (store spacestorage.SpaceStorage, err error) {
keys := newSpaceKeys(spaceId)
err = objDb.View(func(txn *badger.Txn) error {
@ -30,10 +32,15 @@ func newSpaceStorage(objDb *badger.DB, spaceId string) (store spacestorage.Space
return err
}
spaceSettingsId, err := getTxn(txn, keys.SpaceSettingsId())
if err != nil {
return err
}
store = &spaceStorage{
spaceId: spaceId,
objDb: objDb,
keys: keys,
spaceId: spaceId,
spaceSettingsId: string(spaceSettingsId),
objDb: objDb,
keys: keys,
header: &spacesyncproto.RawSpaceHeaderWithId{
RawHeader: header,
Id: spaceId,
@ -54,8 +61,32 @@ func createSpaceStorage(db *badger.DB, payload spacestorage.SpaceStorageCreatePa
err = spacesyncproto.ErrSpaceExists
return
}
err = spaceValidationFunc(payload)
if err != nil {
return
}
spaceStore := &spaceStorage{
spaceId: payload.SpaceHeaderWithId.Id,
objDb: db,
keys: keys,
spaceSettingsId: payload.SpaceSettingsWithId.Id,
header: payload.SpaceHeaderWithId,
}
_, err = spaceStore.CreateTreeStorage(storage.TreeStorageCreatePayload{
RootRawChange: payload.SpaceSettingsWithId,
Changes: []*treechangeproto.RawTreeChangeWithId{payload.SpaceSettingsWithId},
Heads: []string{payload.SpaceSettingsWithId.Id},
})
if err != nil {
return
}
err = db.Update(func(txn *badger.Txn) error {
aclStorage, err := createListStorage(payload.SpaceHeaderWithId.Id, db, txn, payload.RecWithId)
err = txn.Set(keys.SpaceSettingsId(), []byte(payload.SpaceSettingsWithId.Id))
if err != nil {
return err
}
aclStorage, err := createListStorage(payload.SpaceHeaderWithId.Id, db, txn, payload.AclWithId)
if err != nil {
return err
}
@ -65,15 +96,10 @@ func createSpaceStorage(db *badger.DB, payload spacestorage.SpaceStorageCreatePa
return err
}
store = &spaceStorage{
spaceId: payload.SpaceHeaderWithId.Id,
objDb: db,
keys: keys,
aclStorage: aclStorage,
header: payload.SpaceHeaderWithId,
}
spaceStore.aclStorage = aclStorage
return nil
})
store = spaceStore
return
}
@ -81,15 +107,15 @@ func (s *spaceStorage) Id() string {
return s.spaceId
}
func (s *spaceStorage) SpaceSettingsId() string {
return s.spaceSettingsId
}
func (s *spaceStorage) TreeStorage(id string) (storage.TreeStorage, error) {
return newTreeStorage(s.objDb, s.spaceId, id)
}
func (s *spaceStorage) CreateTreeStorage(payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) {
// we have mutex here, so we prevent overwriting the heads of a tree on concurrent creation
s.mx.Lock()
defer s.mx.Unlock()
return createTreeStorage(s.objDb, s.spaceId, payload)
}
@ -112,7 +138,8 @@ func (s *spaceStorage) StoredIds() (ids []string, err error) {
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
id := item.Key()
id := make([]byte, 0, len(item.Key()))
id = item.KeyCopy(id)
if len(id) <= len(s.keys.TreeRootPrefix())+1 {
continue
}
@ -124,6 +151,27 @@ func (s *spaceStorage) StoredIds() (ids []string, err error) {
return
}
func (s *spaceStorage) SetTreeDeletedStatus(id, status string) (err error) {
return s.objDb.Update(func(txn *badger.Txn) error {
return txn.Set(s.keys.TreeDeletedKey(id), []byte(status))
})
}
func (s *spaceStorage) TreeDeletedStatus(id string) (status string, err error) {
err = s.objDb.View(func(txn *badger.Txn) error {
res, err := getTxn(txn, s.keys.TreeDeletedKey(id))
if err != nil {
return err
}
status = string(res)
return nil
})
if err == badger.ErrKeyNotFound {
err = nil
}
return
}
func (s *spaceStorage) Close() (err error) {
return nil
}

View File

@ -4,7 +4,9 @@ import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
"github.com/stretchr/testify/require"
"sort"
"strconv"
"testing"
)
@ -18,9 +20,14 @@ func spaceTestPayload() spacestorage.SpaceStorageCreatePayload {
Payload: []byte("aclRoot"),
Id: "aclRootId",
}
settings := &treechangeproto.RawTreeChangeWithId{
RawChange: []byte("settings"),
Id: "settingsId",
}
return spacestorage.SpaceStorageCreatePayload{
RecWithId: aclRoot,
SpaceHeaderWithId: header,
AclWithId: aclRoot,
SpaceHeaderWithId: header,
SpaceSettingsWithId: settings,
}
}
@ -31,7 +38,7 @@ func testSpace(t *testing.T, store spacestorage.SpaceStorage, payload spacestora
aclStorage, err := store.ACLStorage()
require.NoError(t, err)
testList(t, aclStorage, payload.RecWithId, payload.RecWithId.Id)
testList(t, aclStorage, payload.AclWithId, payload.AclWithId.Id)
}
func TestSpaceStorage_Create(t *testing.T) {
@ -68,7 +75,7 @@ func TestSpaceStorage_NewAndCreateTree(t *testing.T) {
require.NoError(t, err)
testSpace(t, store, payload)
t.Run("create tree and get tree", func(t *testing.T) {
t.Run("create tree, get tree and mark deleted", func(t *testing.T) {
payload := treeTestPayload()
treeStore, err := store.CreateTreeStorage(payload)
require.NoError(t, err)
@ -77,6 +84,14 @@ func TestSpaceStorage_NewAndCreateTree(t *testing.T) {
otherStore, err := store.TreeStorage(payload.RootRawChange.Id)
require.NoError(t, err)
testTreePayload(t, otherStore, payload)
initialStatus := "deleted"
err = store.SetTreeDeletedStatus(otherStore.Id(), initialStatus)
require.NoError(t, err)
status, err := store.TreeDeletedStatus(otherStore.Id())
require.NoError(t, err)
require.Equal(t, initialStatus, status)
})
}
@ -101,8 +116,11 @@ func TestSpaceStorage_StoredIds(t *testing.T) {
_, err := store.CreateTreeStorage(treePayload)
require.NoError(t, err)
}
ids = append(ids, payload.SpaceSettingsWithId.Id)
sort.Strings(ids)
storedIds, err := store.StoredIds()
require.NoError(t, err)
sort.Strings(storedIds)
require.Equal(t, ids, storedIds)
}

View File

@ -136,3 +136,46 @@ func (t *treeStorage) GetRawChange(ctx context.Context, id string) (raw *treecha
func (t *treeStorage) HasChange(ctx context.Context, id string) (bool, error) {
return hasDB(t.db, t.keys.RawChangeKey(id)), nil
}
func (t *treeStorage) Delete() (err error) {
storedKeys, err := t.storedKeys()
if err != nil {
return
}
err = t.db.Update(func(txn *badger.Txn) error {
for _, k := range storedKeys {
err = txn.Delete(k)
if err != nil {
return err
}
}
return nil
})
return
}
func (t *treeStorage) storedKeys() (keys [][]byte, err error) {
err = t.db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchValues = false
// this will get all raw changes and also "heads"
opts.Prefix = t.keys.RawChangePrefix()
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
key := item.Key()
keyCopy := make([]byte, 0, len(key))
keyCopy = item.KeyCopy(keyCopy)
keys = append(keys, keyCopy)
}
return nil
})
if err != nil {
return
}
keys = append(keys, t.keys.RootIdKey())
return
}

View File

@ -61,6 +61,42 @@ func (fx *fixture) stop(t *testing.T) {
require.NoError(t, fx.db.Close())
}
func (fx *fixture) testNoKeysExist(t *testing.T, spaceId, treeId string) {
treeKeys := newTreeKeys(spaceId, treeId)
var keys [][]byte
err := fx.db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.PrefetchValues = false
opts.Prefix = treeKeys.RawChangePrefix()
it := txn.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
key := item.Key()
keyCopy := make([]byte, 0, len(key))
keyCopy = item.KeyCopy(key)
keys = append(keys, keyCopy)
}
return nil
})
require.NoError(t, err)
require.Equal(t, 0, len(keys))
err = fx.db.View(func(txn *badger.Txn) error {
_, err = getTxn(txn, treeKeys.RootIdKey())
require.Equal(t, err, badger.ErrKeyNotFound)
_, err = getTxn(txn, treeKeys.HeadsKey())
require.Equal(t, err, badger.ErrKeyNotFound)
return nil
})
}
func TestTreeStorage_Create(t *testing.T) {
fx := newFixture(t)
fx.open(t)
@ -121,3 +157,32 @@ func TestTreeStorage_Methods(t *testing.T) {
require.False(t, has)
})
}
func TestTreeStorage_Delete(t *testing.T) {
fx := newFixture(t)
fx.open(t)
payload := treeTestPayload()
spaceId := "spaceId"
_, err := createTreeStorage(fx.db, spaceId, payload)
require.NoError(t, err)
fx.stop(t)
fx.open(t)
defer fx.stop(t)
store, err := newTreeStorage(fx.db, spaceId, payload.RootRawChange.Id)
require.NoError(t, err)
testTreePayload(t, store, payload)
t.Run("add raw change, get change and has change", func(t *testing.T) {
newChange := &treechangeproto.RawTreeChangeWithId{RawChange: []byte("ab"), Id: "newId"}
require.NoError(t, store.AddRawChange(newChange))
err = store.Delete()
require.NoError(t, err)
_, err = newTreeStorage(fx.db, spaceId, payload.RootRawChange.Id)
require.Equal(t, err, storage.ErrUnknownTreeId)
fx.testNoKeysExist(t, spaceId, payload.RootRawChange.Id)
})
}

View File

@ -0,0 +1,78 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/account (interfaces: Service)
// Package mock_account is a generated GoMock package.
package mock_account
import (
reflect "reflect"
app "github.com/anytypeio/go-anytype-infrastructure-experiments/common/app"
account "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/account"
gomock "github.com/golang/mock/gomock"
)
// MockService is a mock of Service interface.
type MockService struct {
ctrl *gomock.Controller
recorder *MockServiceMockRecorder
}
// MockServiceMockRecorder is the mock recorder for MockService.
type MockServiceMockRecorder struct {
mock *MockService
}
// NewMockService creates a new mock instance.
func NewMockService(ctrl *gomock.Controller) *MockService {
mock := &MockService{ctrl: ctrl}
mock.recorder = &MockServiceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockService) EXPECT() *MockServiceMockRecorder {
return m.recorder
}
// Account mocks base method.
func (m *MockService) Account() *account.AccountData {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Account")
ret0, _ := ret[0].(*account.AccountData)
return ret0
}
// Account indicates an expected call of Account.
func (mr *MockServiceMockRecorder) Account() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Account", reflect.TypeOf((*MockService)(nil).Account))
}
// Init mocks base method.
func (m *MockService) 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 *MockServiceMockRecorder) Init(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockService)(nil).Init), arg0)
}
// Name mocks base method.
func (m *MockService) 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 *MockServiceMockRecorder) Name() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockService)(nil).Name))
}

View File

@ -1,3 +1,4 @@
//go:generate mockgen -destination mock_account/mock_account.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/account Service
package account
import (

View File

@ -3,6 +3,7 @@ package commonspace
import (
"context"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/objectgetter"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncacl"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/treegetter"
)
@ -11,13 +12,15 @@ type commonSpaceGetter struct {
spaceId string
aclList *syncacl.SyncACL
treeGetter treegetter.TreeGetter
settings settingsdocument.SettingsDocument
}
func newCommonSpaceGetter(spaceId string, aclList *syncacl.SyncACL, treeGetter treegetter.TreeGetter) objectgetter.ObjectGetter {
func newCommonSpaceGetter(spaceId string, aclList *syncacl.SyncACL, treeGetter treegetter.TreeGetter, settings settingsdocument.SettingsDocument) objectgetter.ObjectGetter {
return &commonSpaceGetter{
spaceId: spaceId,
aclList: aclList,
treeGetter: treeGetter,
settings: settings,
}
}
@ -26,6 +29,10 @@ func (c *commonSpaceGetter) GetObject(ctx context.Context, objectId string) (obj
obj = c.aclList
return
}
if c.settings.ID() == objectId {
obj = c.settings.(objectgetter.Object)
return
}
t, err := c.treeGetter.GetTree(ctx, c.spaceId, objectId)
if err != nil {
return

View File

@ -0,0 +1,28 @@
package commonspace
import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
treestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
)
type commonStorage struct {
storage.SpaceStorage
}
func newCommonStorage(spaceStorage storage.SpaceStorage) storage.SpaceStorage {
return &commonStorage{
SpaceStorage: spaceStorage,
}
}
func (c *commonStorage) CreateTreeStorage(payload treestorage.TreeStorageCreatePayload) (store treestorage.TreeStorage, err error) {
status, err := c.TreeDeletedStatus(payload.RootRawChange.Id)
if err != nil {
return
}
if status == "" {
return c.SpaceStorage.CreateTreeStorage(payload)
}
err = storage.ErrTreeStorageAlreadyDeleted
return
}

View File

@ -1,14 +1,16 @@
//go:generate mockgen -destination mock_diffservice/mock_diffservice.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice DiffSyncer,PeriodicSync
//go:generate mockgen -destination mock_diffservice/mock_diffservice.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice DiffSyncer
package diffservice
import (
"context"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/remotediff"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate"
"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/treegetter"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/ldiff"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/periodicsync"
"go.uber.org/zap"
"strings"
)
@ -16,19 +18,20 @@ import (
type DiffService interface {
HeadNotifiable
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
RemoveObject(id string)
RemoveObjects(ids []string)
AllIds() []string
Init(objectIds []string)
Init(objectIds []string, deletionState deletionstate.DeletionState)
Close() (err error)
}
type diffService struct {
spaceId string
periodicSync PeriodicSync
periodicSync periodicsync.PeriodicSync
storage storage.SpaceStorage
diff ldiff.Diff
log *zap.Logger
syncer DiffSyncer
syncPeriod int
}
@ -45,11 +48,12 @@ func NewDiffService(
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)
periodicSync := periodicsync.NewPeriodicSync(syncPeriod, syncer.Sync, l)
return &diffService{
spaceId: spaceId,
storage: storage,
syncer: syncer,
periodicSync: periodicSync,
diff: diff,
log: log,
@ -57,8 +61,9 @@ func NewDiffService(
}
}
func (d *diffService) Init(objectIds []string) {
func (d *diffService) Init(objectIds []string, deletionState deletionstate.DeletionState) {
d.fillDiff(objectIds)
d.syncer.Init(deletionState)
d.periodicSync.Run()
}
@ -67,19 +72,15 @@ func (d *diffService) HandleRangeRequest(ctx context.Context, req *spacesyncprot
}
func (d *diffService) UpdateHeads(id string, heads []string) {
d.diff.Set(ldiff.Element{
Id: id,
Head: concatStrings(heads),
})
d.syncer.UpdateHeads(id, heads)
}
func (d *diffService) AllIds() []string {
return d.diff.Ids()
}
func (d *diffService) RemoveObject(id string) {
// TODO: add space document to remove ids
d.diff.RemoveId(id)
func (d *diffService) RemoveObjects(ids []string) {
d.syncer.RemoveObjects(ids)
}
func (d *diffService) Close() (err error) {

View File

@ -3,10 +3,12 @@ package diffservice
import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app/logger"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice/mock_diffservice"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate/mock_deletionstate"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage/mock_storage"
mock_storage2 "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage/mock_storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/ldiff"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/ldiff/mock_ldiff"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/periodicsync/mock_periodicsync"
"github.com/golang/mock/gomock"
"testing"
)
@ -17,10 +19,12 @@ func TestDiffService(t *testing.T) {
spaceId := "spaceId"
l := logger.NewNamed("sync")
pSyncMock := mock_diffservice.NewMockPeriodicSync(ctrl)
pSyncMock := mock_periodicsync.NewMockPeriodicSync(ctrl)
storageMock := mock_storage.NewMockSpaceStorage(ctrl)
treeStorageMock := mock_storage2.NewMockTreeStorage(ctrl)
diffMock := mock_ldiff.NewMockDiff(ctrl)
syncer := mock_diffservice.NewMockDiffSyncer(ctrl)
delState := mock_deletionstate.NewMockDeletionState(ctrl)
syncPeriod := 1
initId := "initId"
@ -28,6 +32,7 @@ func TestDiffService(t *testing.T) {
spaceId: spaceId,
storage: storageMock,
periodicSync: pSyncMock,
syncer: syncer,
diff: diffMock,
log: l,
syncPeriod: syncPeriod,
@ -36,22 +41,25 @@ func TestDiffService(t *testing.T) {
t.Run("init", func(t *testing.T) {
storageMock.EXPECT().TreeStorage(initId).Return(treeStorageMock, nil)
treeStorageMock.EXPECT().Heads().Return([]string{"h1", "h2"}, nil)
syncer.EXPECT().Init(delState)
diffMock.EXPECT().Set(ldiff.Element{
Id: initId,
Head: "h1h2",
})
pSyncMock.EXPECT().Run()
service.Init([]string{initId})
service.Init([]string{initId}, delState)
})
t.Run("update heads", func(t *testing.T) {
diffMock.EXPECT().Set(ldiff.Element{
Id: initId,
Head: "h1h2",
})
syncer.EXPECT().UpdateHeads(initId, []string{"h1", "h2"})
service.UpdateHeads(initId, []string{"h1", "h2"})
})
t.Run("remove objects", func(t *testing.T) {
syncer.EXPECT().RemoveObjects([]string{"h1", "h2"})
service.RemoveObjects([]string{"h1", "h2"})
})
t.Run("close", func(t *testing.T) {
pSyncMock.EXPECT().Close()
service.Close()

View File

@ -3,6 +3,7 @@ package diffservice
import (
"context"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/remotediff"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate"
"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/treegetter"
@ -16,6 +17,9 @@ import (
type DiffSyncer interface {
Sync(ctx context.Context) error
RemoveObjects(ids []string)
UpdateHeads(id string, heads []string)
Init(deletionState deletionstate.DeletionState)
}
func newDiffSyncer(
@ -45,6 +49,28 @@ type diffSyncer struct {
storage storage.SpaceStorage
clientFactory spacesyncproto.ClientFactory
log *zap.Logger
deletionState deletionstate.DeletionState
}
func (d *diffSyncer) Init(deletionState deletionstate.DeletionState) {
d.deletionState = deletionState
d.deletionState.AddObserver(d.RemoveObjects)
}
func (d *diffSyncer) RemoveObjects(ids []string) {
for _, id := range ids {
d.diff.RemoveId(id)
}
}
func (d *diffSyncer) UpdateHeads(id string, heads []string) {
if d.deletionState.Exists(id) {
return
}
d.diff.Set(ldiff.Element{
Id: id,
Head: concatStrings(heads),
})
}
func (d *diffSyncer) Sync(ctx context.Context) error {
@ -74,15 +100,17 @@ func (d *diffSyncer) syncWithPeer(ctx context.Context, p peer.Peer) (err error)
if err == spacesyncproto.ErrSpaceMissing {
return d.sendPushSpaceRequest(ctx, cl)
}
totalLen := len(newIds) + len(changedIds) + len(removedIds)
// not syncing ids which were removed through settings document
filteredIds := d.deletionState.FilterJoin(newIds, changedIds, removedIds)
ctx = peer.CtxWithPeerId(ctx, p.Id())
d.pingTreesInCache(ctx, newIds)
d.pingTreesInCache(ctx, changedIds)
d.pingTreesInCache(ctx, removedIds)
d.pingTreesInCache(ctx, filteredIds)
d.log.Info("sync done:", zap.Int("newIds", len(newIds)),
zap.Int("changedIds", len(changedIds)),
zap.Int("removedIds", len(removedIds)))
zap.Int("removedIds", len(removedIds)),
zap.Int("already deleted ids", totalLen-len(filteredIds)))
return
}
@ -108,10 +136,24 @@ func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, cl spacesyncproto
return
}
settingsStorage, err := d.storage.TreeStorage(d.storage.SpaceSettingsId())
if err != nil {
return
}
spaceSettingsRoot, err := settingsStorage.Root()
if err != nil {
return
}
spacePayload := &spacesyncproto.SpacePayload{
SpaceHeader: header,
AclPayload: root.Payload,
AclPayloadId: root.Id,
SpaceSettingsPayload: spaceSettingsRoot.RawChange,
SpaceSettingsPayloadId: spaceSettingsRoot.Id,
}
_, err = cl.PushSpace(ctx, &spacesyncproto.PushSpaceRequest{
SpaceHeader: header,
AclPayload: root.Payload,
AclPayloadId: root.Id,
Payload: spacePayload,
})
return
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app/logger"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/remotediff"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate/mock_deletionstate"
"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"
@ -13,6 +14,9 @@ import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf/mock_nodeconf"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
mock_aclstorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage/mock_storage"
mock_treestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage/mock_storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/ldiff"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/ldiff/mock_ldiff"
"github.com/golang/mock/gomock"
"github.com/libp2p/go-libp2p/core/sec"
@ -25,6 +29,7 @@ import (
type pushSpaceRequestMatcher struct {
spaceId string
aclRootId string
settingsId string
spaceHeader *spacesyncproto.RawSpaceHeaderWithId
}
@ -34,7 +39,7 @@ func (p pushSpaceRequestMatcher) Matches(x interface{}) bool {
return false
}
return res.AclPayloadId == p.aclRootId && res.SpaceHeader == p.spaceHeader
return res.Payload.AclPayloadId == p.aclRootId && res.Payload.SpaceHeader == p.spaceHeader && res.Payload.SpaceSettingsPayloadId == p.settingsId
}
func (p pushSpaceRequestMatcher) String() string {
@ -77,10 +82,12 @@ func (m mockPeer) NewStream(ctx context.Context, rpc string, enc drpc.Encoding)
func newPushSpaceRequestMatcher(
spaceId string,
aclRootId string,
settingsId string,
spaceHeader *spacesyncproto.RawSpaceHeaderWithId) *pushSpaceRequestMatcher {
return &pushSpaceRequestMatcher{
spaceId: spaceId,
aclRootId: aclRootId,
settingsId: settingsId,
spaceHeader: spaceHeader,
}
}
@ -99,18 +106,22 @@ func TestDiffSyncer_Sync(t *testing.T) {
factory := spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceClient {
return clientMock
})
delState := mock_deletionstate.NewMockDeletionState(ctrl)
spaceId := "spaceId"
aclRootId := "aclRootId"
l := logger.NewNamed(spaceId)
diffSyncer := newDiffSyncer(spaceId, diffMock, connectorMock, cacheMock, stMock, factory, l)
delState.EXPECT().AddObserver(gomock.Any())
diffSyncer.Init(delState)
t.Run("diff syncer sync simple", func(t *testing.T) {
t.Run("diff syncer sync", 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)
delState.EXPECT().FilterJoin(gomock.Any()).Return([]string{"new", "changed"})
for _, arg := range []string{"new", "changed"} {
cacheMock.EXPECT().
GetTree(gomock.Any(), spaceId, arg).
@ -127,12 +138,37 @@ func TestDiffSyncer_Sync(t *testing.T) {
require.Error(t, diffSyncer.Sync(ctx))
})
t.Run("deletion state remove objects", func(t *testing.T) {
deletedId := "id"
delState.EXPECT().Exists(deletedId).Return(true)
// this should not result in any mock being called
diffSyncer.UpdateHeads(deletedId, []string{"someHead"})
})
t.Run("update heads updates diff", func(t *testing.T) {
newId := "newId"
newHeads := []string{"h1", "h2"}
diffMock.EXPECT().Set(ldiff.Element{
Id: newId,
Head: concatStrings(newHeads),
})
delState.EXPECT().Exists(newId).Return(false)
diffSyncer.UpdateHeads(newId, newHeads)
})
t.Run("diff syncer sync space missing", func(t *testing.T) {
aclStorageMock := mock_aclstorage.NewMockListStorage(ctrl)
settingsStorage := mock_treestorage.NewMockTreeStorage(ctrl)
settingsId := "settingsId"
aclRoot := &aclrecordproto.RawACLRecordWithId{
Id: aclRootId,
}
settingsRoot := &treechangeproto.RawTreeChangeWithId{
Id: settingsId,
}
spaceHeader := &spacesyncproto.RawSpaceHeaderWithId{}
spaceSettingsId := "spaceSettingsId"
connectorMock.EXPECT().
GetResponsiblePeers(gomock.Any(), spaceId).
@ -140,17 +176,18 @@ func TestDiffSyncer_Sync(t *testing.T) {
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)
stMock.EXPECT().ACLStorage().Return(aclStorageMock, nil)
stMock.EXPECT().SpaceHeader().Return(spaceHeader, nil)
stMock.EXPECT().SpaceSettingsId().Return(spaceSettingsId)
stMock.EXPECT().TreeStorage(spaceSettingsId).Return(settingsStorage, nil)
settingsStorage.EXPECT().Root().Return(settingsRoot, nil)
aclStorageMock.EXPECT().
Root().
Return(aclRoot, nil)
clientMock.EXPECT().
PushSpace(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRootId, spaceHeader)).
PushSpace(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRootId, settingsId, spaceHeader)).
Return(nil, nil)
require.NoError(t, diffSyncer.Sync(ctx))

View File

@ -3,3 +3,9 @@ package diffservice
type HeadNotifiable interface {
UpdateHeads(id string, heads []string)
}
type HeadNotifiableFunc func(id string, heads []string)
func (h HeadNotifiableFunc) UpdateHeads(id string, heads []string) {
h(id, heads)
}

View File

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice (interfaces: DiffSyncer,PeriodicSync)
// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice (interfaces: DiffSyncer)
// Package mock_diffservice is a generated GoMock package.
package mock_diffservice
@ -8,6 +8,7 @@ import (
context "context"
reflect "reflect"
deletionstate "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate"
gomock "github.com/golang/mock/gomock"
)
@ -34,6 +35,30 @@ func (m *MockDiffSyncer) EXPECT() *MockDiffSyncerMockRecorder {
return m.recorder
}
// Init mocks base method.
func (m *MockDiffSyncer) Init(arg0 deletionstate.DeletionState) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Init", arg0)
}
// Init indicates an expected call of Init.
func (mr *MockDiffSyncerMockRecorder) Init(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockDiffSyncer)(nil).Init), arg0)
}
// RemoveObjects mocks base method.
func (m *MockDiffSyncer) RemoveObjects(arg0 []string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "RemoveObjects", arg0)
}
// RemoveObjects indicates an expected call of RemoveObjects.
func (mr *MockDiffSyncerMockRecorder) RemoveObjects(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveObjects", reflect.TypeOf((*MockDiffSyncer)(nil).RemoveObjects), arg0)
}
// Sync mocks base method.
func (m *MockDiffSyncer) Sync(arg0 context.Context) error {
m.ctrl.T.Helper()
@ -48,49 +73,14 @@ func (mr *MockDiffSyncerMockRecorder) Sync(arg0 interface{}) *gomock.Call {
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() {
// UpdateHeads mocks base method.
func (m *MockDiffSyncer) UpdateHeads(arg0 string, arg1 []string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Close")
m.ctrl.Call(m, "UpdateHeads", arg0, arg1)
}
// Close indicates an expected call of Close.
func (mr *MockPeriodicSyncMockRecorder) Close() *gomock.Call {
// UpdateHeads indicates an expected call of UpdateHeads.
func (mr *MockDiffSyncerMockRecorder) UpdateHeads(arg0, arg1 interface{}) *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))
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateHeads", reflect.TypeOf((*MockDiffSyncer)(nil).UpdateHeads), arg0, arg1)
}

View File

@ -3,7 +3,9 @@ package commonspace
import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
aclrecordproto2 "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
aclrecordproto "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/common"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/tree"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/cid"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/asymmetric/signingkey"
"hash/fnv"
@ -11,6 +13,11 @@ import (
"time"
)
const (
SpaceSettingsChangeType = "reserved.spacesettings"
SpaceDerivationScheme = "derivation.standard"
)
func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload storage.SpaceStorageCreatePayload, err error) {
// unmarshalling signing and encryption keys
identity, err := payload.SigningKey.GetPublic().Raw()
@ -23,8 +30,8 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload st
}
// preparing header and space id
bytes := make([]byte, 32)
_, err = rand.Read(bytes)
spaceHeaderSeed := make([]byte, 32)
_, err = rand.Read(spaceHeaderSeed)
if err != nil {
return
}
@ -33,7 +40,7 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload st
Timestamp: time.Now().UnixNano(),
SpaceType: payload.SpaceType,
ReplicationKey: payload.ReplicationKey,
Seed: bytes,
Seed: spaceHeaderSeed,
}
marshalled, err := header.Marshal()
if err != nil {
@ -68,12 +75,11 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload st
}
// preparing acl
aclRoot := &aclrecordproto2.ACLRoot{
aclRoot := &aclrecordproto.ACLRoot{
Identity: identity,
EncryptionKey: encPubKey,
SpaceId: spaceId,
EncryptedReadKey: encReadKey,
DerivationScheme: "",
CurrentReadKeyHash: readKeyHash,
Timestamp: time.Now().UnixNano(),
}
@ -82,10 +88,31 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload st
return
}
builder := tree.NewChangeBuilder(common.NewKeychain(), nil)
spaceSettingsSeed := make([]byte, 32)
_, err = rand.Read(spaceSettingsSeed)
if err != nil {
return
}
_, settingsRoot, err := builder.BuildInitialContent(tree.InitialContent{
AclHeadId: rawWithId.Id,
Identity: aclRoot.Identity,
SigningKey: payload.SigningKey,
SpaceId: spaceId,
Seed: spaceSettingsSeed,
ChangeType: SpaceSettingsChangeType,
Timestamp: time.Now().UnixNano(),
})
if err != nil {
return
}
// creating storage
storagePayload = storage.SpaceStorageCreatePayload{
RecWithId: rawWithId,
SpaceHeaderWithId: rawHeaderWithId,
AclWithId: rawWithId,
SpaceHeaderWithId: rawHeaderWithId,
SpaceSettingsWithId: settingsRoot,
}
return
}
@ -144,7 +171,7 @@ func storagePayloadForSpaceDerive(payload SpaceDerivePayload) (storagePayload st
}
// deriving and encrypting read key
readKey, err := aclrecordproto2.ACLReadKeyDerive(signPrivKey, encPrivKey)
readKey, err := aclrecordproto.ACLReadKeyDerive(signPrivKey, encPrivKey)
if err != nil {
return
}
@ -160,29 +187,41 @@ func storagePayloadForSpaceDerive(payload SpaceDerivePayload) (storagePayload st
}
// preparing acl
aclRoot := &aclrecordproto2.ACLRoot{
aclRoot := &aclrecordproto.ACLRoot{
Identity: identity,
EncryptionKey: encPubKey,
SpaceId: spaceId,
EncryptedReadKey: encReadKey,
DerivationScheme: "",
DerivationScheme: SpaceDerivationScheme,
CurrentReadKeyHash: readKeyHash,
Timestamp: time.Now().UnixNano(),
}
rawWithId, err := marshalACLRoot(aclRoot, payload.SigningKey)
if err != nil {
return
}
builder := tree.NewChangeBuilder(common.NewKeychain(), nil)
_, settingsRoot, err := builder.BuildInitialContent(tree.InitialContent{
AclHeadId: rawWithId.Id,
Identity: aclRoot.Identity,
SigningKey: payload.SigningKey,
SpaceId: spaceId,
ChangeType: SpaceSettingsChangeType,
})
if err != nil {
return
}
// creating storage
storagePayload = storage.SpaceStorageCreatePayload{
RecWithId: rawWithId,
SpaceHeaderWithId: rawHeaderWithId,
AclWithId: rawWithId,
SpaceHeaderWithId: rawHeaderWithId,
SpaceSettingsWithId: settingsRoot,
}
return
}
func marshalACLRoot(aclRoot *aclrecordproto2.ACLRoot, key signingkey.PrivKey) (rawWithId *aclrecordproto2.RawACLRecordWithId, err error) {
func marshalACLRoot(aclRoot *aclrecordproto.ACLRoot, key signingkey.PrivKey) (rawWithId *aclrecordproto.RawACLRecordWithId, err error) {
marshalledRoot, err := aclRoot.Marshal()
if err != nil {
return
@ -191,7 +230,7 @@ func marshalACLRoot(aclRoot *aclrecordproto2.ACLRoot, key signingkey.PrivKey) (r
if err != nil {
return
}
raw := &aclrecordproto2.RawACLRecord{
raw := &aclrecordproto.RawACLRecord{
Payload: marshalledRoot,
Signature: signature,
}
@ -203,7 +242,7 @@ func marshalACLRoot(aclRoot *aclrecordproto2.ACLRoot, key signingkey.PrivKey) (r
if err != nil {
return
}
rawWithId = &aclrecordproto2.RawACLRecordWithId{
rawWithId = &aclrecordproto.RawACLRecordWithId{
Payload: marshalledRaw,
Id: aclHeadId,
}

View File

@ -15,6 +15,7 @@ import (
"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/common/pkg/acl/aclrecordproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
)
const CName = "common.commonspace"
@ -25,11 +26,14 @@ func New() Service {
return &service{}
}
type ctxKey int
const AddSpaceCtxKey ctxKey = 0
type Service interface {
DeriveSpace(ctx context.Context, payload SpaceDerivePayload) (string, error)
CreateSpace(ctx context.Context, payload SpaceCreatePayload) (string, error)
NewSpace(ctx context.Context, id string) (sp Space, err error)
AddSpace(ctx context.Context, spaceDescription SpaceDescription) (err error)
app.Component
}
@ -82,47 +86,23 @@ func (s *service) DeriveSpace(ctx context.Context, payload SpaceDerivePayload) (
return store.Id(), nil
}
func (s *service) AddSpace(ctx context.Context, spaceDescription SpaceDescription) (err error) {
_, err = s.storageProvider.SpaceStorage(spaceDescription.SpaceHeader.Id)
if err == nil {
err = spacesyncproto.ErrSpaceExists
return
}
if err != storage.ErrSpaceStorageMissing {
err = spacesyncproto.ErrUnexpected
return
}
payload := storage.SpaceStorageCreatePayload{
RecWithId: &aclrecordproto.RawACLRecordWithId{
Payload: spaceDescription.AclPayload,
Id: spaceDescription.AclId,
},
SpaceHeaderWithId: spaceDescription.SpaceHeader,
}
st, err := s.storageProvider.CreateSpaceStorage(payload)
if err != nil {
err = spacesyncproto.ErrUnexpected
if err == storage.ErrSpaceStorageExists {
err = spacesyncproto.ErrSpaceExists
}
return
}
err = st.Close()
return
}
func (s *service) NewSpace(ctx context.Context, id string) (Space, error) {
st, err := s.storageProvider.SpaceStorage(id)
if err != nil {
if err != spacesyncproto.ErrSpaceMissing {
if err != storage.ErrSpaceStorageMissing {
return nil, err
}
st, err = s.getSpaceStorageFromRemote(ctx, id)
if err != nil {
err = storage.ErrSpaceStorageMissing
return nil, err
if description, ok := ctx.Value(AddSpaceCtxKey).(SpaceDescription); ok {
st, err = s.addSpaceStorage(ctx, description)
if err != nil {
return nil, err
}
} else {
st, err = s.getSpaceStorageFromRemote(ctx, id)
if err != nil {
return nil, err
}
}
}
@ -142,10 +122,33 @@ func (s *service) NewSpace(ctx context.Context, id string) (Space, error) {
return sp, nil
}
func (s *service) addSpaceStorage(ctx context.Context, spaceDescription SpaceDescription) (st storage.SpaceStorage, err error) {
payload := storage.SpaceStorageCreatePayload{
AclWithId: &aclrecordproto.RawACLRecordWithId{
Payload: spaceDescription.AclPayload,
Id: spaceDescription.AclId,
},
SpaceHeaderWithId: spaceDescription.SpaceHeader,
SpaceSettingsWithId: &treechangeproto.RawTreeChangeWithId{
RawChange: spaceDescription.SpaceSettingsPayload,
Id: spaceDescription.SpaceSettingsId,
},
}
st, err = s.storageProvider.CreateSpaceStorage(payload)
if err != nil {
err = spacesyncproto.ErrUnexpected
if err == storage.ErrSpaceStorageExists {
err = spacesyncproto.ErrSpaceExists
}
return
}
return
}
func (s *service) getSpaceStorageFromRemote(ctx context.Context, id string) (st storage.SpaceStorage, err error) {
var p peer.Peer
lastConfiguration := s.configurationService.GetLast()
// for nodes we always get remote space only if we have id in the context
// we can't connect to client if it is a node
if lastConfiguration.IsResponsible(id) {
err = spacesyncproto.ErrSpaceMissing
return
@ -161,12 +164,17 @@ func (s *service) getSpaceStorageFromRemote(ctx context.Context, id string) (st
if err != nil {
return
}
st, err = s.storageProvider.CreateSpaceStorage(storage.SpaceStorageCreatePayload{
RecWithId: &aclrecordproto.RawACLRecordWithId{
Payload: res.AclPayload,
Id: res.AclPayloadId,
AclWithId: &aclrecordproto.RawACLRecordWithId{
Payload: res.Payload.AclPayload,
Id: res.Payload.AclPayloadId,
},
SpaceHeaderWithId: res.SpaceHeader,
SpaceSettingsWithId: &treechangeproto.RawTreeChangeWithId{
RawChange: res.Payload.SpaceSettingsPayload,
Id: res.Payload.SpaceSettingsPayloadId,
},
SpaceHeaderWithId: res.Payload.SpaceHeader,
})
return
}

View File

@ -0,0 +1,53 @@
package settingsdocument
import (
"context"
)
type deleteLoop struct {
deleteCtx context.Context
deleteCancel context.CancelFunc
deleteChan chan struct{}
deleteFunc func()
loopDone chan struct{}
}
func newDeleteLoop(deleteFunc func()) *deleteLoop {
ctx, cancel := context.WithCancel(context.Background())
return &deleteLoop{
deleteCtx: ctx,
deleteCancel: cancel,
deleteChan: make(chan struct{}, 1),
deleteFunc: deleteFunc,
loopDone: make(chan struct{}),
}
}
func (dl *deleteLoop) Run() {
go dl.loop()
}
func (dl *deleteLoop) loop() {
defer close(dl.loopDone)
dl.deleteFunc()
for {
select {
case <-dl.deleteCtx.Done():
return
case <-dl.deleteChan:
dl.deleteFunc()
}
}
}
func (dl *deleteLoop) notify() {
select {
case dl.deleteChan <- struct{}{}:
default:
}
}
func (dl *deleteLoop) Close() {
dl.deleteCancel()
<-dl.loopDone
}

View File

@ -0,0 +1,39 @@
package settingsdocument
import (
"context"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/treegetter"
"go.uber.org/zap"
)
type Deleter interface {
Delete()
}
type deleter struct {
st storage.SpaceStorage
state deletionstate.DeletionState
getter treegetter.TreeGetter
}
func newDeleter(st storage.SpaceStorage, state deletionstate.DeletionState, getter treegetter.TreeGetter) Deleter {
return &deleter{st, state, getter}
}
func (d *deleter) Delete() {
allQueued := d.state.GetQueued()
for _, id := range allQueued {
err := d.getter.DeleteTree(context.Background(), d.st.Id(), id)
if err != nil && err != storage.ErrTreeStorageAlreadyDeleted {
log.With(zap.String("id", id), zap.Error(err)).Error("failed to delete object")
continue
}
err = d.state.Delete(id)
if err != nil {
log.With(zap.String("id", id), zap.Error(err)).Error("failed to mark object as deleted")
}
log.With(zap.String("id", id), zap.Error(err)).Debug("object successfully deleted")
}
}

View File

@ -0,0 +1,52 @@
package settingsdocument
import (
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate/mock_deletionstate"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage/mock_storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/treegetter/mock_treegetter"
"github.com/golang/mock/gomock"
"testing"
)
func TestDeleter_Delete(t *testing.T) {
ctrl := gomock.NewController(t)
treeGetter := mock_treegetter.NewMockTreeGetter(ctrl)
st := mock_storage.NewMockSpaceStorage(ctrl)
delState := mock_deletionstate.NewMockDeletionState(ctrl)
deleter := newDeleter(st, delState, treeGetter)
t.Run("deleter delete queued", func(t *testing.T) {
id := "id"
spaceId := "spaceId"
delState.EXPECT().GetQueued().Return([]string{id})
st.EXPECT().Id().Return(spaceId)
treeGetter.EXPECT().DeleteTree(gomock.Any(), spaceId, id).Return(nil)
delState.EXPECT().Delete(id).Return(nil)
deleter.Delete()
})
t.Run("deleter delete already deleted", func(t *testing.T) {
id := "id"
spaceId := "spaceId"
delState.EXPECT().GetQueued().Return([]string{id})
st.EXPECT().Id().Return(spaceId)
treeGetter.EXPECT().DeleteTree(gomock.Any(), spaceId, id).Return(storage.ErrTreeStorageAlreadyDeleted)
delState.EXPECT().Delete(id).Return(nil)
deleter.Delete()
})
t.Run("deleter delete error", func(t *testing.T) {
id := "id"
spaceId := "spaceId"
delState.EXPECT().GetQueued().Return([]string{id})
st.EXPECT().Id().Return(spaceId)
treeGetter.EXPECT().DeleteTree(gomock.Any(), spaceId, id).Return(fmt.Errorf("some error"))
deleter.Delete()
})
}

View File

@ -0,0 +1,153 @@
//go:generate mockgen -destination mock_deletionstate/mock_deletionstate.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate DeletionState
package deletionstate
import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
"sync"
)
type StateUpdateObserver func(ids []string)
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)
CreateDeleteChange(id string, isSnapshot bool) (res []byte, err error)
}
type deletionState struct {
sync.RWMutex
queued map[string]struct{}
deleted map[string]struct{}
stateUpdateObservers []StateUpdateObserver
storage storage.SpaceStorage
}
func NewDeletionState(storage storage.SpaceStorage) DeletionState {
return &deletionState{
queued: map[string]struct{}{},
deleted: map[string]struct{}{},
storage: storage,
}
}
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) {
st.Lock()
defer func() {
st.Unlock()
if err != nil {
return
}
for _, ob := range st.stateUpdateObservers {
ob(ids)
}
}()
for _, id := range ids {
if _, exists := st.deleted[id]; exists {
continue
}
if _, exists := st.queued[id]; exists {
continue
}
var status string
status, err = st.storage.TreeDeletedStatus(id)
if err != nil {
return
}
switch status {
case storage.TreeDeletedStatusQueued:
st.queued[id] = struct{}{}
case storage.TreeDeletedStatusDeleted:
st.deleted[id] = struct{}{}
default:
st.queued[id] = struct{}{}
err = st.storage.SetTreeDeletedStatus(id, storage.TreeDeletedStatusQueued)
if err != nil {
return
}
}
}
return
}
func (st *deletionState) GetQueued() (ids []string) {
st.RLock()
defer st.RUnlock()
ids = make([]string, 0, len(st.queued))
for id := range st.queued {
ids = append(ids, id)
}
return
}
func (st *deletionState) Delete(id string) (err error) {
st.Lock()
defer st.Unlock()
delete(st.queued, id)
st.deleted[id] = struct{}{}
err = st.storage.SetTreeDeletedStatus(id, storage.TreeDeletedStatusDeleted)
if err != nil {
return
}
return
}
func (st *deletionState) Exists(id string) bool {
st.RLock()
defer st.RUnlock()
return st.exists(id)
}
func (st *deletionState) FilterJoin(ids ...[]string) (filtered []string) {
st.RLock()
defer st.RUnlock()
filter := func(ids []string) {
for _, id := range ids {
if !st.exists(id) {
filtered = append(filtered, id)
}
}
}
for _, arr := range ids {
filter(arr)
}
return
}
func (st *deletionState) CreateDeleteChange(id string, isSnapshot bool) (res []byte, err error) {
content := &spacesyncproto.SpaceSettingsContent_ObjectDelete{
ObjectDelete: &spacesyncproto.ObjectDelete{Id: id},
}
change := &spacesyncproto.SettingsData{
Content: []*spacesyncproto.SpaceSettingsContent{
{content},
},
Snapshot: nil,
}
// TODO: add snapshot logic
res, err = change.Marshal()
return
}
func (st *deletionState) exists(id string) bool {
if _, exists := st.deleted[id]; exists {
return true
}
if _, exists := st.queued[id]; exists {
return true
}
return false
}

View File

@ -0,0 +1,127 @@
package deletionstate
import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage/mock_storage"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"testing"
)
type fixture struct {
ctrl *gomock.Controller
delState *deletionState
spaceStorage *mock_storage.MockSpaceStorage
}
func newFixture(t *testing.T) *fixture {
ctrl := gomock.NewController(t)
spaceStorage := mock_storage.NewMockSpaceStorage(ctrl)
delState := NewDeletionState(spaceStorage).(*deletionState)
return &fixture{
ctrl: ctrl,
delState: delState,
spaceStorage: spaceStorage,
}
}
func (fx *fixture) stop() {
fx.ctrl.Finish()
}
func TestDeletionState_Add(t *testing.T) {
t.Run("add new", func(t *testing.T) {
fx := newFixture(t)
defer fx.stop()
id := "newId"
fx.spaceStorage.EXPECT().TreeDeletedStatus(id).Return("", nil)
fx.spaceStorage.EXPECT().SetTreeDeletedStatus(id, storage.TreeDeletedStatusQueued).Return(nil)
err := fx.delState.Add([]string{id})
require.NoError(t, err)
require.Contains(t, fx.delState.queued, id)
})
t.Run("add existing queued", func(t *testing.T) {
fx := newFixture(t)
defer fx.stop()
id := "newId"
fx.spaceStorage.EXPECT().TreeDeletedStatus(id).Return(storage.TreeDeletedStatusQueued, nil)
err := fx.delState.Add([]string{id})
require.NoError(t, err)
require.Contains(t, fx.delState.queued, id)
})
t.Run("add existing deleted", func(t *testing.T) {
fx := newFixture(t)
defer fx.stop()
id := "newId"
fx.spaceStorage.EXPECT().TreeDeletedStatus(id).Return(storage.TreeDeletedStatusDeleted, nil)
err := fx.delState.Add([]string{id})
require.NoError(t, err)
require.Contains(t, fx.delState.deleted, id)
})
}
func TestDeletionState_GetQueued(t *testing.T) {
fx := newFixture(t)
defer fx.stop()
fx.delState.queued["id1"] = struct{}{}
fx.delState.queued["id2"] = struct{}{}
queued := fx.delState.GetQueued()
require.Equal(t, []string{"id1", "id2"}, queued)
}
func TestDeletionState_FilterJoin(t *testing.T) {
fx := newFixture(t)
defer fx.stop()
fx.delState.queued["id1"] = struct{}{}
fx.delState.queued["id2"] = struct{}{}
filtered := fx.delState.FilterJoin([]string{"id1"}, []string{"id3", "id2"}, []string{"id4"})
require.Equal(t, []string{"id3", "id4"}, filtered)
}
func TestDeletionState_AddObserver(t *testing.T) {
fx := newFixture(t)
defer fx.stop()
var queued []string
fx.delState.AddObserver(func(ids []string) {
queued = ids
})
id := "newId"
fx.spaceStorage.EXPECT().TreeDeletedStatus(id).Return("", nil)
fx.spaceStorage.EXPECT().SetTreeDeletedStatus(id, storage.TreeDeletedStatusQueued).Return(nil)
err := fx.delState.Add([]string{id})
require.NoError(t, err)
require.Contains(t, fx.delState.queued, id)
require.Equal(t, []string{id}, queued)
}
func TestDeletionState_Delete(t *testing.T) {
fx := newFixture(t)
defer fx.stop()
id := "deletedId"
fx.delState.queued[id] = struct{}{}
fx.spaceStorage.EXPECT().SetTreeDeletedStatus(id, storage.TreeDeletedStatusDeleted).Return(nil)
err := fx.delState.Delete(id)
require.NoError(t, err)
require.Contains(t, fx.delState.deleted, id)
require.NotContains(t, fx.delState.queued, id)
}
func TestDeletionState_Exists(t *testing.T) {
fx := newFixture(t)
defer fx.stop()
fx.delState.queued["id1"] = struct{}{}
fx.delState.deleted["id2"] = struct{}{}
require.True(t, fx.delState.Exists("id1"))
require.True(t, fx.delState.Exists("id2"))
require.False(t, fx.delState.Exists("id3"))
}

View File

@ -0,0 +1,136 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate (interfaces: DeletionState)
// Package mock_deletionstate is a generated GoMock package.
package mock_deletionstate
import (
reflect "reflect"
deletionstate "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate"
gomock "github.com/golang/mock/gomock"
)
// MockDeletionState is a mock of DeletionState interface.
type MockDeletionState struct {
ctrl *gomock.Controller
recorder *MockDeletionStateMockRecorder
}
// MockDeletionStateMockRecorder is the mock recorder for MockDeletionState.
type MockDeletionStateMockRecorder struct {
mock *MockDeletionState
}
// NewMockDeletionState creates a new mock instance.
func NewMockDeletionState(ctrl *gomock.Controller) *MockDeletionState {
mock := &MockDeletionState{ctrl: ctrl}
mock.recorder = &MockDeletionStateMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockDeletionState) EXPECT() *MockDeletionStateMockRecorder {
return m.recorder
}
// Add mocks base method.
func (m *MockDeletionState) Add(arg0 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Add", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Add indicates an expected call of Add.
func (mr *MockDeletionStateMockRecorder) Add(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockDeletionState)(nil).Add), arg0)
}
// AddObserver mocks base method.
func (m *MockDeletionState) AddObserver(arg0 deletionstate.StateUpdateObserver) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "AddObserver", arg0)
}
// AddObserver indicates an expected call of AddObserver.
func (mr *MockDeletionStateMockRecorder) AddObserver(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddObserver", reflect.TypeOf((*MockDeletionState)(nil).AddObserver), arg0)
}
// CreateDeleteChange mocks base method.
func (m *MockDeletionState) CreateDeleteChange(arg0 string, arg1 bool) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateDeleteChange", arg0, arg1)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateDeleteChange indicates an expected call of CreateDeleteChange.
func (mr *MockDeletionStateMockRecorder) CreateDeleteChange(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateDeleteChange", reflect.TypeOf((*MockDeletionState)(nil).CreateDeleteChange), arg0, arg1)
}
// Delete mocks base method.
func (m *MockDeletionState) Delete(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockDeletionStateMockRecorder) Delete(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockDeletionState)(nil).Delete), arg0)
}
// Exists mocks base method.
func (m *MockDeletionState) Exists(arg0 string) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Exists", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// Exists indicates an expected call of Exists.
func (mr *MockDeletionStateMockRecorder) Exists(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockDeletionState)(nil).Exists), arg0)
}
// FilterJoin mocks base method.
func (m *MockDeletionState) FilterJoin(arg0 ...[]string) []string {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "FilterJoin", varargs...)
ret0, _ := ret[0].([]string)
return ret0
}
// FilterJoin indicates an expected call of FilterJoin.
func (mr *MockDeletionStateMockRecorder) FilterJoin(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FilterJoin", reflect.TypeOf((*MockDeletionState)(nil).FilterJoin), arg0...)
}
// GetQueued mocks base method.
func (m *MockDeletionState) GetQueued() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetQueued")
ret0, _ := ret[0].([]string)
return ret0
}
// GetQueued indicates an expected call of GetQueued.
func (mr *MockDeletionStateMockRecorder) GetQueued() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueued", reflect.TypeOf((*MockDeletionState)(nil).GetQueued))
}

View File

@ -0,0 +1,59 @@
package settingsdocument
import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/tree"
"github.com/gogo/protobuf/proto"
)
type DeletedIdsProvider interface {
ProvideIds(tr tree.ObjectTree, startId string) (ids []string, lastId string, err error)
}
type provider struct{}
func (p *provider) processChange(change *tree.Change, rootId, startId string, ids []string) []string {
// ignoring root change which has empty model or startId change
if change.Model == nil || (change.Id == startId && startId != "") {
return ids
}
deleteChange := change.Model.(*spacesyncproto.SettingsData)
// getting data from snapshot if we start from it
if change.Id == rootId {
ids = deleteChange.Snapshot.DeletedIds
return ids
}
// otherwise getting data from content
for _, cnt := range deleteChange.Content {
if cnt.GetObjectDelete() != nil {
ids = append(ids, cnt.GetObjectDelete().GetId())
}
}
return ids
}
func (p *provider) ProvideIds(tr tree.ObjectTree, startId string) (ids []string, lastId string, err error) {
rootId := tr.Root().Id
process := func(change *tree.Change) bool {
lastId = change.Id
ids = p.processChange(change, rootId, startId, ids)
return true
}
convert := func(decrypted []byte) (res any, err error) {
deleteChange := &spacesyncproto.SettingsData{}
err = proto.Unmarshal(decrypted, deleteChange)
if err != nil {
return nil, err
}
return deleteChange, nil
}
if startId == "" {
err = tr.IterateFrom(tr.ID(), convert, process)
} else {
err = tr.IterateFrom(startId, convert, process)
}
return
}

View File

@ -0,0 +1,94 @@
package settingsdocument
import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/tree"
mock_tree "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/tree/mock_objecttree"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"testing"
)
func TestProvider_ProcessChange(t *testing.T) {
//ctrl := gomock.NewController(t)
//objTree := mock_tree.NewMockObjectTree(ctrl)
prov := &provider{}
//defer ctrl.Finish()
t.Run("empty model", func(t *testing.T) {
ch := &tree.Change{}
startId := "startId"
rootId := "rootId"
ids := []string{startId}
otherIds := prov.processChange(ch, rootId, startId, ids)
require.Equal(t, []string{startId}, otherIds)
})
t.Run("changeId is equal to startId", func(t *testing.T) {
ch := &tree.Change{}
ch.Model = &spacesyncproto.SettingsData{}
ch.Id = "startId"
startId := "startId"
rootId := "rootId"
ids := []string{startId}
otherIds := prov.processChange(ch, rootId, startId, ids)
require.Equal(t, []string{startId}, otherIds)
})
t.Run("changeId is equal to rootId, startId is empty", func(t *testing.T) {
ch := &tree.Change{}
ch.Model = &spacesyncproto.SettingsData{
Snapshot: &spacesyncproto.SpaceSettingsSnapshot{
DeletedIds: []string{"id1", "id2"},
},
}
ch.Id = "rootId"
startId := ""
rootId := "rootId"
otherIds := prov.processChange(ch, rootId, startId, nil)
require.Equal(t, []string{"id1", "id2"}, otherIds)
})
t.Run("changeId is equal to rootId, startId is empty", func(t *testing.T) {
ch := &tree.Change{}
ch.Model = &spacesyncproto.SettingsData{
Content: []*spacesyncproto.SpaceSettingsContent{
{&spacesyncproto.SpaceSettingsContent_ObjectDelete{
ObjectDelete: &spacesyncproto.ObjectDelete{Id: "id1"},
}},
},
}
ch.Id = "someId"
startId := "startId"
rootId := "rootId"
otherIds := prov.processChange(ch, rootId, startId, nil)
require.Equal(t, []string{"id1"}, otherIds)
})
}
func TestProvider_ProvideIds(t *testing.T) {
ctrl := gomock.NewController(t)
objTree := mock_tree.NewMockObjectTree(ctrl)
prov := &provider{}
defer ctrl.Finish()
t.Run("startId is empty", func(t *testing.T) {
ch := &tree.Change{Id: "rootId"}
objTree.EXPECT().Root().Return(ch)
objTree.EXPECT().ID().Return("id")
objTree.EXPECT().IterateFrom("id", gomock.Any(), gomock.Any()).Return(nil)
_, _, err := prov.ProvideIds(objTree, "")
require.NoError(t, err)
})
t.Run("startId is not empty", func(t *testing.T) {
ch := &tree.Change{Id: "rootId"}
objTree.EXPECT().Root().Return(ch)
objTree.EXPECT().IterateFrom("startId", gomock.Any(), gomock.Any()).Return(nil)
_, _, err := prov.ProvideIds(objTree, "startId")
require.NoError(t, err)
})
}

View File

@ -0,0 +1,86 @@
// Code generated by MockGen. DO NOT EDIT.
// 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
import (
reflect "reflect"
tree "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/tree"
gomock "github.com/golang/mock/gomock"
)
// MockDeletedIdsProvider is a mock of DeletedIdsProvider interface.
type MockDeletedIdsProvider struct {
ctrl *gomock.Controller
recorder *MockDeletedIdsProviderMockRecorder
}
// MockDeletedIdsProviderMockRecorder is the mock recorder for MockDeletedIdsProvider.
type MockDeletedIdsProviderMockRecorder struct {
mock *MockDeletedIdsProvider
}
// NewMockDeletedIdsProvider creates a new mock instance.
func NewMockDeletedIdsProvider(ctrl *gomock.Controller) *MockDeletedIdsProvider {
mock := &MockDeletedIdsProvider{ctrl: ctrl}
mock.recorder = &MockDeletedIdsProviderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockDeletedIdsProvider) EXPECT() *MockDeletedIdsProviderMockRecorder {
return m.recorder
}
// ProvideIds mocks base method.
func (m *MockDeletedIdsProvider) ProvideIds(arg0 tree.ObjectTree, arg1 string) ([]string, string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ProvideIds", arg0, arg1)
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(string)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ProvideIds indicates an expected call of ProvideIds.
func (mr *MockDeletedIdsProviderMockRecorder) ProvideIds(arg0, arg1 interface{}) *gomock.Call {
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))
}

View File

@ -0,0 +1,154 @@
//go:generate mockgen -destination mock_settingsdocument/mock_settingsdocument.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument DeletedIdsProvider,Deleter
package settingsdocument
import (
"context"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app/logger"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate"
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree/updatelistener"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/treegetter"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/tree"
"go.uber.org/zap"
)
var log = logger.NewNamed("commonspace.settingsdocument")
type SettingsDocument interface {
synctree.SyncTree
Init(ctx context.Context) (err error)
DeleteObject(id string) (err error)
}
type BuildTreeFunc func(ctx context.Context, id string, listener updatelistener.UpdateListener) (t synctree.SyncTree, err error)
type Deps struct {
BuildFunc BuildTreeFunc
Account account.Service
TreeGetter treegetter.TreeGetter
Store spacestorage.SpaceStorage
DeletionState deletionstate.DeletionState
// testing dependencies
prov DeletedIdsProvider
del Deleter
}
type settingsDocument struct {
synctree.SyncTree
account account.Service
spaceId string
treeGetter treegetter.TreeGetter
store spacestorage.SpaceStorage
prov DeletedIdsProvider
buildFunc BuildTreeFunc
loop *deleteLoop
deletionState deletionstate.DeletionState
lastChangeId string
}
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()
})
deps.DeletionState.AddObserver(func(ids []string) {
loop.notify()
})
s := &settingsDocument{
loop: loop,
spaceId: spaceId,
account: deps.Account,
deletionState: deps.DeletionState,
treeGetter: deps.TreeGetter,
store: deps.Store,
buildFunc: deps.BuildFunc,
}
// this is needed mainly for testing
if deps.prov == nil {
s.prov = &provider{}
} else {
s.prov = deps.prov
}
doc = s
return
}
func (s *settingsDocument) updateIds(tr tree.ObjectTree, lastChangeId string) {
s.lastChangeId = lastChangeId
ids, lastId, err := s.prov.ProvideIds(tr, s.lastChangeId)
if err != nil {
log.With(zap.Strings("ids", ids), zap.Error(err)).Error("failed to update state")
return
}
s.lastChangeId = lastId
if err = s.deletionState.Add(ids); err != nil {
log.With(zap.Strings("ids", ids), zap.Error(err)).Error("failed to queue ids to delete")
}
}
// 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, "")
}
func (s *settingsDocument) Init(ctx context.Context) (err error) {
s.SyncTree, err = s.buildFunc(ctx, s.store.SpaceSettingsId(), s)
if err != nil {
return
}
s.loop.Run()
return
}
func (s *settingsDocument) Close() error {
s.loop.Close()
return s.SyncTree.Close()
}
func (s *settingsDocument) DeleteObject(id string) (err error) {
s.Lock()
defer s.Unlock()
if s.deletionState.Exists(id) {
return nil
}
// TODO: add snapshot logic
res, err := s.deletionState.CreateDeleteChange(id, false)
if err != nil {
return
}
accountData := s.account.Account()
_, err = s.AddContent(context.Background(), tree.SignableChangeContent{
Data: res,
Key: accountData.SignKey,
Identity: accountData.Identity,
IsSnapshot: false,
IsEncrypted: false,
})
if err != nil {
return
}
s.Update(s)
return
}

View File

@ -0,0 +1,188 @@
package settingsdocument
import (
"context"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/account/mock_account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate/mock_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/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/tree"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/asymmetric/signingkey"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"sync"
"testing"
"time"
)
type testSyncTreeMock struct {
*mock_synctree.MockSyncTree
m sync.Mutex
}
func newTestObjMock(mockTree *mock_synctree.MockSyncTree) *testSyncTreeMock {
return &testSyncTreeMock{
MockSyncTree: mockTree,
}
}
func (t *testSyncTreeMock) Lock() {
t.m.Lock()
}
func (t *testSyncTreeMock) Unlock() {
t.m.Unlock()
}
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 *mock_deletionstate.MockDeletionState
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 := mock_deletionstate.NewMockDeletionState(ctrl)
prov := mock_settingsdocument.NewMockDeletedIdsProvider(ctrl)
syncTree := mock_synctree.NewMockSyncTree(ctrl)
del := mock_settingsdocument.NewMockDeleter(ctrl)
delState.EXPECT().AddObserver(gomock.Any())
buildFunc := BuildTreeFunc(func(ctx context.Context, id string, listener updatelistener.UpdateListener) (synctree.SyncTree, error) {
require.Equal(t, docId, id)
return newTestObjMock(syncTree), nil
})
deps := Deps{
BuildFunc: buildFunc,
Account: acc,
TreeGetter: treeGetter,
Store: st,
DeletionState: delState,
prov: prov,
del: del,
}
doc := NewSettingsDocument(deps, spaceId).(*settingsDocument)
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()
err := fx.doc.Init(context.Background())
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)
delId := "delId"
fx.delState.EXPECT().Exists(delId).Return(false)
res := []byte("settingsData")
fx.delState.EXPECT().CreateDeleteChange(delId, false).Return(res, nil)
accountData := &account.AccountData{
Identity: []byte("id"),
PeerKey: nil,
SignKey: &signingkey.Ed25519PrivateKey{},
EncKey: nil,
}
fx.account.EXPECT().Account().Return(accountData)
fx.syncTree.EXPECT().AddContent(gomock.Any(), tree.SignableChangeContent{
Data: res,
Key: accountData.SignKey,
Identity: accountData.Identity,
IsSnapshot: false,
IsEncrypted: false,
}).Return(tree.AddResult{}, nil)
lastChangeId := "someId"
retIds := []string{"id1", "id2"}
fx.doc.lastChangeId = lastChangeId
fx.provider.EXPECT().ProvideIds(gomock.Not(nil), lastChangeId).Return(retIds, retIds[len(retIds)-1], nil)
fx.delState.EXPECT().Add(retIds).Return(nil)
err = fx.doc.DeleteObject(delId)
require.NoError(t, err)
require.Equal(t, retIds[len(retIds)-1], fx.doc.lastChangeId)
fx.syncTree.EXPECT().Close().Return(nil)
err = fx.doc.Close()
require.NoError(t, err)
}
func TestSettingsDocument_Rebuild(t *testing.T) {
fx := newSettingsFixture(t)
defer fx.stop()
fx.spaceStorage.EXPECT().SpaceSettingsId().Return(fx.docId)
fx.deleter.EXPECT().Delete()
err := fx.doc.Init(context.Background())
require.NoError(t, err)
time.Sleep(100 * time.Millisecond)
lastChangeId := "someId"
retIds := []string{"id1", "id2"}
fx.doc.lastChangeId = lastChangeId
fx.provider.EXPECT().ProvideIds(gomock.Not(nil), "").Return(retIds, retIds[len(retIds)-1], nil)
fx.delState.EXPECT().Add(retIds).Return(nil)
fx.doc.Rebuild(fx.doc)
require.Equal(t, retIds[len(retIds)-1], fx.doc.lastChangeId)
fx.syncTree.EXPECT().Close().Return(nil)
err = fx.doc.Close()
require.NoError(t, err)
}

View File

@ -6,6 +6,8 @@ import (
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate"
"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/syncacl"
@ -40,7 +42,10 @@ type SpaceCreatePayload struct {
ReplicationKey uint64
}
const SpaceTypeDerived = "derived.space"
const (
SpaceTypeDerived = "derived.space"
SettingsSyncPeriodSeconds = 10
)
type SpaceDerivePayload struct {
SigningKey signingkey.PrivKey
@ -48,9 +53,11 @@ type SpaceDerivePayload struct {
}
type SpaceDescription struct {
SpaceHeader *spacesyncproto.RawSpaceHeaderWithId
AclId string
AclPayload []byte
SpaceHeader *spacesyncproto.RawSpaceHeaderWithId
AclId string
AclPayload []byte
SpaceSettingsId string
SpaceSettingsPayload []byte
}
func NewSpaceId(id string, repKey uint64) string {
@ -62,13 +69,14 @@ type Space interface {
Init(ctx context.Context) error
StoredIds() []string
Description() SpaceDescription
Description() (SpaceDescription, error)
SpaceSyncRpc() RpcHandler
DeriveTree(ctx context.Context, payload tree.ObjectTreeCreatePayload, listener updatelistener.UpdateListener) (tree.ObjectTree, error)
CreateTree(ctx context.Context, payload tree.ObjectTreeCreatePayload, listener updatelistener.UpdateListener) (tree.ObjectTree, error)
BuildTree(ctx context.Context, id string, listener updatelistener.UpdateListener) (tree.ObjectTree, error)
DeleteTree(ctx context.Context, id string) (err error)
Close() error
}
@ -80,13 +88,14 @@ type space struct {
rpc *rpcHandler
syncService syncservice.SyncService
diffService diffservice.DiffService
storage storage.SpaceStorage
cache treegetter.TreeGetter
account account.Service
aclList *syncacl.SyncACL
configuration nodeconf.Configuration
syncService syncservice.SyncService
diffService diffservice.DiffService
storage storage.SpaceStorage
cache treegetter.TreeGetter
account account.Service
aclList *syncacl.SyncACL
configuration nodeconf.Configuration
settingsDocument settingsdocument.SettingsDocument
isClosed atomic.Bool
}
@ -99,16 +108,30 @@ func (s *space) Id() string {
return s.id
}
func (s *space) Description() SpaceDescription {
func (s *space) Description() (desc SpaceDescription, err error) {
root := s.aclList.Root()
return SpaceDescription{
SpaceHeader: s.header,
AclId: root.Id,
AclPayload: root.Payload,
settingsStorage, err := s.storage.TreeStorage(s.storage.SpaceSettingsId())
if err != nil {
return
}
settingsRoot, err := settingsStorage.Root()
if err != nil {
return
}
desc = SpaceDescription{
SpaceHeader: s.header,
AclId: root.Id,
AclPayload: root.Payload,
SpaceSettingsId: settingsRoot.Id,
SpaceSettingsPayload: settingsRoot.RawChange,
}
return
}
func (s *space) Init(ctx context.Context) (err error) {
s.storage = newCommonStorage(s.storage)
header, err := s.storage.SpaceHeader()
if err != nil {
return
@ -128,9 +151,32 @@ func (s *space) Init(ctx context.Context) (err error) {
return
}
s.aclList = syncacl.NewSyncACL(aclList, s.syncService.StreamPool())
objectGetter := newCommonSpaceGetter(s.id, s.aclList, s.cache)
deletionState := deletionstate.NewDeletionState(s.storage)
deps := settingsdocument.Deps{
BuildFunc: func(ctx context.Context, id string, listener updatelistener.UpdateListener) (t synctree.SyncTree, err error) {
res, err := s.BuildTree(ctx, id, listener)
if err != nil {
return
}
t = res.(synctree.SyncTree)
return
},
Account: s.account,
TreeGetter: s.cache,
Store: s.storage,
DeletionState: deletionState,
}
s.settingsDocument = settingsdocument.NewSettingsDocument(deps, s.id)
objectGetter := newCommonSpaceGetter(s.id, s.aclList, s.cache, s.settingsDocument)
s.syncService.Init(objectGetter)
s.diffService.Init(initialIds)
s.diffService.Init(initialIds, deletionState)
err = s.settingsDocument.Init(ctx)
if err != nil {
return
}
return nil
}
@ -163,7 +209,7 @@ func (s *space) DeriveTree(ctx context.Context, payload tree.ObjectTreeCreatePay
HeadNotifiable: s.diffService,
Listener: listener,
AclList: s.aclList,
CreateStorage: s.storage.CreateTreeStorage,
SpaceStorage: s.storage,
}
return synctree.DeriveSyncTree(ctx, deps)
}
@ -181,7 +227,7 @@ func (s *space) CreateTree(ctx context.Context, payload tree.ObjectTreeCreatePay
HeadNotifiable: s.diffService,
Listener: listener,
AclList: s.aclList,
CreateStorage: s.storage.CreateTreeStorage,
SpaceStorage: s.storage,
}
return synctree.CreateSyncTree(ctx, deps)
}
@ -203,6 +249,10 @@ func (s *space) BuildTree(ctx context.Context, id string, listener updatelistene
return synctree.BuildSyncTreeOrGetRemote(ctx, id, deps)
}
func (s *space) DeleteTree(ctx context.Context, id string) (err error) {
return s.settingsDocument.DeleteObject(id)
}
func (s *space) Close() error {
log.With(zap.String("id", s.id)).Debug("space is closing")
defer func() {
@ -216,6 +266,9 @@ func (s *space) Close() error {
if err := s.syncService.Close(); err != nil {
mError.Add(err)
}
if err := s.settingsDocument.Close(); err != nil {
mError.Add(err)
}
if err := s.aclList.Close(); err != nil {
mError.Add(err)
}

View File

@ -64,9 +64,7 @@ message ObjectSyncMessage {
// PushSpaceRequest is a request to add space on a node containing only one acl record
message PushSpaceRequest {
RawSpaceHeaderWithId spaceHeader = 1;
bytes aclPayload = 2;
string aclPayloadId = 3;
SpacePayload payload = 1;
}
// PushSpaceResponse is an empty response
@ -79,9 +77,15 @@ message PullSpaceRequest {
// PullSpaceResponse is a response with header and acl root
message PullSpaceResponse {
SpacePayload payload = 1;
}
message SpacePayload {
RawSpaceHeaderWithId spaceHeader = 1;
bytes aclPayload = 2;
string aclPayloadId = 3;
bytes spaceSettingsPayload = 4;
string spaceSettingsPayloadId = 5;
}
// SpaceHeader is a header for a space
@ -102,3 +106,23 @@ message RawSpaceHeaderWithId {
bytes rawHeader = 1;
string id = 2;
}
message SpaceSettingsContent {
oneof value {
ObjectDelete objectDelete = 1;
}
}
message ObjectDelete {
string id = 1;
}
message SpaceSettingsSnapshot {
repeated string deletedIds = 1;
}
message SettingsData {
repeated SpaceSettingsContent content = 1;
SpaceSettingsSnapshot snapshot = 2;
}

File diff suppressed because it is too large Load Diff

View File

@ -176,6 +176,20 @@ func (mr *MockSpaceStorageMockRecorder) Id() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Id", reflect.TypeOf((*MockSpaceStorage)(nil).Id))
}
// SetTreeDeletedStatus mocks base method.
func (m *MockSpaceStorage) SetTreeDeletedStatus(arg0, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetTreeDeletedStatus", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// SetTreeDeletedStatus indicates an expected call of SetTreeDeletedStatus.
func (mr *MockSpaceStorageMockRecorder) SetTreeDeletedStatus(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTreeDeletedStatus", reflect.TypeOf((*MockSpaceStorage)(nil).SetTreeDeletedStatus), arg0, arg1)
}
// SpaceHeader mocks base method.
func (m *MockSpaceStorage) SpaceHeader() (*spacesyncproto.RawSpaceHeaderWithId, error) {
m.ctrl.T.Helper()
@ -191,6 +205,20 @@ func (mr *MockSpaceStorageMockRecorder) SpaceHeader() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpaceHeader", reflect.TypeOf((*MockSpaceStorage)(nil).SpaceHeader))
}
// SpaceSettingsId mocks base method.
func (m *MockSpaceStorage) SpaceSettingsId() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SpaceSettingsId")
ret0, _ := ret[0].(string)
return ret0
}
// SpaceSettingsId indicates an expected call of SpaceSettingsId.
func (mr *MockSpaceStorageMockRecorder) SpaceSettingsId() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SpaceSettingsId", reflect.TypeOf((*MockSpaceStorage)(nil).SpaceSettingsId))
}
// StoredIds mocks base method.
func (m *MockSpaceStorage) StoredIds() ([]string, error) {
m.ctrl.T.Helper()
@ -206,6 +234,21 @@ func (mr *MockSpaceStorageMockRecorder) StoredIds() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoredIds", reflect.TypeOf((*MockSpaceStorage)(nil).StoredIds))
}
// TreeDeletedStatus mocks base method.
func (m *MockSpaceStorage) TreeDeletedStatus(arg0 string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TreeDeletedStatus", arg0)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// TreeDeletedStatus indicates an expected call of TreeDeletedStatus.
func (mr *MockSpaceStorageMockRecorder) TreeDeletedStatus(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TreeDeletedStatus", reflect.TypeOf((*MockSpaceStorage)(nil).TreeDeletedStatus), arg0)
}
// TreeStorage mocks base method.
func (m *MockSpaceStorage) TreeStorage(arg0 string) (storage0.TreeStorage, error) {
m.ctrl.T.Helper()

View File

@ -7,16 +7,29 @@ import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
)
const CName = "commonspace.storage"
var ErrSpaceStorageExists = errors.New("space storage exists")
var ErrSpaceStorageMissing = errors.New("space storage missing")
var (
ErrSpaceStorageExists = errors.New("space storage exists")
ErrSpaceStorageMissing = errors.New("space storage missing")
ErrTreeStorageAlreadyDeleted = errors.New("tree storage already deleted")
)
const (
TreeDeletedStatusQueued = "queued"
TreeDeletedStatusDeleted = "deleted"
)
type SpaceStorage interface {
storage.Provider
Id() string
SetTreeDeletedStatus(id, state string) error
TreeDeletedStatus(id string) (string, error)
SpaceSettingsId() string
ACLStorage() (storage.ListStorage, error)
SpaceHeader() (*spacesyncproto.RawSpaceHeaderWithId, error)
StoredIds() ([]string, error)
@ -24,8 +37,9 @@ type SpaceStorage interface {
}
type SpaceStorageCreatePayload struct {
RecWithId *aclrecordproto.RawACLRecordWithId
SpaceHeaderWithId *spacesyncproto.RawSpaceHeaderWithId
AclWithId *aclrecordproto.RawACLRecordWithId
SpaceHeaderWithId *spacesyncproto.RawSpaceHeaderWithId
SpaceSettingsWithId *treechangeproto.RawTreeChangeWithId
}
type SpaceStorageProvider interface {
@ -33,3 +47,8 @@ type SpaceStorageProvider interface {
SpaceStorage(id string) (SpaceStorage, error)
CreateSpaceStorage(payload SpaceStorageCreatePayload) (SpaceStorage, error)
}
func ValidateSpaceStorageCreatePayload(payload SpaceStorageCreatePayload) (err error) {
// TODO: add proper validation
return nil
}

View File

@ -1,12 +1,15 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree (interfaces: SyncClient)
// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree (interfaces: SyncClient,SyncTree)
// Package mock_synctree is a generated GoMock package.
package mock_synctree
import (
context "context"
reflect "reflect"
spacesyncproto "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
storage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
tree "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/tree"
treechangeproto "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
gomock "github.com/golang/mock/gomock"
@ -134,3 +137,311 @@ func (mr *MockSyncClientMockRecorder) SendAsync(arg0, arg1, arg2 interface{}) *g
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAsync", reflect.TypeOf((*MockSyncClient)(nil).SendAsync), arg0, arg1, arg2)
}
// MockSyncTree is a mock of SyncTree interface.
type MockSyncTree struct {
ctrl *gomock.Controller
recorder *MockSyncTreeMockRecorder
}
// MockSyncTreeMockRecorder is the mock recorder for MockSyncTree.
type MockSyncTreeMockRecorder struct {
mock *MockSyncTree
}
// NewMockSyncTree creates a new mock instance.
func NewMockSyncTree(ctrl *gomock.Controller) *MockSyncTree {
mock := &MockSyncTree{ctrl: ctrl}
mock.recorder = &MockSyncTreeMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockSyncTree) EXPECT() *MockSyncTreeMockRecorder {
return m.recorder
}
// AddContent mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) AddContent(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddContent", reflect.TypeOf((*MockSyncTree)(nil).AddContent), arg0, arg1)
}
// AddRawChanges mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) 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((*MockSyncTree)(nil).AddRawChanges), varargs...)
}
// ChangesAfterCommonSnapshot mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) ChangesAfterCommonSnapshot(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangesAfterCommonSnapshot", reflect.TypeOf((*MockSyncTree)(nil).ChangesAfterCommonSnapshot), arg0, arg1)
}
// Close mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) Close() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSyncTree)(nil).Close))
}
// DebugDump mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) DebugDump() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DebugDump", reflect.TypeOf((*MockSyncTree)(nil).DebugDump))
}
// Delete mocks base method.
func (m *MockSyncTree) Delete() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete")
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockSyncTreeMockRecorder) Delete() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockSyncTree)(nil).Delete))
}
// HandleMessage mocks base method.
func (m *MockSyncTree) HandleMessage(arg0 context.Context, arg1 string, arg2 *spacesyncproto.ObjectSyncMessage) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HandleMessage", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// HandleMessage indicates an expected call of HandleMessage.
func (mr *MockSyncTreeMockRecorder) HandleMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleMessage", reflect.TypeOf((*MockSyncTree)(nil).HandleMessage), arg0, arg1, arg2)
}
// HasChanges mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) HasChanges(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasChanges", reflect.TypeOf((*MockSyncTree)(nil).HasChanges), arg0...)
}
// Header mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) Header() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockSyncTree)(nil).Header))
}
// Heads mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) Heads() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Heads", reflect.TypeOf((*MockSyncTree)(nil).Heads))
}
// ID mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) ID() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockSyncTree)(nil).ID))
}
// Iterate mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) Iterate(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterate", reflect.TypeOf((*MockSyncTree)(nil).Iterate), arg0, arg1)
}
// IterateFrom mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) IterateFrom(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IterateFrom", reflect.TypeOf((*MockSyncTree)(nil).IterateFrom), arg0, arg1, arg2)
}
// Lock mocks base method.
func (m *MockSyncTree) Lock() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Lock")
}
// Lock indicates an expected call of Lock.
func (mr *MockSyncTreeMockRecorder) Lock() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Lock", reflect.TypeOf((*MockSyncTree)(nil).Lock))
}
// RLock mocks base method.
func (m *MockSyncTree) RLock() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "RLock")
}
// RLock indicates an expected call of RLock.
func (mr *MockSyncTreeMockRecorder) RLock() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RLock", reflect.TypeOf((*MockSyncTree)(nil).RLock))
}
// RUnlock mocks base method.
func (m *MockSyncTree) RUnlock() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "RUnlock")
}
// RUnlock indicates an expected call of RUnlock.
func (mr *MockSyncTreeMockRecorder) RUnlock() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RUnlock", reflect.TypeOf((*MockSyncTree)(nil).RUnlock))
}
// Root mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) Root() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Root", reflect.TypeOf((*MockSyncTree)(nil).Root))
}
// SnapshotPath mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) SnapshotPath() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SnapshotPath", reflect.TypeOf((*MockSyncTree)(nil).SnapshotPath))
}
// Storage mocks base method.
func (m *MockSyncTree) 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 *MockSyncTreeMockRecorder) Storage() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Storage", reflect.TypeOf((*MockSyncTree)(nil).Storage))
}
// Unlock mocks base method.
func (m *MockSyncTree) Unlock() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Unlock")
}
// Unlock indicates an expected call of Unlock.
func (mr *MockSyncTreeMockRecorder) Unlock() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockSyncTree)(nil).Unlock))
}

View File

@ -1,4 +1,4 @@
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree SyncClient
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree SyncClient,SyncTree
package synctree
import (

View File

@ -20,15 +20,24 @@ import (
"go.uber.org/zap"
)
var ErrSyncTreeClosed = errors.New("sync tree is closed")
var (
ErrSyncTreeClosed = errors.New("sync tree is closed")
ErrSyncTreeDeleted = errors.New("sync tree is deleted")
)
type SyncTree interface {
tree.ObjectTree
synchandler.SyncHandler
}
// SyncTree sends head updates to sync service and also sends new changes to update listener
type SyncTree struct {
type syncTree struct {
tree.ObjectTree
synchandler.SyncHandler
syncClient SyncClient
listener updatelistener.UpdateListener
isClosed bool
isDeleted bool
}
var log = logger.NewNamed("commonspace.synctree").Sugar()
@ -46,7 +55,7 @@ type CreateDeps struct {
StreamPool syncservice.StreamPool
Listener updatelistener.UpdateListener
AclList list.ACLList
CreateStorage storage.TreeStorageCreatorFunc
SpaceStorage spacestorage.SpaceStorage
}
type BuildDeps struct {
@ -60,8 +69,8 @@ type BuildDeps struct {
TreeStorage storage.TreeStorage
}
func DeriveSyncTree(ctx context.Context, deps CreateDeps) (t tree.ObjectTree, err error) {
t, err = createDerivedObjectTree(deps.Payload, deps.AclList, deps.CreateStorage)
func DeriveSyncTree(ctx context.Context, deps CreateDeps) (t SyncTree, err error) {
objTree, err := createDerivedObjectTree(deps.Payload, deps.AclList, deps.SpaceStorage.CreateTreeStorage)
if err != nil {
return
}
@ -71,22 +80,27 @@ func DeriveSyncTree(ctx context.Context, deps CreateDeps) (t tree.ObjectTree, er
deps.HeadNotifiable,
sharedFactory,
deps.Configuration)
syncTree := &SyncTree{
ObjectTree: t,
syncTree := &syncTree{
ObjectTree: objTree,
syncClient: syncClient,
listener: deps.Listener,
}
syncHandler := newSyncTreeHandler(syncTree, syncClient)
syncTree.SyncHandler = syncHandler
t = syncTree
syncTree.Lock()
defer syncTree.Unlock()
if syncTree.listener != nil {
syncTree.listener.Rebuild(syncTree)
}
headUpdate := syncClient.CreateHeadUpdate(t, nil)
err = syncClient.BroadcastAsync(headUpdate)
return
}
func CreateSyncTree(ctx context.Context, deps CreateDeps) (t tree.ObjectTree, err error) {
t, err = createObjectTree(deps.Payload, deps.AclList, deps.CreateStorage)
func CreateSyncTree(ctx context.Context, deps CreateDeps) (t SyncTree, err error) {
objTree, err := createObjectTree(deps.Payload, deps.AclList, deps.SpaceStorage.CreateTreeStorage)
if err != nil {
return
}
@ -96,21 +110,27 @@ func CreateSyncTree(ctx context.Context, deps CreateDeps) (t tree.ObjectTree, er
deps.HeadNotifiable,
GetRequestFactory(),
deps.Configuration)
syncTree := &SyncTree{
ObjectTree: t,
syncTree := &syncTree{
ObjectTree: objTree,
syncClient: syncClient,
listener: deps.Listener,
}
syncHandler := newSyncTreeHandler(syncTree, syncClient)
syncTree.SyncHandler = syncHandler
t = syncTree
syncTree.Lock()
defer syncTree.Unlock()
// TODO: refactor here because the code is duplicated, when we create a tree we should only create a storage and then build a tree
if syncTree.listener != nil {
syncTree.listener.Rebuild(syncTree)
}
headUpdate := syncClient.CreateHeadUpdate(t, nil)
err = syncClient.BroadcastAsync(headUpdate)
return
}
func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t tree.ObjectTree, err error) {
func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t SyncTree, err error) {
getTreeRemote := func() (msg *treechangeproto.TreeSyncMessage, err error) {
peerId, err := peer.CtxPeerId(ctx)
if err != nil {
@ -140,6 +160,15 @@ func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t
return
}
status, err := deps.SpaceStorage.TreeDeletedStatus(id)
if err != nil {
return
}
if status != "" {
err = spacestorage.ErrTreeStorageAlreadyDeleted
return
}
resp, err := getTreeRemote()
if err != nil {
return
@ -170,9 +199,8 @@ func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t
return buildSyncTree(ctx, true, deps)
}
func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t tree.ObjectTree, err error) {
t, err = buildObjectTree(deps.TreeStorage, deps.AclList)
func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t SyncTree, err error) {
objTree, err := buildObjectTree(deps.TreeStorage, deps.AclList)
if err != nil {
return
}
@ -182,14 +210,19 @@ func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t tr
deps.HeadNotifiable,
GetRequestFactory(),
deps.Configuration)
syncTree := &SyncTree{
ObjectTree: t,
syncTree := &syncTree{
ObjectTree: objTree,
syncClient: syncClient,
listener: deps.Listener,
}
syncHandler := newSyncTreeHandler(syncTree, syncClient)
syncTree.SyncHandler = syncHandler
t = syncTree
syncTree.Lock()
defer syncTree.Unlock()
if syncTree.listener != nil {
syncTree.listener.Rebuild(syncTree)
}
headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil)
// here we will have different behaviour based on who is sending this update
@ -203,9 +236,22 @@ func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t tr
return
}
func (s *SyncTree) AddContent(ctx context.Context, content tree.SignableChangeContent) (res tree.AddResult, err error) {
if s.isClosed {
err = ErrSyncTreeClosed
func (s *syncTree) IterateFrom(id string, convert tree.ChangeConvertFunc, iterate tree.ChangeIterateFunc) (err error) {
if err = s.checkAlive(); err != nil {
return
}
return s.ObjectTree.IterateFrom(id, convert, iterate)
}
func (s *syncTree) Iterate(convert tree.ChangeConvertFunc, iterate tree.ChangeIterateFunc) (err error) {
if err = s.checkAlive(); err != nil {
return
}
return s.ObjectTree.Iterate(convert, iterate)
}
func (s *syncTree) AddContent(ctx context.Context, content tree.SignableChangeContent) (res tree.AddResult, err error) {
if err = s.checkAlive(); err != nil {
return
}
res, err = s.ObjectTree.AddContent(ctx, content)
@ -217,9 +263,8 @@ func (s *SyncTree) AddContent(ctx context.Context, content tree.SignableChangeCo
return
}
func (s *SyncTree) AddRawChanges(ctx context.Context, changes ...*treechangeproto.RawTreeChangeWithId) (res tree.AddResult, err error) {
if s.isClosed {
err = ErrSyncTreeClosed
func (s *syncTree) AddRawChanges(ctx context.Context, changes ...*treechangeproto.RawTreeChangeWithId) (res tree.AddResult, err error) {
if err = s.checkAlive(); err != nil {
return
}
res, err = s.ObjectTree.AddRawChanges(ctx, changes...)
@ -243,15 +288,38 @@ func (s *SyncTree) AddRawChanges(ctx context.Context, changes ...*treechangeprot
return
}
func (s *SyncTree) Close() (err error) {
func (s *syncTree) Delete() (err error) {
log.With("id", s.ID()).Debug("deleting sync tree")
s.Lock()
defer s.Unlock()
if err = s.checkAlive(); err != nil {
return
}
err = s.ObjectTree.Delete()
if err != nil {
return
}
s.isDeleted = true
return
}
func (s *syncTree) Close() (err error) {
log.With("id", s.ID()).Debug("closing sync tree")
s.Lock()
defer s.Unlock()
log.With("id", s.ID()).Debug("taken lock on sync tree")
if s.isClosed {
err = ErrSyncTreeClosed
return
return ErrSyncTreeClosed
}
s.isClosed = true
return
}
func (s *syncTree) checkAlive() (err error) {
if s.isClosed {
err = ErrSyncTreeClosed
}
if s.isDeleted {
err = ErrSyncTreeDeleted
}
return
}

View File

@ -3,6 +3,7 @@ package synctree
import (
"context"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage/mock_storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree/mock_synctree"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree/updatelistener"
@ -26,7 +27,7 @@ type syncTreeMatcher struct {
}
func (s syncTreeMatcher) Matches(x interface{}) bool {
t, ok := x.(*SyncTree)
t, ok := x.(*syncTree)
if !ok {
return false
}
@ -45,7 +46,8 @@ func Test_DeriveSyncTree(t *testing.T) {
updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl)
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
aclListMock := mock_list.NewMockACLList(ctrl)
objTreeMock := mock_tree.NewMockObjectTree(ctrl)
objTreeMock := newTestObjMock(mock_tree.NewMockObjectTree(ctrl))
spaceStorageMock := mock_storage.NewMockSpaceStorage(ctrl)
spaceId := "spaceId"
expectedPayload := tree.ObjectTreeCreatePayload{SpaceId: spaceId}
createDerivedObjectTree = func(payload tree.ObjectTreeCreatePayload, l list.ACLList, create storage2.TreeStorageCreatorFunc) (objTree tree.ObjectTree, err error) {
@ -59,7 +61,14 @@ func Test_DeriveSyncTree(t *testing.T) {
headUpdate := &treechangeproto.TreeSyncMessage{}
syncClientMock.EXPECT().CreateHeadUpdate(syncTreeMatcher{objTreeMock, syncClientMock, updateListenerMock}, gomock.Nil()).Return(headUpdate)
syncClientMock.EXPECT().BroadcastAsync(gomock.Eq(headUpdate)).Return(nil)
deps := CreateDeps{AclList: aclListMock, SpaceId: spaceId, Payload: expectedPayload, Listener: updateListenerMock}
updateListenerMock.EXPECT().Rebuild(gomock.Any())
deps := CreateDeps{
AclList: aclListMock,
SpaceId: spaceId,
Payload: expectedPayload,
Listener: updateListenerMock,
SpaceStorage: spaceStorageMock,
}
_, err := DeriveSyncTree(ctx, deps)
require.NoError(t, err)
@ -73,7 +82,8 @@ func Test_CreateSyncTree(t *testing.T) {
updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl)
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
aclListMock := mock_list.NewMockACLList(ctrl)
objTreeMock := mock_tree.NewMockObjectTree(ctrl)
objTreeMock := newTestObjMock(mock_tree.NewMockObjectTree(ctrl))
spaceStorageMock := mock_storage.NewMockSpaceStorage(ctrl)
spaceId := "spaceId"
expectedPayload := tree.ObjectTreeCreatePayload{SpaceId: spaceId}
createObjectTree = func(payload tree.ObjectTreeCreatePayload, l list.ACLList, create storage2.TreeStorageCreatorFunc) (objTree tree.ObjectTree, err error) {
@ -87,7 +97,14 @@ func Test_CreateSyncTree(t *testing.T) {
headUpdate := &treechangeproto.TreeSyncMessage{}
syncClientMock.EXPECT().CreateHeadUpdate(syncTreeMatcher{objTreeMock, syncClientMock, updateListenerMock}, gomock.Nil()).Return(headUpdate)
syncClientMock.EXPECT().BroadcastAsync(gomock.Eq(headUpdate)).Return(nil)
deps := CreateDeps{AclList: aclListMock, SpaceId: spaceId, Payload: expectedPayload, Listener: updateListenerMock}
updateListenerMock.EXPECT().Rebuild(gomock.Any())
deps := CreateDeps{
AclList: aclListMock,
SpaceId: spaceId,
Payload: expectedPayload,
Listener: updateListenerMock,
SpaceStorage: spaceStorageMock,
}
_, err := CreateSyncTree(ctx, deps)
require.NoError(t, err)
@ -100,8 +117,8 @@ func Test_BuildSyncTree(t *testing.T) {
updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl)
syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
objTreeMock := mock_tree.NewMockObjectTree(ctrl)
tr := &SyncTree{
objTreeMock := newTestObjMock(mock_tree.NewMockObjectTree(ctrl))
tr := &syncTree{
ObjectTree: objTreeMock,
SyncHandler: nil,
syncClient: syncClientMock,

View File

@ -50,6 +50,20 @@ func (mr *MockTreeGetterMockRecorder) Close(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockTreeGetter)(nil).Close), arg0)
}
// DeleteTree mocks base method.
func (m *MockTreeGetter) DeleteTree(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteTree", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteTree indicates an expected call of DeleteTree.
func (mr *MockTreeGetterMockRecorder) DeleteTree(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTree", reflect.TypeOf((*MockTreeGetter)(nil).DeleteTree), arg0, arg1, arg2)
}
// GetTree mocks base method.
func (m *MockTreeGetter) GetTree(arg0 context.Context, arg1, arg2 string) (tree.ObjectTree, error) {
m.ctrl.T.Helper()

View File

@ -15,4 +15,5 @@ var ErrSpaceNotFound = errors.New("space not found")
type TreeGetter interface {
app.ComponentRunnable
GetTree(ctx context.Context, spaceId, treeId string) (tree.ObjectTree, error)
DeleteTree(ctx context.Context, spaceId, treeId string) error
}

View File

@ -157,6 +157,10 @@ func (t *inMemoryTreeStorage) GetRawChange(ctx context.Context, changeId string)
return nil, fmt.Errorf("could not get change with id: %s", changeId)
}
func (t *inMemoryTreeStorage) Delete() error {
return nil
}
type inMemoryStorageProvider struct {
objects map[string]TreeStorage
sync.RWMutex

View File

@ -160,6 +160,20 @@ func (mr *MockTreeStorageMockRecorder) AddRawChange(arg0 interface{}) *gomock.Ca
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRawChange", reflect.TypeOf((*MockTreeStorage)(nil).AddRawChange), arg0)
}
// Delete mocks base method.
func (m *MockTreeStorage) Delete() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete")
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockTreeStorageMockRecorder) Delete() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockTreeStorage)(nil).Delete))
}
// GetRawChange mocks base method.
func (m *MockTreeStorage) GetRawChange(arg0 context.Context, arg1 string) (*treechangeproto.RawTreeChangeWithId, error) {
m.ctrl.T.Helper()

View File

@ -5,9 +5,11 @@ import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
)
var ErrUnknownTreeId = errors.New("tree does not exist")
var ErrTreeExists = errors.New("tree already exists")
var ErrUnkownChange = errors.New("change doesn't exist")
var (
ErrUnknownTreeId = errors.New("tree does not exist")
ErrTreeExists = errors.New("tree already exists")
ErrUnknownChange = errors.New("change doesn't exist")
)
type TreeStorageCreatePayload struct {
RootRawChange *treechangeproto.RawTreeChangeWithId

View File

@ -14,6 +14,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)
Delete() error
}
type TreeStorageCreatorFunc = func(payload TreeStorageCreatePayload) (TreeStorage, error)

View File

@ -48,7 +48,7 @@ type changeBuilder struct {
keys *common.Keychain
}
func newChangeBuilder(keys *common.Keychain, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder {
func NewChangeBuilder(keys *common.Keychain, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder {
return &changeBuilder{keys: keys, rootChange: rootChange}
}
@ -155,12 +155,16 @@ func (c *changeBuilder) BuildContent(payload BuilderContent) (ch *Change, rawIdC
Identity: payload.Identity,
IsSnapshot: payload.IsSnapshot,
}
encrypted, err := payload.ReadKey.Encrypt(payload.Content)
if err != nil {
return
if payload.ReadKey != nil {
var encrypted []byte
encrypted, err = payload.ReadKey.Encrypt(payload.Content)
if err != nil {
return
}
change.ChangesData = encrypted
} else {
change.ChangesData = payload.Content
}
change.ChangesData = encrypted
marshalledChange, err := proto.Marshal(change)
if err != nil {
@ -188,7 +192,6 @@ func (c *changeBuilder) BuildContent(payload BuilderContent) (ch *Change, rawIdC
}
ch = NewChange(id, change, signature)
ch.Model = payload.Content
rawIdChange = &treechangeproto.RawTreeChangeWithId{
RawChange: marshalledRawChange,

View File

@ -116,6 +116,20 @@ func (mr *MockObjectTreeMockRecorder) DebugDump() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DebugDump", reflect.TypeOf((*MockObjectTree)(nil).DebugDump))
}
// Delete mocks base method.
func (m *MockObjectTree) Delete() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete")
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockObjectTreeMockRecorder) Delete() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockObjectTree)(nil).Delete))
}
// HasChanges mocks base method.
func (m *MockObjectTree) HasChanges(arg0 ...string) bool {
m.ctrl.T.Helper()

View File

@ -5,7 +5,7 @@ import (
"context"
"errors"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/common"
list2 "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/list"
list "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/list"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/symmetric"
@ -57,6 +57,7 @@ type ObjectTree interface {
AddContent(ctx context.Context, content SignableChangeContent) (AddResult, error)
AddRawChanges(ctx context.Context, changes ...*treechangeproto.RawTreeChangeWithId) (AddResult, error)
Delete() error
Close() error
}
@ -66,7 +67,7 @@ type objectTree struct {
validator ObjectTreeValidator
rawChangeLoader *rawChangeLoader
treeBuilder *treeBuilder
aclList list2.ACLList
aclList list.ACLList
id string
root *treechangeproto.RawTreeChangeWithId
@ -91,16 +92,16 @@ type objectTreeDeps struct {
treeStorage storage.TreeStorage
validator ObjectTreeValidator
rawChangeLoader *rawChangeLoader
aclList list2.ACLList
aclList list.ACLList
}
func defaultObjectTreeDeps(
rootChange *treechangeproto.RawTreeChangeWithId,
treeStorage storage.TreeStorage,
aclList list2.ACLList) objectTreeDeps {
aclList list.ACLList) objectTreeDeps {
keychain := common.NewKeychain()
changeBuilder := newChangeBuilder(keychain, rootChange)
changeBuilder := NewChangeBuilder(keychain, rootChange)
treeBuilder := newTreeBuilder(treeStorage, changeBuilder)
return objectTreeDeps{
changeBuilder: changeBuilder,
@ -186,16 +187,23 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt
ot.aclList.RLock()
defer ot.aclList.RUnlock()
state := ot.aclList.ACLState() // special method for own keys
readKey, err := state.CurrentReadKey()
if err != nil {
return
var (
state = ot.aclList.ACLState() // special method for own keys
readKey *symmetric.Key
readKeyHash uint64
)
if content.IsEncrypted {
readKeyHash = state.CurrentReadKeyHash()
readKey, err = state.CurrentReadKey()
if err != nil {
return
}
}
cnt = BuilderContent{
TreeHeadIds: ot.tree.Heads(),
AclHeadId: ot.aclList.Head().Id,
SnapshotBaseId: ot.tree.RootId(),
CurrentReadKeyHash: state.CurrentReadKeyHash(),
CurrentReadKeyHash: readKeyHash,
Identity: content.Identity,
IsSnapshot: content.IsSnapshot,
SigningKey: content.Key,
@ -439,9 +447,25 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate
ot.tree.Iterate(id, iterate)
return
}
decrypt := func(c *Change) (decrypted []byte, err error) {
// the change is not encrypted
if c.ReadKeyHash == 0 {
decrypted = c.Data
return
}
readKey, exists := ot.keys[c.ReadKeyHash]
if !exists {
err = list.ErrNoReadKey
return
}
ot.tree.Iterate(ot.tree.RootId(), func(c *Change) (isContinue bool) {
decrypted, err = readKey.Decrypt(c.Data)
return
}
ot.tree.Iterate(id, func(c *Change) (isContinue bool) {
var model any
// if already saved as a model
if c.Model != nil {
return iterate(c)
}
@ -449,14 +473,9 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate
if c.Id == ot.id {
return iterate(c)
}
readKey, exists := ot.keys[c.ReadKeyHash]
if !exists {
err = list2.ErrNoReadKey
return false
}
var decrypted []byte
decrypted, err = readKey.Decrypt(c.Data)
decrypted, err = decrypt(c)
if err != nil {
return false
}
@ -508,6 +527,10 @@ func (ot *objectTree) Close() error {
return nil
}
func (ot *objectTree) Delete() error {
return ot.treeStorage.Delete()
}
func (ot *objectTree) SnapshotPath() []string {
// TODO: Add error as return parameter
if ot.snapshotPathIsActual() {

View File

@ -116,7 +116,7 @@ func prepareTreeContext(t *testing.T, aclList list.ACLList) testTreeContext {
treeStorage := changeCreator.createNewTreeStorage("0", aclList.Head().Id)
root, _ := treeStorage.Root()
changeBuilder := &mockChangeBuilder{
originalBuilder: newChangeBuilder(nil, root),
originalBuilder: NewChangeBuilder(nil, root),
}
deps := objectTreeDeps{
changeBuilder: changeBuilder,

View File

@ -14,10 +14,11 @@ import (
)
type ObjectTreeCreatePayload struct {
SignKey signingkey.PrivKey
ChangeType string
SpaceId string
Identity []byte
SignKey signingkey.PrivKey
ChangeType string
SpaceId string
Identity []byte
IsEncrypted bool
}
func BuildObjectTree(treeStorage storage.TreeStorage, aclList list.ACLList) (ObjectTree, error) {
@ -71,7 +72,7 @@ func createObjectTree(
Seed: seed,
}
_, raw, err := newChangeBuilder(common.NewKeychain(), nil).BuildInitialContent(cnt)
_, raw, err := NewChangeBuilder(common.NewKeychain(), nil).BuildInitialContent(cnt)
if err != nil {
return
}

View File

@ -5,8 +5,9 @@ import (
)
type SignableChangeContent struct {
Data []byte
Key signingkey.PrivKey
Identity []byte
IsSnapshot bool
Data []byte
Key signingkey.PrivKey
Identity []byte
IsSnapshot bool
IsEncrypted bool
}

View File

@ -0,0 +1,58 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/periodicsync (interfaces: PeriodicSync)
// Package mock_periodicsync is a generated GoMock package.
package mock_periodicsync
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// 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))
}

View File

@ -1,4 +1,5 @@
package diffservice
//go:generate mockgen -destination mock_periodicsync/mock_periodicsync.go github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/periodicsync PeriodicSync
package periodicsync
import (
"context"
@ -11,7 +12,9 @@ type PeriodicSync interface {
Close()
}
func newPeriodicSync(periodSeconds int, syncer DiffSyncer, l *zap.Logger) *periodicSync {
type SyncerFunc func(ctx context.Context) error
func NewPeriodicSync(periodSeconds int, syncer SyncerFunc, l *zap.Logger) PeriodicSync {
ctx, cancel := context.WithCancel(context.Background())
return &periodicSync{
syncer: syncer,
@ -25,7 +28,7 @@ func newPeriodicSync(periodSeconds int, syncer DiffSyncer, l *zap.Logger) *perio
type periodicSync struct {
log *zap.Logger
syncer DiffSyncer
syncer SyncerFunc
syncCtx context.Context
syncCancel context.CancelFunc
syncLoopDone chan struct{}
@ -42,7 +45,7 @@ func (p *periodicSync) syncLoop(periodSeconds int) {
doSync := func() {
ctx, cancel := context.WithTimeout(p.syncCtx, time.Minute)
defer cancel()
if err := p.syncer.Sync(ctx); err != nil {
if err := p.syncer(ctx); err != nil {
p.log.Warn("periodic sync error", zap.Error(err))
}
}

View File

@ -1,9 +1,10 @@
package diffservice
package periodicsync
import (
"context"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app/logger"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice/mock_diffservice"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"testing"
"time"
)
@ -14,25 +15,34 @@ func TestPeriodicSync_Run(t *testing.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)
times := 0
diffSyncer := func(ctx context.Context) (err error) {
times += 1
return nil
}
pSync := NewPeriodicSync(secs, diffSyncer, l)
pSync.Run()
pSync.Close()
require.Equal(t, 1, times)
})
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)
times := 0
diffSyncer := func(ctx context.Context) (err error) {
times += 1
return nil
}
pSync := NewPeriodicSync(secs, diffSyncer, l)
pSync.Run()
time.Sleep(time.Second * time.Duration(secs))
pSync.Close()
require.Equal(t, 2, times)
})
}

View File

@ -10,6 +10,9 @@ require (
github.com/akrylysov/pogreb v0.10.1
github.com/anytypeio/go-anytype-infrastructure-experiments/common v0.0.0-00010101000000-000000000000
github.com/anytypeio/go-anytype-infrastructure-experiments/consensus v0.0.0-00010101000000-000000000000
github.com/golang/mock v1.6.0
github.com/ipfs/go-cid v0.3.2
github.com/stretchr/testify v1.8.0
go.uber.org/zap v1.23.0
)
@ -19,6 +22,7 @@ require (
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // 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.1.0 // indirect
github.com/fogleman/gg v1.3.0 // indirect
github.com/goccy/go-graphviz v0.0.9 // indirect
@ -26,12 +30,10 @@ require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/huandu/skiplist v1.2.0 // indirect
github.com/ipfs/go-cid v0.3.2 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-libp2p v0.23.2 // indirect
github.com/libp2p/go-libp2p-core v0.20.1 // indirect
github.com/libp2p/go-openssl v0.1.0 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-pointer v0.0.1 // indirect
@ -46,6 +48,7 @@ require (
github.com/multiformats/go-multihash v0.2.1 // indirect
github.com/multiformats/go-varint v0.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.13.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect

View File

@ -109,6 +109,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -191,8 +192,6 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-libp2p v0.23.2 h1:yqyTeKQJyofWXxEv/eEVUvOrGdt/9x+0PIQ4N1kaxmE=
github.com/libp2p/go-libp2p v0.23.2/go.mod h1:s9DEa5NLR4g+LZS+md5uGU4emjMWFiqkZr6hBTY8UxI=
github.com/libp2p/go-libp2p-core v0.20.1 h1:fQz4BJyIFmSZAiTbKV8qoYhEH5Dtv/cVhZbG3Ib/+Cw=
github.com/libp2p/go-libp2p-core v0.20.1/go.mod h1:6zR8H7CvQWgYLsbG4on6oLNSGcyKaYFSEYyDt51+bIY=
github.com/libp2p/go-openssl v0.1.0 h1:LBkKEcUv6vtZIQLVTegAil8jbNpJErQ9AnT+bWV+Ooo=
github.com/libp2p/go-openssl v0.1.0/go.mod h1:OiOxwPpL3n4xlenjx2h7AwSGaFSC/KZvf6gNdOBQMtc=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
@ -271,11 +270,14 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -497,6 +499,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
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/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=

View File

@ -70,3 +70,16 @@ func (c *treeCache) GetTree(ctx context.Context, spaceId, id string) (tr tree.Ob
tr = value.(tree.ObjectTree)
return
}
func (c *treeCache) DeleteTree(ctx context.Context, spaceId, treeId string) (err error) {
tr, err := c.GetTree(ctx, spaceId, treeId)
if err != nil {
return
}
err = tr.Delete()
if err != nil {
return
}
_, err = c.cache.Remove(treeId)
return
}

View File

@ -19,22 +19,34 @@ func (r *rpcHandler) PullSpace(ctx context.Context, request *spacesyncproto.Pull
return
}
description := sp.Description()
spaceDesc, err := sp.Description()
if err != nil {
err = spacesyncproto.ErrUnexpected
return
}
resp = &spacesyncproto.PullSpaceResponse{
SpaceHeader: description.SpaceHeader,
AclPayload: description.AclPayload,
AclPayloadId: description.AclId,
Payload: &spacesyncproto.SpacePayload{
SpaceHeader: spaceDesc.SpaceHeader,
AclPayloadId: spaceDesc.AclId,
AclPayload: spaceDesc.AclPayload,
SpaceSettingsPayload: spaceDesc.SpaceSettingsPayload,
SpaceSettingsPayloadId: spaceDesc.SpaceSettingsId,
},
}
return
}
func (r *rpcHandler) PushSpace(ctx context.Context, req *spacesyncproto.PushSpaceRequest) (resp *spacesyncproto.PushSpaceResponse, err error) {
description := commonspace.SpaceDescription{
SpaceHeader: req.SpaceHeader,
AclId: req.AclPayloadId,
AclPayload: req.AclPayload,
SpaceHeader: req.Payload.SpaceHeader,
AclId: req.Payload.AclPayloadId,
AclPayload: req.Payload.AclPayload,
SpaceSettingsPayload: req.Payload.SpaceSettingsPayload,
SpaceSettingsId: req.Payload.SpaceSettingsPayloadId,
}
err = r.s.AddSpace(ctx, description)
ctx = context.WithValue(ctx, commonspace.AddSpaceCtxKey, description)
_, err = r.s.GetSpace(ctx, description.SpaceHeader.GetId())
if err != nil {
return
}

View File

@ -22,7 +22,6 @@ func New() Service {
}
type Service interface {
AddSpace(ctx context.Context, description commonspace.SpaceDescription) (err error)
GetSpace(ctx context.Context, id string) (commonspace.Space, error)
app.ComponentRunnable
}
@ -63,10 +62,6 @@ func (s *service) GetSpace(ctx context.Context, id string) (commonspace.Space, e
return v.(commonspace.Space), nil
}
func (s *service) AddSpace(ctx context.Context, description commonspace.SpaceDescription) (err error) {
return s.commonSpace.AddSpace(ctx, description)
}
func (s *service) loadSpace(ctx context.Context, id string) (value ocache.Object, err error) {
cc, err := s.commonSpace.NewSpace(ctx, id)
if err != nil {

View File

@ -1,6 +1,7 @@
package storage
import (
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
"strings"
)
@ -25,6 +26,7 @@ func (a aclKeys) RawRecordKey(id string) []byte {
type treeKeys struct {
id string
prefix string
headsKey []byte
}
@ -32,6 +34,7 @@ func newTreeKeys(id string) treeKeys {
return treeKeys{
id: id,
headsKey: storage.JoinStringsToBytes("t", id, "heads"),
prefix: fmt.Sprintf("t/%s", id),
}
}
@ -43,6 +46,10 @@ func (t treeKeys) RawChangeKey(id string) []byte {
return storage.JoinStringsToBytes("t", t.id, id)
}
func (t treeKeys) isTreeRelatedKey(key string) bool {
return strings.HasPrefix(key, t.prefix)
}
type spaceKeys struct {
headerKey []byte
}
@ -51,7 +58,10 @@ func newSpaceKeys(spaceId string) spaceKeys {
return spaceKeys{headerKey: storage.JoinStringsToBytes("s", spaceId)}
}
var spaceIdKey = []byte("spaceId")
var (
spaceIdKey = []byte("spaceId")
spaceSettingsIdKey = []byte("spaceSettingsId")
)
func (s spaceKeys) SpaceIdKey() []byte {
return spaceIdKey
@ -61,7 +71,15 @@ func (s spaceKeys) HeaderKey() []byte {
return s.headerKey
}
func isRootIdKey(key string) bool {
func (s spaceKeys) SpaceSettingsIdKey() []byte {
return spaceSettingsIdKey
}
func (s spaceKeys) TreeDeletedKey(id string) []byte {
return storage.JoinStringsToBytes("del", id)
}
func isTreeHeadsKey(key string) bool {
return strings.HasPrefix(key, "t/") && strings.HasSuffix(key, "/heads")
}

View File

@ -6,25 +6,25 @@ import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
"go.uber.org/zap"
"path"
"sync"
"time"
)
var defPogrebOptions = &pogreb.Options{
BackgroundCompactionInterval: time.Minute * 5,
}
var log = logger.NewNamed("storage.spacestorage")
var (
defPogrebOptions = &pogreb.Options{BackgroundCompactionInterval: time.Minute * 5}
log = logger.NewNamed("storage.spacestorage")
spaceValidationFunc = spacestorage.ValidateSpaceStorageCreatePayload
)
type spaceStorage struct {
spaceId string
objDb *pogreb.DB
keys spaceKeys
aclStorage storage.ListStorage
header *spacesyncproto.RawSpaceHeaderWithId
mx sync.Mutex
spaceId string
spaceSettingsId string
objDb *pogreb.DB
keys spaceKeys
aclStorage storage.ListStorage
header *spacesyncproto.RawSpaceHeaderWithId
}
func newSpaceStorage(rootPath string, spaceId string) (store spacestorage.SpaceStorage, err error) {
@ -61,15 +61,25 @@ func newSpaceStorage(rootPath string, spaceId string) (store spacestorage.SpaceS
return
}
spaceSettingsId, err := objDb.Get(keys.SpaceSettingsIdKey())
if err != nil {
return
}
if spaceSettingsId == nil {
err = spacestorage.ErrSpaceStorageMissing
return
}
aclStorage, err := newListStorage(objDb)
if err != nil {
return
}
store = &spaceStorage{
spaceId: spaceId,
objDb: objDb,
keys: keys,
spaceId: spaceId,
spaceSettingsId: string(spaceSettingsId),
objDb: objDb,
keys: keys,
header: &spacesyncproto.RawSpaceHeaderWithId{
RawHeader: header,
Id: spaceId,
@ -103,8 +113,35 @@ func createSpaceStorage(rootPath string, payload spacestorage.SpaceStorageCreate
err = spacesyncproto.ErrSpaceExists
return
}
err = spaceValidationFunc(payload)
if err != nil {
return
}
aclStorage, err := createListStorage(db, payload.RecWithId)
aclStorage, err := createListStorage(db, payload.AclWithId)
if err != nil {
return
}
store = &spaceStorage{
spaceId: payload.SpaceHeaderWithId.Id,
objDb: db,
keys: keys,
aclStorage: aclStorage,
spaceSettingsId: payload.SpaceSettingsWithId.Id,
header: payload.SpaceHeaderWithId,
}
_, err = store.CreateTreeStorage(storage.TreeStorageCreatePayload{
RootRawChange: payload.SpaceSettingsWithId,
Changes: []*treechangeproto.RawTreeChangeWithId{payload.SpaceSettingsWithId},
Heads: []string{payload.SpaceSettingsWithId.Id},
})
if err != nil {
return
}
err = db.Put(keys.SpaceSettingsIdKey(), []byte(payload.SpaceSettingsWithId.Id))
if err != nil {
return
}
@ -119,13 +156,6 @@ func createSpaceStorage(rootPath string, payload spacestorage.SpaceStorageCreate
return
}
store = &spaceStorage{
spaceId: payload.SpaceHeaderWithId.Id,
objDb: db,
keys: keys,
aclStorage: aclStorage,
header: payload.SpaceHeaderWithId,
}
return
}
@ -133,15 +163,15 @@ func (s *spaceStorage) Id() string {
return s.spaceId
}
func (s *spaceStorage) SpaceSettingsId() string {
return s.spaceSettingsId
}
func (s *spaceStorage) TreeStorage(id string) (storage.TreeStorage, error) {
return newTreeStorage(s.objDb, id)
}
func (s *spaceStorage) CreateTreeStorage(payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) {
// we have mutex here, so we prevent overwriting the heads of a tree on concurrent creation
s.mx.Lock()
defer s.mx.Unlock()
return createTreeStorage(s.objDb, payload)
}
@ -153,13 +183,26 @@ func (s *spaceStorage) SpaceHeader() (header *spacesyncproto.RawSpaceHeaderWithI
return s.header, nil
}
func (s *spaceStorage) SetTreeDeletedStatus(id, state string) (err error) {
return s.objDb.Put(s.keys.TreeDeletedKey(id), []byte(state))
}
func (s *spaceStorage) TreeDeletedStatus(id string) (status string, err error) {
res, err := s.objDb.Get(s.keys.TreeDeletedKey(id))
if err != nil {
return
}
status = string(res)
return
}
func (s *spaceStorage) StoredIds() (ids []string, err error) {
index := s.objDb.Items()
key, _, err := index.Next()
for err == nil {
strKey := string(key)
if isRootIdKey(strKey) {
if isTreeHeadsKey(strKey) {
ids = append(ids, getRootId(strKey))
}
key, _, err = index.Next()

View File

@ -4,8 +4,10 @@ import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
"github.com/stretchr/testify/require"
"os"
"sort"
"strconv"
"testing"
)
@ -19,9 +21,14 @@ func spaceTestPayload() spacestorage.SpaceStorageCreatePayload {
Payload: []byte("aclRoot"),
Id: "aclRootId",
}
settings := &treechangeproto.RawTreeChangeWithId{
RawChange: []byte("settings"),
Id: "settingsId",
}
return spacestorage.SpaceStorageCreatePayload{
RecWithId: aclRoot,
SpaceHeaderWithId: header,
AclWithId: aclRoot,
SpaceHeaderWithId: header,
SpaceSettingsWithId: settings,
}
}
@ -32,7 +39,7 @@ func testSpace(t *testing.T, store spacestorage.SpaceStorage, payload spacestora
aclStorage, err := store.ACLStorage()
require.NoError(t, err)
testList(t, aclStorage, payload.RecWithId, payload.RecWithId.Id)
testList(t, aclStorage, payload.AclWithId, payload.AclWithId.Id)
}
func TestSpaceStorage_Create(t *testing.T) {
@ -68,7 +75,7 @@ func TestSpaceStorage_NewAndCreateTree(t *testing.T) {
}()
testSpace(t, store, payload)
t.Run("create tree and get tree", func(t *testing.T) {
t.Run("create tree, get tree and mark deleted", func(t *testing.T) {
payload := treeTestPayload()
treeStore, err := store.CreateTreeStorage(payload)
require.NoError(t, err)
@ -77,6 +84,14 @@ func TestSpaceStorage_NewAndCreateTree(t *testing.T) {
otherStore, err := store.TreeStorage(payload.RootRawChange.Id)
require.NoError(t, err)
testTreePayload(t, otherStore, payload)
initialStatus := "deleted"
err = store.SetTreeDeletedStatus(otherStore.Id(), initialStatus)
require.NoError(t, err)
status, err := store.TreeDeletedStatus(otherStore.Id())
require.NoError(t, err)
require.Equal(t, initialStatus, status)
})
}
@ -100,8 +115,11 @@ func TestSpaceStorage_StoredIds(t *testing.T) {
_, err := store.CreateTreeStorage(treePayload)
require.NoError(t, err)
}
ids = append(ids, payload.SpaceSettingsWithId.Id)
sort.Strings(ids)
storedIds, err := store.StoredIds()
sort.Strings(storedIds)
require.NoError(t, err)
require.Equal(t, ids, storedIds)
}

View File

@ -122,7 +122,7 @@ func (t *treeStorage) GetRawChange(ctx context.Context, id string) (raw *treecha
return
}
if res == nil {
err = storage.ErrUnkownChange
err = storage.ErrUnknownChange
}
raw = &treechangeproto.RawTreeChangeWithId{
@ -135,3 +135,36 @@ func (t *treeStorage) GetRawChange(ctx context.Context, id string) (raw *treecha
func (t *treeStorage) HasChange(ctx context.Context, id string) (bool, error) {
return t.db.Has(t.keys.RawChangeKey(id))
}
func (t *treeStorage) Delete() (err error) {
storedKeys, err := t.storedKeys()
if err != nil {
return
}
for _, k := range storedKeys {
err = t.db.Delete(k)
if err != nil {
return
}
}
return
}
func (t *treeStorage) storedKeys() (keys [][]byte, err error) {
index := t.db.Items()
key, _, err := index.Next()
for err == nil {
strKey := string(key)
if t.keys.isTreeRelatedKey(strKey) {
keys = append(keys, key)
}
key, _, err = index.Next()
}
if err != pogreb.ErrIterationDone {
return
}
err = nil
return
}

View File

@ -42,6 +42,26 @@ func (fx *fixture) stop(t *testing.T) {
require.NoError(t, fx.db.Close())
}
func (fx *fixture) testNoKeysExist(t *testing.T, treeId string) {
index := fx.db.Items()
treeKeys := newTreeKeys(treeId)
var keys [][]byte
key, _, err := index.Next()
for err == nil {
strKey := string(key)
if treeKeys.isTreeRelatedKey(strKey) {
keys = append(keys, key)
}
key, _, err = index.Next()
}
require.Equal(t, pogreb.ErrIterationDone, err)
require.Equal(t, 0, len(keys))
res, err := fx.db.Has(treeKeys.HeadsKey())
require.NoError(t, err)
require.False(t, res)
}
func testTreePayload(t *testing.T, store storage.TreeStorage, payload storage.TreeStorageCreatePayload) {
require.Equal(t, payload.RootRawChange.Id, store.Id())
@ -119,3 +139,31 @@ func TestTreeStorage_Methods(t *testing.T) {
require.False(t, has)
})
}
func TestTreeStorage_Delete(t *testing.T) {
fx := newFixture(t)
fx.open(t)
payload := treeTestPayload()
_, err := createTreeStorage(fx.db, payload)
require.NoError(t, err)
fx.stop(t)
fx.open(t)
defer fx.stop(t)
store, err := newTreeStorage(fx.db, payload.RootRawChange.Id)
require.NoError(t, err)
testTreePayload(t, store, payload)
t.Run("add raw change, get change and has change", func(t *testing.T) {
newChange := &treechangeproto.RawTreeChangeWithId{RawChange: []byte("ab"), Id: "newId"}
require.NoError(t, store.AddRawChange(newChange))
err = store.Delete()
require.NoError(t, err)
_, err = newTreeStorage(fx.db, payload.RootRawChange.Id)
require.Equal(t, err, storage.ErrUnknownTreeId)
fx.testNoKeysExist(t, payload.RootRawChange.Id)
})
}