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 creates new document in space
CreateDocument(spaceId string) (id string, err error) 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 gets all ids of documents in space
AllDocumentIds(spaceId string) (ids []string, err error) AllDocumentIds(spaceId string) (ids []string, err error)
// AddText adds text to space document // 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) 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) { func (c *controller) AllDocumentIds(spaceId string) (ids []string, err error) {
return c.docService.AllDocumentIds(spaceId) 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("/loadSpace", s.loadSpace)
mux.HandleFunc("/allSpaceIds", s.allSpaceIds) mux.HandleFunc("/allSpaceIds", s.allSpaceIds)
mux.HandleFunc("/createDocument", s.createDocument) mux.HandleFunc("/createDocument", s.createDocument)
mux.HandleFunc("/deleteDocument", s.deleteDocument)
mux.HandleFunc("/allDocumentIds", s.allDocumentIds) mux.HandleFunc("/allDocumentIds", s.allDocumentIds)
mux.HandleFunc("/addText", s.addText) mux.HandleFunc("/addText", s.addText)
mux.HandleFunc("/dumpDocumentTree", s.dumpDocumentTree) mux.HandleFunc("/dumpDocumentTree", s.dumpDocumentTree)
@ -134,6 +135,18 @@ func (s *service) createDocument(w http.ResponseWriter, req *http.Request) {
sendText(w, http.StatusOK, id) 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) { func (s *service) allDocumentIds(w http.ResponseWriter, req *http.Request) {
query := req.URL.Query() query := req.URL.Query()
spaceId := query.Get("spaceId") 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 { if err != nil {
return 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 return
} }

View File

@ -19,22 +19,34 @@ func (r *rpcHandler) PullSpace(ctx context.Context, request *spacesyncproto.Pull
return return
} }
description := sp.Description() spaceDesc, err := sp.Description()
if err != nil {
err = spacesyncproto.ErrUnexpected
return
}
resp = &spacesyncproto.PullSpaceResponse{ resp = &spacesyncproto.PullSpaceResponse{
SpaceHeader: description.SpaceHeader, Payload: &spacesyncproto.SpacePayload{
AclPayload: description.AclPayload, SpaceHeader: spaceDesc.SpaceHeader,
AclPayloadId: description.AclId, AclPayloadId: spaceDesc.AclId,
AclPayload: spaceDesc.AclPayload,
SpaceSettingsPayload: spaceDesc.SpaceSettingsPayload,
SpaceSettingsPayloadId: spaceDesc.SpaceSettingsId,
},
} }
return return
} }
func (r *rpcHandler) PushSpace(ctx context.Context, req *spacesyncproto.PushSpaceRequest) (resp *spacesyncproto.PushSpaceResponse, err error) { func (r *rpcHandler) PushSpace(ctx context.Context, req *spacesyncproto.PushSpaceRequest) (resp *spacesyncproto.PushSpaceResponse, err error) {
description := commonspace.SpaceDescription{ description := commonspace.SpaceDescription{
SpaceHeader: req.SpaceHeader, SpaceHeader: req.Payload.SpaceHeader,
AclId: req.AclPayloadId, AclId: req.Payload.AclPayloadId,
AclPayload: req.AclPayload, 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 { if err != nil {
return return
} }

View File

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

View File

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

View File

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

View File

@ -35,6 +35,7 @@ type treeKeys struct {
spaceId string spaceId string
headsKey []byte headsKey []byte
rootKey []byte rootKey []byte
rawChangePrefix []byte
} }
func newTreeKeys(spaceId, id string) treeKeys { func newTreeKeys(spaceId, id string) treeKeys {
@ -43,6 +44,7 @@ func newTreeKeys(spaceId, id string) treeKeys {
spaceId: spaceId, spaceId: spaceId,
headsKey: storage.JoinStringsToBytes("space", spaceId, "t", id, "heads"), headsKey: storage.JoinStringsToBytes("space", spaceId, "t", id, "heads"),
rootKey: storage.JoinStringsToBytes("space", spaceId, "t", "rootId", id), 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) return storage.JoinStringsToBytes("space", t.spaceId, "t", t.id, id)
} }
func (t treeKeys) RawChangePrefix() []byte {
return t.rawChangePrefix
}
type spaceKeys struct { type spaceKeys struct {
spaceId string
headerKey []byte headerKey []byte
treePrefixKey []byte treePrefixKey []byte
spaceSettingsIdKey []byte
} }
func newSpaceKeys(spaceId string) spaceKeys { func newSpaceKeys(spaceId string) spaceKeys {
return spaceKeys{ return spaceKeys{
spaceId: spaceId,
headerKey: storage.JoinStringsToBytes("space", "header", spaceId), headerKey: storage.JoinStringsToBytes("space", "header", spaceId),
treePrefixKey: storage.JoinStringsToBytes("space", spaceId, "t", "rootId"), 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 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 { type storageServiceKeys struct {
spacePrefix []byte spacePrefix []byte
} }

View File

@ -4,19 +4,21 @@ import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
storage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/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" "github.com/dgraph-io/badger/v3"
"sync"
) )
type spaceStorage struct { type spaceStorage struct {
spaceId string spaceId string
spaceSettingsId string
objDb *badger.DB objDb *badger.DB
keys spaceKeys keys spaceKeys
aclStorage storage.ListStorage aclStorage storage.ListStorage
header *spacesyncproto.RawSpaceHeaderWithId header *spacesyncproto.RawSpaceHeaderWithId
mx sync.Mutex
} }
var spaceValidationFunc = spacestorage.ValidateSpaceStorageCreatePayload
func newSpaceStorage(objDb *badger.DB, spaceId string) (store spacestorage.SpaceStorage, err error) { func newSpaceStorage(objDb *badger.DB, spaceId string) (store spacestorage.SpaceStorage, err error) {
keys := newSpaceKeys(spaceId) keys := newSpaceKeys(spaceId)
err = objDb.View(func(txn *badger.Txn) error { err = objDb.View(func(txn *badger.Txn) error {
@ -30,8 +32,13 @@ func newSpaceStorage(objDb *badger.DB, spaceId string) (store spacestorage.Space
return err return err
} }
spaceSettingsId, err := getTxn(txn, keys.SpaceSettingsId())
if err != nil {
return err
}
store = &spaceStorage{ store = &spaceStorage{
spaceId: spaceId, spaceId: spaceId,
spaceSettingsId: string(spaceSettingsId),
objDb: objDb, objDb: objDb,
keys: keys, keys: keys,
header: &spacesyncproto.RawSpaceHeaderWithId{ header: &spacesyncproto.RawSpaceHeaderWithId{
@ -54,8 +61,32 @@ func createSpaceStorage(db *badger.DB, payload spacestorage.SpaceStorageCreatePa
err = spacesyncproto.ErrSpaceExists err = spacesyncproto.ErrSpaceExists
return 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 { 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 { if err != nil {
return err return err
} }
@ -65,15 +96,10 @@ func createSpaceStorage(db *badger.DB, payload spacestorage.SpaceStorageCreatePa
return err return err
} }
store = &spaceStorage{ spaceStore.aclStorage = aclStorage
spaceId: payload.SpaceHeaderWithId.Id,
objDb: db,
keys: keys,
aclStorage: aclStorage,
header: payload.SpaceHeaderWithId,
}
return nil return nil
}) })
store = spaceStore
return return
} }
@ -81,15 +107,15 @@ func (s *spaceStorage) Id() string {
return s.spaceId return s.spaceId
} }
func (s *spaceStorage) SpaceSettingsId() string {
return s.spaceSettingsId
}
func (s *spaceStorage) TreeStorage(id string) (storage.TreeStorage, error) { func (s *spaceStorage) TreeStorage(id string) (storage.TreeStorage, error) {
return newTreeStorage(s.objDb, s.spaceId, id) return newTreeStorage(s.objDb, s.spaceId, id)
} }
func (s *spaceStorage) CreateTreeStorage(payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) { 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) 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() { for it.Rewind(); it.Valid(); it.Next() {
item := it.Item() 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 { if len(id) <= len(s.keys.TreeRootPrefix())+1 {
continue continue
} }
@ -124,6 +151,27 @@ func (s *spaceStorage) StoredIds() (ids []string, err error) {
return 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) { func (s *spaceStorage) Close() (err error) {
return nil return nil
} }

View File

@ -4,7 +4,9 @@ import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" 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/aclrecordproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"sort"
"strconv" "strconv"
"testing" "testing"
) )
@ -18,9 +20,14 @@ func spaceTestPayload() spacestorage.SpaceStorageCreatePayload {
Payload: []byte("aclRoot"), Payload: []byte("aclRoot"),
Id: "aclRootId", Id: "aclRootId",
} }
settings := &treechangeproto.RawTreeChangeWithId{
RawChange: []byte("settings"),
Id: "settingsId",
}
return spacestorage.SpaceStorageCreatePayload{ return spacestorage.SpaceStorageCreatePayload{
RecWithId: aclRoot, AclWithId: aclRoot,
SpaceHeaderWithId: header, SpaceHeaderWithId: header,
SpaceSettingsWithId: settings,
} }
} }
@ -31,7 +38,7 @@ func testSpace(t *testing.T, store spacestorage.SpaceStorage, payload spacestora
aclStorage, err := store.ACLStorage() aclStorage, err := store.ACLStorage()
require.NoError(t, err) 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) { func TestSpaceStorage_Create(t *testing.T) {
@ -68,7 +75,7 @@ func TestSpaceStorage_NewAndCreateTree(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
testSpace(t, store, payload) 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() payload := treeTestPayload()
treeStore, err := store.CreateTreeStorage(payload) treeStore, err := store.CreateTreeStorage(payload)
require.NoError(t, err) require.NoError(t, err)
@ -77,6 +84,14 @@ func TestSpaceStorage_NewAndCreateTree(t *testing.T) {
otherStore, err := store.TreeStorage(payload.RootRawChange.Id) otherStore, err := store.TreeStorage(payload.RootRawChange.Id)
require.NoError(t, err) require.NoError(t, err)
testTreePayload(t, otherStore, payload) 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) _, err := store.CreateTreeStorage(treePayload)
require.NoError(t, err) require.NoError(t, err)
} }
ids = append(ids, payload.SpaceSettingsWithId.Id)
sort.Strings(ids)
storedIds, err := store.StoredIds() storedIds, err := store.StoredIds()
require.NoError(t, err) require.NoError(t, err)
sort.Strings(storedIds)
require.Equal(t, ids, 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) { func (t *treeStorage) HasChange(ctx context.Context, id string) (bool, error) {
return hasDB(t.db, t.keys.RawChangeKey(id)), nil 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()) 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) { func TestTreeStorage_Create(t *testing.T) {
fx := newFixture(t) fx := newFixture(t)
fx.open(t) fx.open(t)
@ -121,3 +157,32 @@ func TestTreeStorage_Methods(t *testing.T) {
require.False(t, has) 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 package account
import ( import (

View File

@ -3,6 +3,7 @@ package commonspace
import ( import (
"context" "context"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/objectgetter" "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/syncacl"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/treegetter" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/treegetter"
) )
@ -11,13 +12,15 @@ type commonSpaceGetter struct {
spaceId string spaceId string
aclList *syncacl.SyncACL aclList *syncacl.SyncACL
treeGetter treegetter.TreeGetter 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{ return &commonSpaceGetter{
spaceId: spaceId, spaceId: spaceId,
aclList: aclList, aclList: aclList,
treeGetter: treeGetter, treeGetter: treeGetter,
settings: settings,
} }
} }
@ -26,6 +29,10 @@ func (c *commonSpaceGetter) GetObject(ctx context.Context, objectId string) (obj
obj = c.aclList obj = c.aclList
return return
} }
if c.settings.ID() == objectId {
obj = c.settings.(objectgetter.Object)
return
}
t, err := c.treeGetter.GetTree(ctx, c.spaceId, objectId) t, err := c.treeGetter.GetTree(ctx, c.spaceId, objectId)
if err != nil { if err != nil {
return 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 package diffservice
import ( import (
"context" "context"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/remotediff" "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/spacesyncproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" "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/commonspace/treegetter"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf" "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/pkg/ldiff"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/periodicsync"
"go.uber.org/zap" "go.uber.org/zap"
"strings" "strings"
) )
@ -16,19 +18,20 @@ import (
type DiffService interface { type DiffService interface {
HeadNotifiable HeadNotifiable
HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error) HandleRangeRequest(ctx context.Context, req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse, err error)
RemoveObject(id string) RemoveObjects(ids []string)
AllIds() []string AllIds() []string
Init(objectIds []string) Init(objectIds []string, deletionState deletionstate.DeletionState)
Close() (err error) Close() (err error)
} }
type diffService struct { type diffService struct {
spaceId string spaceId string
periodicSync PeriodicSync periodicSync periodicsync.PeriodicSync
storage storage.SpaceStorage storage storage.SpaceStorage
diff ldiff.Diff diff ldiff.Diff
log *zap.Logger log *zap.Logger
syncer DiffSyncer
syncPeriod int syncPeriod int
} }
@ -45,11 +48,12 @@ func NewDiffService(
l := log.With(zap.String("spaceId", spaceId)) l := log.With(zap.String("spaceId", spaceId))
factory := spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceClient) factory := spacesyncproto.ClientFactoryFunc(spacesyncproto.NewDRPCSpaceClient)
syncer := newDiffSyncer(spaceId, diff, confConnector, cache, storage, factory, l) syncer := newDiffSyncer(spaceId, diff, confConnector, cache, storage, factory, l)
periodicSync := newPeriodicSync(syncPeriod, syncer, l) periodicSync := periodicsync.NewPeriodicSync(syncPeriod, syncer.Sync, l)
return &diffService{ return &diffService{
spaceId: spaceId, spaceId: spaceId,
storage: storage, storage: storage,
syncer: syncer,
periodicSync: periodicSync, periodicSync: periodicSync,
diff: diff, diff: diff,
log: log, 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.fillDiff(objectIds)
d.syncer.Init(deletionState)
d.periodicSync.Run() d.periodicSync.Run()
} }
@ -67,19 +72,15 @@ func (d *diffService) HandleRangeRequest(ctx context.Context, req *spacesyncprot
} }
func (d *diffService) UpdateHeads(id string, heads []string) { func (d *diffService) UpdateHeads(id string, heads []string) {
d.diff.Set(ldiff.Element{ d.syncer.UpdateHeads(id, heads)
Id: id,
Head: concatStrings(heads),
})
} }
func (d *diffService) AllIds() []string { func (d *diffService) AllIds() []string {
return d.diff.Ids() return d.diff.Ids()
} }
func (d *diffService) RemoveObject(id string) { func (d *diffService) RemoveObjects(ids []string) {
// TODO: add space document to remove ids d.syncer.RemoveObjects(ids)
d.diff.RemoveId(id)
} }
func (d *diffService) Close() (err error) { func (d *diffService) Close() (err error) {

View File

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

View File

@ -3,6 +3,7 @@ package diffservice
import ( import (
"context" "context"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/remotediff" "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/spacesyncproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" "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/commonspace/treegetter"
@ -16,6 +17,9 @@ import (
type DiffSyncer interface { type DiffSyncer interface {
Sync(ctx context.Context) error Sync(ctx context.Context) error
RemoveObjects(ids []string)
UpdateHeads(id string, heads []string)
Init(deletionState deletionstate.DeletionState)
} }
func newDiffSyncer( func newDiffSyncer(
@ -45,6 +49,28 @@ type diffSyncer struct {
storage storage.SpaceStorage storage storage.SpaceStorage
clientFactory spacesyncproto.ClientFactory clientFactory spacesyncproto.ClientFactory
log *zap.Logger 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 { 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 { if err == spacesyncproto.ErrSpaceMissing {
return d.sendPushSpaceRequest(ctx, cl) 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()) ctx = peer.CtxWithPeerId(ctx, p.Id())
d.pingTreesInCache(ctx, newIds) d.pingTreesInCache(ctx, filteredIds)
d.pingTreesInCache(ctx, changedIds)
d.pingTreesInCache(ctx, removedIds)
d.log.Info("sync done:", zap.Int("newIds", len(newIds)), d.log.Info("sync done:", zap.Int("newIds", len(newIds)),
zap.Int("changedIds", len(changedIds)), zap.Int("changedIds", len(changedIds)),
zap.Int("removedIds", len(removedIds))) zap.Int("removedIds", len(removedIds)),
zap.Int("already deleted ids", totalLen-len(filteredIds)))
return return
} }
@ -108,10 +136,24 @@ func (d *diffSyncer) sendPushSpaceRequest(ctx context.Context, cl spacesyncproto
return return
} }
_, err = cl.PushSpace(ctx, &spacesyncproto.PushSpaceRequest{ 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, SpaceHeader: header,
AclPayload: root.Payload, AclPayload: root.Payload,
AclPayloadId: root.Id, AclPayloadId: root.Id,
SpaceSettingsPayload: spaceSettingsRoot.RawChange,
SpaceSettingsPayloadId: spaceSettingsRoot.Id,
}
_, err = cl.PushSpace(ctx, &spacesyncproto.PushSpaceRequest{
Payload: spacePayload,
}) })
return return
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/app/logger" "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/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"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto/mock_spacesyncproto" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto/mock_spacesyncproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage/mock_storage" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/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/nodeconf/mock_nodeconf"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto" "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_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/anytypeio/go-anytype-infrastructure-experiments/common/pkg/ldiff/mock_ldiff"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/libp2p/go-libp2p/core/sec" "github.com/libp2p/go-libp2p/core/sec"
@ -25,6 +29,7 @@ import (
type pushSpaceRequestMatcher struct { type pushSpaceRequestMatcher struct {
spaceId string spaceId string
aclRootId string aclRootId string
settingsId string
spaceHeader *spacesyncproto.RawSpaceHeaderWithId spaceHeader *spacesyncproto.RawSpaceHeaderWithId
} }
@ -34,7 +39,7 @@ func (p pushSpaceRequestMatcher) Matches(x interface{}) bool {
return false 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 { func (p pushSpaceRequestMatcher) String() string {
@ -77,10 +82,12 @@ func (m mockPeer) NewStream(ctx context.Context, rpc string, enc drpc.Encoding)
func newPushSpaceRequestMatcher( func newPushSpaceRequestMatcher(
spaceId string, spaceId string,
aclRootId string, aclRootId string,
settingsId string,
spaceHeader *spacesyncproto.RawSpaceHeaderWithId) *pushSpaceRequestMatcher { spaceHeader *spacesyncproto.RawSpaceHeaderWithId) *pushSpaceRequestMatcher {
return &pushSpaceRequestMatcher{ return &pushSpaceRequestMatcher{
spaceId: spaceId, spaceId: spaceId,
aclRootId: aclRootId, aclRootId: aclRootId,
settingsId: settingsId,
spaceHeader: spaceHeader, spaceHeader: spaceHeader,
} }
} }
@ -99,18 +106,22 @@ func TestDiffSyncer_Sync(t *testing.T) {
factory := spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceClient { factory := spacesyncproto.ClientFactoryFunc(func(cc drpc.Conn) spacesyncproto.DRPCSpaceClient {
return clientMock return clientMock
}) })
delState := mock_deletionstate.NewMockDeletionState(ctrl)
spaceId := "spaceId" spaceId := "spaceId"
aclRootId := "aclRootId" aclRootId := "aclRootId"
l := logger.NewNamed(spaceId) l := logger.NewNamed(spaceId)
diffSyncer := newDiffSyncer(spaceId, diffMock, connectorMock, cacheMock, stMock, factory, l) 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(). connectorMock.EXPECT().
GetResponsiblePeers(gomock.Any(), spaceId). GetResponsiblePeers(gomock.Any(), spaceId).
Return([]peer.Peer{mockPeer{}}, nil) Return([]peer.Peer{mockPeer{}}, nil)
diffMock.EXPECT(). diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(remotediff.NewRemoteDiff(spaceId, clientMock))). Diff(gomock.Any(), gomock.Eq(remotediff.NewRemoteDiff(spaceId, clientMock))).
Return([]string{"new"}, []string{"changed"}, nil, nil) Return([]string{"new"}, []string{"changed"}, nil, nil)
delState.EXPECT().FilterJoin(gomock.Any()).Return([]string{"new", "changed"})
for _, arg := range []string{"new", "changed"} { for _, arg := range []string{"new", "changed"} {
cacheMock.EXPECT(). cacheMock.EXPECT().
GetTree(gomock.Any(), spaceId, arg). GetTree(gomock.Any(), spaceId, arg).
@ -127,12 +138,37 @@ func TestDiffSyncer_Sync(t *testing.T) {
require.Error(t, diffSyncer.Sync(ctx)) 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) { t.Run("diff syncer sync space missing", func(t *testing.T) {
aclStorageMock := mock_aclstorage.NewMockListStorage(ctrl) aclStorageMock := mock_aclstorage.NewMockListStorage(ctrl)
settingsStorage := mock_treestorage.NewMockTreeStorage(ctrl)
settingsId := "settingsId"
aclRoot := &aclrecordproto.RawACLRecordWithId{ aclRoot := &aclrecordproto.RawACLRecordWithId{
Id: aclRootId, Id: aclRootId,
} }
settingsRoot := &treechangeproto.RawTreeChangeWithId{
Id: settingsId,
}
spaceHeader := &spacesyncproto.RawSpaceHeaderWithId{} spaceHeader := &spacesyncproto.RawSpaceHeaderWithId{}
spaceSettingsId := "spaceSettingsId"
connectorMock.EXPECT(). connectorMock.EXPECT().
GetResponsiblePeers(gomock.Any(), spaceId). GetResponsiblePeers(gomock.Any(), spaceId).
@ -140,17 +176,18 @@ func TestDiffSyncer_Sync(t *testing.T) {
diffMock.EXPECT(). diffMock.EXPECT().
Diff(gomock.Any(), gomock.Eq(remotediff.NewRemoteDiff(spaceId, clientMock))). Diff(gomock.Any(), gomock.Eq(remotediff.NewRemoteDiff(spaceId, clientMock))).
Return(nil, nil, nil, spacesyncproto.ErrSpaceMissing) Return(nil, nil, nil, spacesyncproto.ErrSpaceMissing)
stMock.EXPECT().
ACLStorage(). stMock.EXPECT().ACLStorage().Return(aclStorageMock, nil)
Return(aclStorageMock, nil) stMock.EXPECT().SpaceHeader().Return(spaceHeader, nil)
stMock.EXPECT(). stMock.EXPECT().SpaceSettingsId().Return(spaceSettingsId)
SpaceHeader(). stMock.EXPECT().TreeStorage(spaceSettingsId).Return(settingsStorage, nil)
Return(spaceHeader, nil)
settingsStorage.EXPECT().Root().Return(settingsRoot, nil)
aclStorageMock.EXPECT(). aclStorageMock.EXPECT().
Root(). Root().
Return(aclRoot, nil) Return(aclRoot, nil)
clientMock.EXPECT(). clientMock.EXPECT().
PushSpace(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRootId, spaceHeader)). PushSpace(gomock.Any(), newPushSpaceRequestMatcher(spaceId, aclRootId, settingsId, spaceHeader)).
Return(nil, nil) Return(nil, nil)
require.NoError(t, diffSyncer.Sync(ctx)) require.NoError(t, diffSyncer.Sync(ctx))

View File

@ -3,3 +3,9 @@ package diffservice
type HeadNotifiable interface { type HeadNotifiable interface {
UpdateHeads(id string, heads []string) 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. // 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 is a generated GoMock package.
package mock_diffservice package mock_diffservice
@ -8,6 +8,7 @@ import (
context "context" context "context"
reflect "reflect" reflect "reflect"
deletionstate "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsdocument/deletionstate"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
) )
@ -34,6 +35,30 @@ func (m *MockDiffSyncer) EXPECT() *MockDiffSyncerMockRecorder {
return m.recorder 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. // Sync mocks base method.
func (m *MockDiffSyncer) Sync(arg0 context.Context) error { func (m *MockDiffSyncer) Sync(arg0 context.Context) error {
m.ctrl.T.Helper() 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) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sync", reflect.TypeOf((*MockDiffSyncer)(nil).Sync), arg0)
} }
// MockPeriodicSync is a mock of PeriodicSync interface. // UpdateHeads mocks base method.
type MockPeriodicSync struct { func (m *MockDiffSyncer) UpdateHeads(arg0 string, arg1 []string) {
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.T.Helper()
m.ctrl.Call(m, "Close") m.ctrl.Call(m, "UpdateHeads", arg0, arg1)
} }
// Close indicates an expected call of Close. // UpdateHeads indicates an expected call of UpdateHeads.
func (mr *MockPeriodicSyncMockRecorder) Close() *gomock.Call { func (mr *MockDiffSyncerMockRecorder) UpdateHeads(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockPeriodicSync)(nil).Close)) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateHeads", reflect.TypeOf((*MockDiffSyncer)(nil).UpdateHeads), arg0, arg1)
}
// 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

@ -3,7 +3,9 @@ package commonspace
import ( import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" "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/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/cid"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/asymmetric/signingkey" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/asymmetric/signingkey"
"hash/fnv" "hash/fnv"
@ -11,6 +13,11 @@ import (
"time" "time"
) )
const (
SpaceSettingsChangeType = "reserved.spacesettings"
SpaceDerivationScheme = "derivation.standard"
)
func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload storage.SpaceStorageCreatePayload, err error) { func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload storage.SpaceStorageCreatePayload, err error) {
// unmarshalling signing and encryption keys // unmarshalling signing and encryption keys
identity, err := payload.SigningKey.GetPublic().Raw() identity, err := payload.SigningKey.GetPublic().Raw()
@ -23,8 +30,8 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload st
} }
// preparing header and space id // preparing header and space id
bytes := make([]byte, 32) spaceHeaderSeed := make([]byte, 32)
_, err = rand.Read(bytes) _, err = rand.Read(spaceHeaderSeed)
if err != nil { if err != nil {
return return
} }
@ -33,7 +40,7 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload st
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
SpaceType: payload.SpaceType, SpaceType: payload.SpaceType,
ReplicationKey: payload.ReplicationKey, ReplicationKey: payload.ReplicationKey,
Seed: bytes, Seed: spaceHeaderSeed,
} }
marshalled, err := header.Marshal() marshalled, err := header.Marshal()
if err != nil { if err != nil {
@ -68,12 +75,11 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload st
} }
// preparing acl // preparing acl
aclRoot := &aclrecordproto2.ACLRoot{ aclRoot := &aclrecordproto.ACLRoot{
Identity: identity, Identity: identity,
EncryptionKey: encPubKey, EncryptionKey: encPubKey,
SpaceId: spaceId, SpaceId: spaceId,
EncryptedReadKey: encReadKey, EncryptedReadKey: encReadKey,
DerivationScheme: "",
CurrentReadKeyHash: readKeyHash, CurrentReadKeyHash: readKeyHash,
Timestamp: time.Now().UnixNano(), Timestamp: time.Now().UnixNano(),
} }
@ -82,10 +88,31 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload st
return 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 // creating storage
storagePayload = storage.SpaceStorageCreatePayload{ storagePayload = storage.SpaceStorageCreatePayload{
RecWithId: rawWithId, AclWithId: rawWithId,
SpaceHeaderWithId: rawHeaderWithId, SpaceHeaderWithId: rawHeaderWithId,
SpaceSettingsWithId: settingsRoot,
} }
return return
} }
@ -144,7 +171,7 @@ func storagePayloadForSpaceDerive(payload SpaceDerivePayload) (storagePayload st
} }
// deriving and encrypting read key // deriving and encrypting read key
readKey, err := aclrecordproto2.ACLReadKeyDerive(signPrivKey, encPrivKey) readKey, err := aclrecordproto.ACLReadKeyDerive(signPrivKey, encPrivKey)
if err != nil { if err != nil {
return return
} }
@ -160,29 +187,41 @@ func storagePayloadForSpaceDerive(payload SpaceDerivePayload) (storagePayload st
} }
// preparing acl // preparing acl
aclRoot := &aclrecordproto2.ACLRoot{ aclRoot := &aclrecordproto.ACLRoot{
Identity: identity, Identity: identity,
EncryptionKey: encPubKey, EncryptionKey: encPubKey,
SpaceId: spaceId, SpaceId: spaceId,
EncryptedReadKey: encReadKey, EncryptedReadKey: encReadKey,
DerivationScheme: "", DerivationScheme: SpaceDerivationScheme,
CurrentReadKeyHash: readKeyHash, CurrentReadKeyHash: readKeyHash,
Timestamp: time.Now().UnixNano(),
} }
rawWithId, err := marshalACLRoot(aclRoot, payload.SigningKey) rawWithId, err := marshalACLRoot(aclRoot, payload.SigningKey)
if err != nil { if err != nil {
return 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 // creating storage
storagePayload = storage.SpaceStorageCreatePayload{ storagePayload = storage.SpaceStorageCreatePayload{
RecWithId: rawWithId, AclWithId: rawWithId,
SpaceHeaderWithId: rawHeaderWithId, SpaceHeaderWithId: rawHeaderWithId,
SpaceSettingsWithId: settingsRoot,
} }
return 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() marshalledRoot, err := aclRoot.Marshal()
if err != nil { if err != nil {
return return
@ -191,7 +230,7 @@ func marshalACLRoot(aclRoot *aclrecordproto2.ACLRoot, key signingkey.PrivKey) (r
if err != nil { if err != nil {
return return
} }
raw := &aclrecordproto2.RawACLRecord{ raw := &aclrecordproto.RawACLRecord{
Payload: marshalledRoot, Payload: marshalledRoot,
Signature: signature, Signature: signature,
} }
@ -203,7 +242,7 @@ func marshalACLRoot(aclRoot *aclrecordproto2.ACLRoot, key signingkey.PrivKey) (r
if err != nil { if err != nil {
return return
} }
rawWithId = &aclrecordproto2.RawACLRecordWithId{ rawWithId = &aclrecordproto.RawACLRecordWithId{
Payload: marshalledRaw, Payload: marshalledRaw,
Id: aclHeadId, 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/net/pool"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/nodeconf" "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/aclrecordproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
) )
const CName = "common.commonspace" const CName = "common.commonspace"
@ -25,11 +26,14 @@ func New() Service {
return &service{} return &service{}
} }
type ctxKey int
const AddSpaceCtxKey ctxKey = 0
type Service interface { type Service interface {
DeriveSpace(ctx context.Context, payload SpaceDerivePayload) (string, error) DeriveSpace(ctx context.Context, payload SpaceDerivePayload) (string, error)
CreateSpace(ctx context.Context, payload SpaceCreatePayload) (string, error) CreateSpace(ctx context.Context, payload SpaceCreatePayload) (string, error)
NewSpace(ctx context.Context, id string) (sp Space, err error) NewSpace(ctx context.Context, id string) (sp Space, err error)
AddSpace(ctx context.Context, spaceDescription SpaceDescription) (err error)
app.Component app.Component
} }
@ -82,49 +86,25 @@ func (s *service) DeriveSpace(ctx context.Context, payload SpaceDerivePayload) (
return store.Id(), nil 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) { func (s *service) NewSpace(ctx context.Context, id string) (Space, error) {
st, err := s.storageProvider.SpaceStorage(id) st, err := s.storageProvider.SpaceStorage(id)
if err != nil { if err != nil {
if err != spacesyncproto.ErrSpaceMissing { if err != storage.ErrSpaceStorageMissing {
return nil, err 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) st, err = s.getSpaceStorageFromRemote(ctx, id)
if err != nil { if err != nil {
err = storage.ErrSpaceStorageMissing
return nil, err return nil, err
} }
} }
}
lastConfiguration := s.configurationService.GetLast() lastConfiguration := s.configurationService.GetLast()
confConnector := nodeconf.NewConfConnector(lastConfiguration, s.pool) confConnector := nodeconf.NewConfConnector(lastConfiguration, s.pool)
@ -142,10 +122,33 @@ func (s *service) NewSpace(ctx context.Context, id string) (Space, error) {
return sp, nil 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) { func (s *service) getSpaceStorageFromRemote(ctx context.Context, id string) (st storage.SpaceStorage, err error) {
var p peer.Peer var p peer.Peer
lastConfiguration := s.configurationService.GetLast() 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) { if lastConfiguration.IsResponsible(id) {
err = spacesyncproto.ErrSpaceMissing err = spacesyncproto.ErrSpaceMissing
return return
@ -161,12 +164,17 @@ func (s *service) getSpaceStorageFromRemote(ctx context.Context, id string) (st
if err != nil { if err != nil {
return return
} }
st, err = s.storageProvider.CreateSpaceStorage(storage.SpaceStorageCreatePayload{ st, err = s.storageProvider.CreateSpaceStorage(storage.SpaceStorageCreatePayload{
RecWithId: &aclrecordproto.RawACLRecordWithId{ AclWithId: &aclrecordproto.RawACLRecordWithId{
Payload: res.AclPayload, Payload: res.Payload.AclPayload,
Id: res.AclPayloadId, Id: res.Payload.AclPayloadId,
}, },
SpaceHeaderWithId: res.SpaceHeader, SpaceSettingsWithId: &treechangeproto.RawTreeChangeWithId{
RawChange: res.Payload.SpaceSettingsPayload,
Id: res.Payload.SpaceSettingsPayloadId,
},
SpaceHeaderWithId: res.Payload.SpaceHeader,
}) })
return 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" "fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/account" "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/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/spacesyncproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncacl" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncacl"
@ -40,7 +42,10 @@ type SpaceCreatePayload struct {
ReplicationKey uint64 ReplicationKey uint64
} }
const SpaceTypeDerived = "derived.space" const (
SpaceTypeDerived = "derived.space"
SettingsSyncPeriodSeconds = 10
)
type SpaceDerivePayload struct { type SpaceDerivePayload struct {
SigningKey signingkey.PrivKey SigningKey signingkey.PrivKey
@ -51,6 +56,8 @@ type SpaceDescription struct {
SpaceHeader *spacesyncproto.RawSpaceHeaderWithId SpaceHeader *spacesyncproto.RawSpaceHeaderWithId
AclId string AclId string
AclPayload []byte AclPayload []byte
SpaceSettingsId string
SpaceSettingsPayload []byte
} }
func NewSpaceId(id string, repKey uint64) string { func NewSpaceId(id string, repKey uint64) string {
@ -62,13 +69,14 @@ type Space interface {
Init(ctx context.Context) error Init(ctx context.Context) error
StoredIds() []string StoredIds() []string
Description() SpaceDescription Description() (SpaceDescription, error)
SpaceSyncRpc() RpcHandler SpaceSyncRpc() RpcHandler
DeriveTree(ctx context.Context, payload tree.ObjectTreeCreatePayload, listener updatelistener.UpdateListener) (tree.ObjectTree, error) 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) 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) BuildTree(ctx context.Context, id string, listener updatelistener.UpdateListener) (tree.ObjectTree, error)
DeleteTree(ctx context.Context, id string) (err error)
Close() error Close() error
} }
@ -87,6 +95,7 @@ type space struct {
account account.Service account account.Service
aclList *syncacl.SyncACL aclList *syncacl.SyncACL
configuration nodeconf.Configuration configuration nodeconf.Configuration
settingsDocument settingsdocument.SettingsDocument
isClosed atomic.Bool isClosed atomic.Bool
} }
@ -99,16 +108,30 @@ func (s *space) Id() string {
return s.id return s.id
} }
func (s *space) Description() SpaceDescription { func (s *space) Description() (desc SpaceDescription, err error) {
root := s.aclList.Root() root := s.aclList.Root()
return SpaceDescription{ 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, SpaceHeader: s.header,
AclId: root.Id, AclId: root.Id,
AclPayload: root.Payload, AclPayload: root.Payload,
SpaceSettingsId: settingsRoot.Id,
SpaceSettingsPayload: settingsRoot.RawChange,
} }
return
} }
func (s *space) Init(ctx context.Context) (err error) { func (s *space) Init(ctx context.Context) (err error) {
s.storage = newCommonStorage(s.storage)
header, err := s.storage.SpaceHeader() header, err := s.storage.SpaceHeader()
if err != nil { if err != nil {
return return
@ -128,9 +151,32 @@ func (s *space) Init(ctx context.Context) (err error) {
return return
} }
s.aclList = syncacl.NewSyncACL(aclList, s.syncService.StreamPool()) 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.syncService.Init(objectGetter)
s.diffService.Init(initialIds) s.diffService.Init(initialIds, deletionState)
err = s.settingsDocument.Init(ctx)
if err != nil {
return
}
return nil return nil
} }
@ -163,7 +209,7 @@ func (s *space) DeriveTree(ctx context.Context, payload tree.ObjectTreeCreatePay
HeadNotifiable: s.diffService, HeadNotifiable: s.diffService,
Listener: listener, Listener: listener,
AclList: s.aclList, AclList: s.aclList,
CreateStorage: s.storage.CreateTreeStorage, SpaceStorage: s.storage,
} }
return synctree.DeriveSyncTree(ctx, deps) return synctree.DeriveSyncTree(ctx, deps)
} }
@ -181,7 +227,7 @@ func (s *space) CreateTree(ctx context.Context, payload tree.ObjectTreeCreatePay
HeadNotifiable: s.diffService, HeadNotifiable: s.diffService,
Listener: listener, Listener: listener,
AclList: s.aclList, AclList: s.aclList,
CreateStorage: s.storage.CreateTreeStorage, SpaceStorage: s.storage,
} }
return synctree.CreateSyncTree(ctx, deps) 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) 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 { func (s *space) Close() error {
log.With(zap.String("id", s.id)).Debug("space is closing") log.With(zap.String("id", s.id)).Debug("space is closing")
defer func() { defer func() {
@ -216,6 +266,9 @@ func (s *space) Close() error {
if err := s.syncService.Close(); err != nil { if err := s.syncService.Close(); err != nil {
mError.Add(err) mError.Add(err)
} }
if err := s.settingsDocument.Close(); err != nil {
mError.Add(err)
}
if err := s.aclList.Close(); err != nil { if err := s.aclList.Close(); err != nil {
mError.Add(err) 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 // PushSpaceRequest is a request to add space on a node containing only one acl record
message PushSpaceRequest { message PushSpaceRequest {
RawSpaceHeaderWithId spaceHeader = 1; SpacePayload payload = 1;
bytes aclPayload = 2;
string aclPayloadId = 3;
} }
// PushSpaceResponse is an empty response // PushSpaceResponse is an empty response
@ -79,9 +77,15 @@ message PullSpaceRequest {
// PullSpaceResponse is a response with header and acl root // PullSpaceResponse is a response with header and acl root
message PullSpaceResponse { message PullSpaceResponse {
SpacePayload payload = 1;
}
message SpacePayload {
RawSpaceHeaderWithId spaceHeader = 1; RawSpaceHeaderWithId spaceHeader = 1;
bytes aclPayload = 2; bytes aclPayload = 2;
string aclPayloadId = 3; string aclPayloadId = 3;
bytes spaceSettingsPayload = 4;
string spaceSettingsPayloadId = 5;
} }
// SpaceHeader is a header for a space // SpaceHeader is a header for a space
@ -102,3 +106,23 @@ message RawSpaceHeaderWithId {
bytes rawHeader = 1; bytes rawHeader = 1;
string id = 2; 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)) 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. // SpaceHeader mocks base method.
func (m *MockSpaceStorage) SpaceHeader() (*spacesyncproto.RawSpaceHeaderWithId, error) { func (m *MockSpaceStorage) SpaceHeader() (*spacesyncproto.RawSpaceHeaderWithId, error) {
m.ctrl.T.Helper() 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)) 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. // StoredIds mocks base method.
func (m *MockSpaceStorage) StoredIds() ([]string, error) { func (m *MockSpaceStorage) StoredIds() ([]string, error) {
m.ctrl.T.Helper() 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)) 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. // TreeStorage mocks base method.
func (m *MockSpaceStorage) TreeStorage(arg0 string) (storage0.TreeStorage, error) { func (m *MockSpaceStorage) TreeStorage(arg0 string) (storage0.TreeStorage, error) {
m.ctrl.T.Helper() 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/commonspace/spacesyncproto"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto" "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/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
) )
const CName = "commonspace.storage" const CName = "commonspace.storage"
var ErrSpaceStorageExists = errors.New("space storage exists") var (
var ErrSpaceStorageMissing = errors.New("space storage missing") 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 { type SpaceStorage interface {
storage.Provider storage.Provider
Id() string Id() string
SetTreeDeletedStatus(id, state string) error
TreeDeletedStatus(id string) (string, error)
SpaceSettingsId() string
ACLStorage() (storage.ListStorage, error) ACLStorage() (storage.ListStorage, error)
SpaceHeader() (*spacesyncproto.RawSpaceHeaderWithId, error) SpaceHeader() (*spacesyncproto.RawSpaceHeaderWithId, error)
StoredIds() ([]string, error) StoredIds() ([]string, error)
@ -24,8 +37,9 @@ type SpaceStorage interface {
} }
type SpaceStorageCreatePayload struct { type SpaceStorageCreatePayload struct {
RecWithId *aclrecordproto.RawACLRecordWithId AclWithId *aclrecordproto.RawACLRecordWithId
SpaceHeaderWithId *spacesyncproto.RawSpaceHeaderWithId SpaceHeaderWithId *spacesyncproto.RawSpaceHeaderWithId
SpaceSettingsWithId *treechangeproto.RawTreeChangeWithId
} }
type SpaceStorageProvider interface { type SpaceStorageProvider interface {
@ -33,3 +47,8 @@ type SpaceStorageProvider interface {
SpaceStorage(id string) (SpaceStorage, error) SpaceStorage(id string) (SpaceStorage, error)
CreateSpaceStorage(payload SpaceStorageCreatePayload) (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. // 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 is a generated GoMock package.
package mock_synctree package mock_synctree
import ( import (
context "context"
reflect "reflect" 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" tree "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/tree"
treechangeproto "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto" treechangeproto "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
@ -134,3 +137,311 @@ func (mr *MockSyncClientMockRecorder) SendAsync(arg0, arg1, arg2 interface{}) *g
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAsync", reflect.TypeOf((*MockSyncClient)(nil).SendAsync), arg0, arg1, arg2) 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 package synctree
import ( import (

View File

@ -20,15 +20,24 @@ import (
"go.uber.org/zap" "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 // SyncTree sends head updates to sync service and also sends new changes to update listener
type SyncTree struct { type syncTree struct {
tree.ObjectTree tree.ObjectTree
synchandler.SyncHandler synchandler.SyncHandler
syncClient SyncClient syncClient SyncClient
listener updatelistener.UpdateListener listener updatelistener.UpdateListener
isClosed bool isClosed bool
isDeleted bool
} }
var log = logger.NewNamed("commonspace.synctree").Sugar() var log = logger.NewNamed("commonspace.synctree").Sugar()
@ -46,7 +55,7 @@ type CreateDeps struct {
StreamPool syncservice.StreamPool StreamPool syncservice.StreamPool
Listener updatelistener.UpdateListener Listener updatelistener.UpdateListener
AclList list.ACLList AclList list.ACLList
CreateStorage storage.TreeStorageCreatorFunc SpaceStorage spacestorage.SpaceStorage
} }
type BuildDeps struct { type BuildDeps struct {
@ -60,8 +69,8 @@ type BuildDeps struct {
TreeStorage storage.TreeStorage TreeStorage storage.TreeStorage
} }
func DeriveSyncTree(ctx context.Context, deps CreateDeps) (t tree.ObjectTree, err error) { func DeriveSyncTree(ctx context.Context, deps CreateDeps) (t SyncTree, err error) {
t, err = createDerivedObjectTree(deps.Payload, deps.AclList, deps.CreateStorage) objTree, err := createDerivedObjectTree(deps.Payload, deps.AclList, deps.SpaceStorage.CreateTreeStorage)
if err != nil { if err != nil {
return return
} }
@ -71,22 +80,27 @@ func DeriveSyncTree(ctx context.Context, deps CreateDeps) (t tree.ObjectTree, er
deps.HeadNotifiable, deps.HeadNotifiable,
sharedFactory, sharedFactory,
deps.Configuration) deps.Configuration)
syncTree := &SyncTree{ syncTree := &syncTree{
ObjectTree: t, ObjectTree: objTree,
syncClient: syncClient, syncClient: syncClient,
listener: deps.Listener, listener: deps.Listener,
} }
syncHandler := newSyncTreeHandler(syncTree, syncClient) syncHandler := newSyncTreeHandler(syncTree, syncClient)
syncTree.SyncHandler = syncHandler syncTree.SyncHandler = syncHandler
t = syncTree t = syncTree
syncTree.Lock()
defer syncTree.Unlock()
if syncTree.listener != nil {
syncTree.listener.Rebuild(syncTree)
}
headUpdate := syncClient.CreateHeadUpdate(t, nil) headUpdate := syncClient.CreateHeadUpdate(t, nil)
err = syncClient.BroadcastAsync(headUpdate) err = syncClient.BroadcastAsync(headUpdate)
return return
} }
func CreateSyncTree(ctx context.Context, deps CreateDeps) (t tree.ObjectTree, err error) { func CreateSyncTree(ctx context.Context, deps CreateDeps) (t SyncTree, err error) {
t, err = createObjectTree(deps.Payload, deps.AclList, deps.CreateStorage) objTree, err := createObjectTree(deps.Payload, deps.AclList, deps.SpaceStorage.CreateTreeStorage)
if err != nil { if err != nil {
return return
} }
@ -96,21 +110,27 @@ func CreateSyncTree(ctx context.Context, deps CreateDeps) (t tree.ObjectTree, er
deps.HeadNotifiable, deps.HeadNotifiable,
GetRequestFactory(), GetRequestFactory(),
deps.Configuration) deps.Configuration)
syncTree := &SyncTree{ syncTree := &syncTree{
ObjectTree: t, ObjectTree: objTree,
syncClient: syncClient, syncClient: syncClient,
listener: deps.Listener, listener: deps.Listener,
} }
syncHandler := newSyncTreeHandler(syncTree, syncClient) syncHandler := newSyncTreeHandler(syncTree, syncClient)
syncTree.SyncHandler = syncHandler syncTree.SyncHandler = syncHandler
t = syncTree 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) headUpdate := syncClient.CreateHeadUpdate(t, nil)
err = syncClient.BroadcastAsync(headUpdate) err = syncClient.BroadcastAsync(headUpdate)
return 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) { getTreeRemote := func() (msg *treechangeproto.TreeSyncMessage, err error) {
peerId, err := peer.CtxPeerId(ctx) peerId, err := peer.CtxPeerId(ctx)
if err != nil { if err != nil {
@ -140,6 +160,15 @@ func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t
return return
} }
status, err := deps.SpaceStorage.TreeDeletedStatus(id)
if err != nil {
return
}
if status != "" {
err = spacestorage.ErrTreeStorageAlreadyDeleted
return
}
resp, err := getTreeRemote() resp, err := getTreeRemote()
if err != nil { if err != nil {
return return
@ -170,9 +199,8 @@ func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t
return buildSyncTree(ctx, true, deps) return buildSyncTree(ctx, true, deps)
} }
func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t tree.ObjectTree, err error) { func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t SyncTree, err error) {
objTree, err := buildObjectTree(deps.TreeStorage, deps.AclList)
t, err = buildObjectTree(deps.TreeStorage, deps.AclList)
if err != nil { if err != nil {
return return
} }
@ -182,14 +210,19 @@ func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t tr
deps.HeadNotifiable, deps.HeadNotifiable,
GetRequestFactory(), GetRequestFactory(),
deps.Configuration) deps.Configuration)
syncTree := &SyncTree{ syncTree := &syncTree{
ObjectTree: t, ObjectTree: objTree,
syncClient: syncClient, syncClient: syncClient,
listener: deps.Listener, listener: deps.Listener,
} }
syncHandler := newSyncTreeHandler(syncTree, syncClient) syncHandler := newSyncTreeHandler(syncTree, syncClient)
syncTree.SyncHandler = syncHandler syncTree.SyncHandler = syncHandler
t = syncTree t = syncTree
syncTree.Lock()
defer syncTree.Unlock()
if syncTree.listener != nil {
syncTree.listener.Rebuild(syncTree)
}
headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil) headUpdate := syncTree.syncClient.CreateHeadUpdate(t, nil)
// here we will have different behaviour based on who is sending this update // 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 return
} }
func (s *SyncTree) AddContent(ctx context.Context, content tree.SignableChangeContent) (res tree.AddResult, err error) { func (s *syncTree) IterateFrom(id string, convert tree.ChangeConvertFunc, iterate tree.ChangeIterateFunc) (err error) {
if s.isClosed { if err = s.checkAlive(); err != nil {
err = ErrSyncTreeClosed 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 return
} }
res, err = s.ObjectTree.AddContent(ctx, content) res, err = s.ObjectTree.AddContent(ctx, content)
@ -217,9 +263,8 @@ func (s *SyncTree) AddContent(ctx context.Context, content tree.SignableChangeCo
return return
} }
func (s *SyncTree) AddRawChanges(ctx context.Context, changes ...*treechangeproto.RawTreeChangeWithId) (res tree.AddResult, err error) { func (s *syncTree) AddRawChanges(ctx context.Context, changes ...*treechangeproto.RawTreeChangeWithId) (res tree.AddResult, err error) {
if s.isClosed { if err = s.checkAlive(); err != nil {
err = ErrSyncTreeClosed
return return
} }
res, err = s.ObjectTree.AddRawChanges(ctx, changes...) res, err = s.ObjectTree.AddRawChanges(ctx, changes...)
@ -243,15 +288,38 @@ func (s *SyncTree) AddRawChanges(ctx context.Context, changes ...*treechangeprot
return 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") log.With("id", s.ID()).Debug("closing sync tree")
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
log.With("id", s.ID()).Debug("taken lock on sync tree")
if s.isClosed { if s.isClosed {
err = ErrSyncTreeClosed return ErrSyncTreeClosed
return
} }
s.isClosed = true s.isClosed = true
return 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 ( import (
"context" "context"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice" "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/syncservice"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/synctree/mock_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/synctree/updatelistener"
@ -26,7 +27,7 @@ type syncTreeMatcher struct {
} }
func (s syncTreeMatcher) Matches(x interface{}) bool { func (s syncTreeMatcher) Matches(x interface{}) bool {
t, ok := x.(*SyncTree) t, ok := x.(*syncTree)
if !ok { if !ok {
return false return false
} }
@ -45,7 +46,8 @@ func Test_DeriveSyncTree(t *testing.T) {
updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl) updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl)
syncClientMock := mock_synctree.NewMockSyncClient(ctrl) syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
aclListMock := mock_list.NewMockACLList(ctrl) aclListMock := mock_list.NewMockACLList(ctrl)
objTreeMock := mock_tree.NewMockObjectTree(ctrl) objTreeMock := newTestObjMock(mock_tree.NewMockObjectTree(ctrl))
spaceStorageMock := mock_storage.NewMockSpaceStorage(ctrl)
spaceId := "spaceId" spaceId := "spaceId"
expectedPayload := tree.ObjectTreeCreatePayload{SpaceId: spaceId} expectedPayload := tree.ObjectTreeCreatePayload{SpaceId: spaceId}
createDerivedObjectTree = func(payload tree.ObjectTreeCreatePayload, l list.ACLList, create storage2.TreeStorageCreatorFunc) (objTree tree.ObjectTree, err error) { 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{} headUpdate := &treechangeproto.TreeSyncMessage{}
syncClientMock.EXPECT().CreateHeadUpdate(syncTreeMatcher{objTreeMock, syncClientMock, updateListenerMock}, gomock.Nil()).Return(headUpdate) syncClientMock.EXPECT().CreateHeadUpdate(syncTreeMatcher{objTreeMock, syncClientMock, updateListenerMock}, gomock.Nil()).Return(headUpdate)
syncClientMock.EXPECT().BroadcastAsync(gomock.Eq(headUpdate)).Return(nil) 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) _, err := DeriveSyncTree(ctx, deps)
require.NoError(t, err) require.NoError(t, err)
@ -73,7 +82,8 @@ func Test_CreateSyncTree(t *testing.T) {
updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl) updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl)
syncClientMock := mock_synctree.NewMockSyncClient(ctrl) syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
aclListMock := mock_list.NewMockACLList(ctrl) aclListMock := mock_list.NewMockACLList(ctrl)
objTreeMock := mock_tree.NewMockObjectTree(ctrl) objTreeMock := newTestObjMock(mock_tree.NewMockObjectTree(ctrl))
spaceStorageMock := mock_storage.NewMockSpaceStorage(ctrl)
spaceId := "spaceId" spaceId := "spaceId"
expectedPayload := tree.ObjectTreeCreatePayload{SpaceId: spaceId} expectedPayload := tree.ObjectTreeCreatePayload{SpaceId: spaceId}
createObjectTree = func(payload tree.ObjectTreeCreatePayload, l list.ACLList, create storage2.TreeStorageCreatorFunc) (objTree tree.ObjectTree, err error) { 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{} headUpdate := &treechangeproto.TreeSyncMessage{}
syncClientMock.EXPECT().CreateHeadUpdate(syncTreeMatcher{objTreeMock, syncClientMock, updateListenerMock}, gomock.Nil()).Return(headUpdate) syncClientMock.EXPECT().CreateHeadUpdate(syncTreeMatcher{objTreeMock, syncClientMock, updateListenerMock}, gomock.Nil()).Return(headUpdate)
syncClientMock.EXPECT().BroadcastAsync(gomock.Eq(headUpdate)).Return(nil) 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) _, err := CreateSyncTree(ctx, deps)
require.NoError(t, err) require.NoError(t, err)
@ -100,8 +117,8 @@ func Test_BuildSyncTree(t *testing.T) {
updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl) updateListenerMock := mock_updatelistener.NewMockUpdateListener(ctrl)
syncClientMock := mock_synctree.NewMockSyncClient(ctrl) syncClientMock := mock_synctree.NewMockSyncClient(ctrl)
objTreeMock := mock_tree.NewMockObjectTree(ctrl) objTreeMock := newTestObjMock(mock_tree.NewMockObjectTree(ctrl))
tr := &SyncTree{ tr := &syncTree{
ObjectTree: objTreeMock, ObjectTree: objTreeMock,
SyncHandler: nil, SyncHandler: nil,
syncClient: syncClientMock, 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) 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. // GetTree mocks base method.
func (m *MockTreeGetter) GetTree(arg0 context.Context, arg1, arg2 string) (tree.ObjectTree, error) { func (m *MockTreeGetter) GetTree(arg0 context.Context, arg1, arg2 string) (tree.ObjectTree, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@ -15,4 +15,5 @@ var ErrSpaceNotFound = errors.New("space not found")
type TreeGetter interface { type TreeGetter interface {
app.ComponentRunnable app.ComponentRunnable
GetTree(ctx context.Context, spaceId, treeId string) (tree.ObjectTree, error) 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) return nil, fmt.Errorf("could not get change with id: %s", changeId)
} }
func (t *inMemoryTreeStorage) Delete() error {
return nil
}
type inMemoryStorageProvider struct { type inMemoryStorageProvider struct {
objects map[string]TreeStorage objects map[string]TreeStorage
sync.RWMutex 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) 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. // GetRawChange mocks base method.
func (m *MockTreeStorage) GetRawChange(arg0 context.Context, arg1 string) (*treechangeproto.RawTreeChangeWithId, error) { func (m *MockTreeStorage) GetRawChange(arg0 context.Context, arg1 string) (*treechangeproto.RawTreeChangeWithId, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

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

View File

@ -14,6 +14,7 @@ type TreeStorage interface {
AddRawChange(change *treechangeproto.RawTreeChangeWithId) error AddRawChange(change *treechangeproto.RawTreeChangeWithId) error
GetRawChange(ctx context.Context, id string) (*treechangeproto.RawTreeChangeWithId, error) GetRawChange(ctx context.Context, id string) (*treechangeproto.RawTreeChangeWithId, error)
HasChange(ctx context.Context, id string) (bool, error) HasChange(ctx context.Context, id string) (bool, error)
Delete() error
} }
type TreeStorageCreatorFunc = func(payload TreeStorageCreatePayload) (TreeStorage, error) type TreeStorageCreatorFunc = func(payload TreeStorageCreatePayload) (TreeStorage, error)

View File

@ -48,7 +48,7 @@ type changeBuilder struct {
keys *common.Keychain 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} return &changeBuilder{keys: keys, rootChange: rootChange}
} }
@ -155,12 +155,16 @@ func (c *changeBuilder) BuildContent(payload BuilderContent) (ch *Change, rawIdC
Identity: payload.Identity, Identity: payload.Identity,
IsSnapshot: payload.IsSnapshot, IsSnapshot: payload.IsSnapshot,
} }
if payload.ReadKey != nil {
encrypted, err := payload.ReadKey.Encrypt(payload.Content) var encrypted []byte
encrypted, err = payload.ReadKey.Encrypt(payload.Content)
if err != nil { if err != nil {
return return
} }
change.ChangesData = encrypted change.ChangesData = encrypted
} else {
change.ChangesData = payload.Content
}
marshalledChange, err := proto.Marshal(change) marshalledChange, err := proto.Marshal(change)
if err != nil { if err != nil {
@ -188,7 +192,6 @@ func (c *changeBuilder) BuildContent(payload BuilderContent) (ch *Change, rawIdC
} }
ch = NewChange(id, change, signature) ch = NewChange(id, change, signature)
ch.Model = payload.Content
rawIdChange = &treechangeproto.RawTreeChangeWithId{ rawIdChange = &treechangeproto.RawTreeChangeWithId{
RawChange: marshalledRawChange, 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)) 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. // HasChanges mocks base method.
func (m *MockObjectTree) HasChanges(arg0 ...string) bool { func (m *MockObjectTree) HasChanges(arg0 ...string) bool {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

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

View File

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

View File

@ -9,4 +9,5 @@ type SignableChangeContent struct {
Key signingkey.PrivKey Key signingkey.PrivKey
Identity []byte Identity []byte
IsSnapshot bool 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 ( import (
"context" "context"
@ -11,7 +12,9 @@ type PeriodicSync interface {
Close() 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()) ctx, cancel := context.WithCancel(context.Background())
return &periodicSync{ return &periodicSync{
syncer: syncer, syncer: syncer,
@ -25,7 +28,7 @@ func newPeriodicSync(periodSeconds int, syncer DiffSyncer, l *zap.Logger) *perio
type periodicSync struct { type periodicSync struct {
log *zap.Logger log *zap.Logger
syncer DiffSyncer syncer SyncerFunc
syncCtx context.Context syncCtx context.Context
syncCancel context.CancelFunc syncCancel context.CancelFunc
syncLoopDone chan struct{} syncLoopDone chan struct{}
@ -42,7 +45,7 @@ func (p *periodicSync) syncLoop(periodSeconds int) {
doSync := func() { doSync := func() {
ctx, cancel := context.WithTimeout(p.syncCtx, time.Minute) ctx, cancel := context.WithTimeout(p.syncCtx, time.Minute)
defer cancel() 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)) p.log.Warn("periodic sync error", zap.Error(err))
} }
} }

View File

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

View File

@ -10,6 +10,9 @@ require (
github.com/akrylysov/pogreb v0.10.1 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/common v0.0.0-00010101000000-000000000000
github.com/anytypeio/go-anytype-infrastructure-experiments/consensus 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 go.uber.org/zap v1.23.0
) )
@ -19,6 +22,7 @@ require (
github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cheggaaa/mb/v2 v2.0.1 // indirect github.com/cheggaaa/mb/v2 v2.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/fogleman/gg v1.3.0 // indirect github.com/fogleman/gg v1.3.0 // indirect
github.com/goccy/go-graphviz v0.0.9 // 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/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/huandu/skiplist v1.2.0 // 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/ipfs/go-log/v2 v2.5.1 // indirect
github.com/klauspost/cpuid/v2 v2.1.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-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-libp2p v0.23.2 // 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/libp2p/go-openssl v0.1.0 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-pointer v0.0.1 // 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-multihash v0.2.1 // indirect
github.com/multiformats/go-varint v0.0.6 // indirect github.com/multiformats/go-varint v0.0.6 // indirect
github.com/pkg/errors v0.9.1 // 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_golang v1.13.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.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.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.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 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.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.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/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-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 h1:yqyTeKQJyofWXxEv/eEVUvOrGdt/9x+0PIQ4N1kaxmE=
github.com/libp2p/go-libp2p v0.23.2/go.mod h1:s9DEa5NLR4g+LZS+md5uGU4emjMWFiqkZr6hBTY8UxI= 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 h1:LBkKEcUv6vtZIQLVTegAil8jbNpJErQ9AnT+bWV+Ooo=
github.com/libp2p/go-openssl v0.1.0/go.mod h1:OiOxwPpL3n4xlenjx2h7AwSGaFSC/KZvf6gNdOBQMtc= 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= 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/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.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.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.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.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 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.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/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= 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-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-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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/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-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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) tr = value.(tree.ObjectTree)
return 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 return
} }
description := sp.Description() spaceDesc, err := sp.Description()
if err != nil {
err = spacesyncproto.ErrUnexpected
return
}
resp = &spacesyncproto.PullSpaceResponse{ resp = &spacesyncproto.PullSpaceResponse{
SpaceHeader: description.SpaceHeader, Payload: &spacesyncproto.SpacePayload{
AclPayload: description.AclPayload, SpaceHeader: spaceDesc.SpaceHeader,
AclPayloadId: description.AclId, AclPayloadId: spaceDesc.AclId,
AclPayload: spaceDesc.AclPayload,
SpaceSettingsPayload: spaceDesc.SpaceSettingsPayload,
SpaceSettingsPayloadId: spaceDesc.SpaceSettingsId,
},
} }
return return
} }
func (r *rpcHandler) PushSpace(ctx context.Context, req *spacesyncproto.PushSpaceRequest) (resp *spacesyncproto.PushSpaceResponse, err error) { func (r *rpcHandler) PushSpace(ctx context.Context, req *spacesyncproto.PushSpaceRequest) (resp *spacesyncproto.PushSpaceResponse, err error) {
description := commonspace.SpaceDescription{ description := commonspace.SpaceDescription{
SpaceHeader: req.SpaceHeader, SpaceHeader: req.Payload.SpaceHeader,
AclId: req.AclPayloadId, AclId: req.Payload.AclPayloadId,
AclPayload: req.AclPayload, 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 { if err != nil {
return return
} }

View File

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

View File

@ -1,6 +1,7 @@
package storage package storage
import ( import (
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage"
"strings" "strings"
) )
@ -25,6 +26,7 @@ func (a aclKeys) RawRecordKey(id string) []byte {
type treeKeys struct { type treeKeys struct {
id string id string
prefix string
headsKey []byte headsKey []byte
} }
@ -32,6 +34,7 @@ func newTreeKeys(id string) treeKeys {
return treeKeys{ return treeKeys{
id: id, id: id,
headsKey: storage.JoinStringsToBytes("t", id, "heads"), 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) return storage.JoinStringsToBytes("t", t.id, id)
} }
func (t treeKeys) isTreeRelatedKey(key string) bool {
return strings.HasPrefix(key, t.prefix)
}
type spaceKeys struct { type spaceKeys struct {
headerKey []byte headerKey []byte
} }
@ -51,7 +58,10 @@ func newSpaceKeys(spaceId string) spaceKeys {
return spaceKeys{headerKey: storage.JoinStringsToBytes("s", spaceId)} return spaceKeys{headerKey: storage.JoinStringsToBytes("s", spaceId)}
} }
var spaceIdKey = []byte("spaceId") var (
spaceIdKey = []byte("spaceId")
spaceSettingsIdKey = []byte("spaceSettingsId")
)
func (s spaceKeys) SpaceIdKey() []byte { func (s spaceKeys) SpaceIdKey() []byte {
return spaceIdKey return spaceIdKey
@ -61,7 +71,15 @@ func (s spaceKeys) HeaderKey() []byte {
return s.headerKey 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") 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" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto"
spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" 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/storage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/treechangeproto"
"go.uber.org/zap" "go.uber.org/zap"
"path" "path"
"sync"
"time" "time"
) )
var defPogrebOptions = &pogreb.Options{ var (
BackgroundCompactionInterval: time.Minute * 5, defPogrebOptions = &pogreb.Options{BackgroundCompactionInterval: time.Minute * 5}
} log = logger.NewNamed("storage.spacestorage")
spaceValidationFunc = spacestorage.ValidateSpaceStorageCreatePayload
var log = logger.NewNamed("storage.spacestorage") )
type spaceStorage struct { type spaceStorage struct {
spaceId string spaceId string
spaceSettingsId string
objDb *pogreb.DB objDb *pogreb.DB
keys spaceKeys keys spaceKeys
aclStorage storage.ListStorage aclStorage storage.ListStorage
header *spacesyncproto.RawSpaceHeaderWithId header *spacesyncproto.RawSpaceHeaderWithId
mx sync.Mutex
} }
func newSpaceStorage(rootPath string, spaceId string) (store spacestorage.SpaceStorage, err error) { func newSpaceStorage(rootPath string, spaceId string) (store spacestorage.SpaceStorage, err error) {
@ -61,6 +61,15 @@ func newSpaceStorage(rootPath string, spaceId string) (store spacestorage.SpaceS
return return
} }
spaceSettingsId, err := objDb.Get(keys.SpaceSettingsIdKey())
if err != nil {
return
}
if spaceSettingsId == nil {
err = spacestorage.ErrSpaceStorageMissing
return
}
aclStorage, err := newListStorage(objDb) aclStorage, err := newListStorage(objDb)
if err != nil { if err != nil {
return return
@ -68,6 +77,7 @@ func newSpaceStorage(rootPath string, spaceId string) (store spacestorage.SpaceS
store = &spaceStorage{ store = &spaceStorage{
spaceId: spaceId, spaceId: spaceId,
spaceSettingsId: string(spaceSettingsId),
objDb: objDb, objDb: objDb,
keys: keys, keys: keys,
header: &spacesyncproto.RawSpaceHeaderWithId{ header: &spacesyncproto.RawSpaceHeaderWithId{
@ -103,8 +113,35 @@ func createSpaceStorage(rootPath string, payload spacestorage.SpaceStorageCreate
err = spacesyncproto.ErrSpaceExists err = spacesyncproto.ErrSpaceExists
return 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 { if err != nil {
return return
} }
@ -119,13 +156,6 @@ func createSpaceStorage(rootPath string, payload spacestorage.SpaceStorageCreate
return return
} }
store = &spaceStorage{
spaceId: payload.SpaceHeaderWithId.Id,
objDb: db,
keys: keys,
aclStorage: aclStorage,
header: payload.SpaceHeaderWithId,
}
return return
} }
@ -133,15 +163,15 @@ func (s *spaceStorage) Id() string {
return s.spaceId return s.spaceId
} }
func (s *spaceStorage) SpaceSettingsId() string {
return s.spaceSettingsId
}
func (s *spaceStorage) TreeStorage(id string) (storage.TreeStorage, error) { func (s *spaceStorage) TreeStorage(id string) (storage.TreeStorage, error) {
return newTreeStorage(s.objDb, id) return newTreeStorage(s.objDb, id)
} }
func (s *spaceStorage) CreateTreeStorage(payload storage.TreeStorageCreatePayload) (ts storage.TreeStorage, err error) { 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) return createTreeStorage(s.objDb, payload)
} }
@ -153,13 +183,26 @@ func (s *spaceStorage) SpaceHeader() (header *spacesyncproto.RawSpaceHeaderWithI
return s.header, nil 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) { func (s *spaceStorage) StoredIds() (ids []string, err error) {
index := s.objDb.Items() index := s.objDb.Items()
key, _, err := index.Next() key, _, err := index.Next()
for err == nil { for err == nil {
strKey := string(key) strKey := string(key)
if isRootIdKey(strKey) { if isTreeHeadsKey(strKey) {
ids = append(ids, getRootId(strKey)) ids = append(ids, getRootId(strKey))
} }
key, _, err = index.Next() key, _, err = index.Next()

View File

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

View File

@ -122,7 +122,7 @@ func (t *treeStorage) GetRawChange(ctx context.Context, id string) (raw *treecha
return return
} }
if res == nil { if res == nil {
err = storage.ErrUnkownChange err = storage.ErrUnknownChange
} }
raw = &treechangeproto.RawTreeChangeWithId{ 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) { func (t *treeStorage) HasChange(ctx context.Context, id string) (bool, error) {
return t.db.Has(t.keys.RawChangeKey(id)) 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()) 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) { func testTreePayload(t *testing.T, store storage.TreeStorage, payload storage.TreeStorageCreatePayload) {
require.Equal(t, payload.RootRawChange.Id, store.Id()) require.Equal(t, payload.RootRawChange.Id, store.Id())
@ -119,3 +139,31 @@ func TestTreeStorage_Methods(t *testing.T) {
require.False(t, has) 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)
})
}