diff --git a/client/storage/keys.go b/client/storage/keys.go index ddf187ac..45eb8cb0 100644 --- a/client/storage/keys.go +++ b/client/storage/keys.go @@ -31,18 +31,20 @@ func (a aclKeys) RawRecordKey(id string) []byte { } type treeKeys struct { - id string - spaceId string - headsKey []byte - rootKey []byte + id string + spaceId string + headsKey []byte + rootKey []byte + rawChangePrefix []byte } func newTreeKeys(spaceId, id string) treeKeys { return treeKeys{ - id: id, - spaceId: spaceId, - headsKey: storage.JoinStringsToBytes("space", spaceId, "t", id, "heads"), - rootKey: storage.JoinStringsToBytes("space", spaceId, "t", "rootId", id), + id: id, + spaceId: spaceId, + headsKey: storage.JoinStringsToBytes("space", spaceId, "t", id, "heads"), + rootKey: storage.JoinStringsToBytes("space", spaceId, "t", "rootId", id), + rawChangePrefix: storage.JoinStringsToBytes("space", spaceId, "t", id), } } @@ -58,6 +60,10 @@ func (t treeKeys) RawChangeKey(id string) []byte { return storage.JoinStringsToBytes("space", t.spaceId, "t", t.id, id) } +func (t treeKeys) RawChangePrefix() []byte { + return t.rawChangePrefix +} + type spaceKeys struct { headerKey []byte treePrefixKey []byte diff --git a/client/storage/treestorage.go b/client/storage/treestorage.go index 8c11f003..04f7dba6 100644 --- a/client/storage/treestorage.go +++ b/client/storage/treestorage.go @@ -133,3 +133,45 @@ func (t *treeStorage) GetRawChange(ctx context.Context, id string) (raw *treecha func (t *treeStorage) HasChange(ctx context.Context, id string) (bool, error) { return hasDB(t.db, t.keys.RawChangeKey(id)), nil } + +func (t *treeStorage) Delete() (err error) { + storedKeys, err := t.storedKeys() + if err != nil { + return + } + err = t.db.Update(func(txn *badger.Txn) error { + for _, k := range storedKeys { + err = txn.Delete(k) + if err != nil { + return err + } + } + return nil + }) + return +} + +func (t *treeStorage) storedKeys() (keys [][]byte, err error) { + err = t.db.View(func(txn *badger.Txn) error { + opts := badger.DefaultIteratorOptions + opts.PrefetchValues = false + 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() + // if it is a heads key + if len(key) <= len(t.keys.HeadsKey()) { + continue + } + keyCopy := make([]byte, 0, len(key)) + keyCopy = item.KeyCopy(key) + keys = append(keys, keyCopy) + } + return nil + }) + return +} diff --git a/common/commonspace/deletionservice/service.go b/common/commonspace/deletionservice/service.go deleted file mode 100644 index f6f715a2..00000000 --- a/common/commonspace/deletionservice/service.go +++ /dev/null @@ -1,28 +0,0 @@ -package deletionservice - -import ( - "github.com/anytypeio/go-anytype-infrastructure-experiments/common/account" - "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/tree" -) - -type Service interface { -} - -const deletionChangeType = "space.deletionlist" - -type service struct { - account account.Service -} - -func New() Service { - return nil -} - -func deriveDeletionTreePayload(account account.Service, spaceId string) tree.ObjectTreeCreatePayload { - return tree.ObjectTreeCreatePayload{ - SignKey: account.Account().SignKey, - ChangeType: deletionChangeType, - SpaceId: spaceId, - Identity: account.Account().Identity, - } -} diff --git a/common/commonspace/payloads.go b/common/commonspace/payloads.go index 843d597c..cd6a4c1f 100644 --- a/common/commonspace/payloads.go +++ b/common/commonspace/payloads.go @@ -3,7 +3,9 @@ package commonspace import ( "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/spacesyncproto" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" - aclrecordproto2 "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto" + aclrecordproto "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/aclrecordproto" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/common" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/tree" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/cid" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/util/keys/asymmetric/signingkey" "hash/fnv" @@ -11,6 +13,11 @@ import ( "time" ) +const ( + SpaceSettingsChangeType = "reserved.spacesettings" + SpaceDerivationScheme = "derivation.standard" +) + func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload storage.SpaceStorageCreatePayload, err error) { // unmarshalling signing and encryption keys identity, err := payload.SigningKey.GetPublic().Raw() @@ -23,8 +30,8 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload st } // preparing header and space id - bytes := make([]byte, 32) - _, err = rand.Read(bytes) + spaceHeaderSeed := make([]byte, 32) + _, err = rand.Read(spaceHeaderSeed) if err != nil { return } @@ -33,7 +40,7 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload st Timestamp: time.Now().UnixNano(), SpaceType: payload.SpaceType, ReplicationKey: payload.ReplicationKey, - Seed: bytes, + Seed: spaceHeaderSeed, } marshalled, err := header.Marshal() if err != nil { @@ -68,12 +75,11 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload st } // preparing acl - aclRoot := &aclrecordproto2.ACLRoot{ + aclRoot := &aclrecordproto.ACLRoot{ Identity: identity, EncryptionKey: encPubKey, SpaceId: spaceId, EncryptedReadKey: encReadKey, - DerivationScheme: "", CurrentReadKeyHash: readKeyHash, Timestamp: time.Now().UnixNano(), } @@ -82,10 +88,31 @@ func storagePayloadForSpaceCreate(payload SpaceCreatePayload) (storagePayload st return } + builder := tree.NewChangeBuilder(common.NewKeychain(), nil) + spaceSettingsSeed := make([]byte, 32) + _, err = rand.Read(spaceSettingsSeed) + if err != nil { + return + } + + _, settingsRoot, err := builder.BuildInitialContent(tree.InitialContent{ + AclHeadId: rawWithId.Id, + Identity: aclRoot.Identity, + SigningKey: payload.SigningKey, + SpaceId: spaceId, + Seed: spaceSettingsSeed, + ChangeType: SpaceSettingsChangeType, + Timestamp: time.Now().UnixNano(), + }) + if err != nil { + return + } + // creating storage storagePayload = storage.SpaceStorageCreatePayload{ - AclWithId: rawWithId, - SpaceHeaderWithId: rawHeaderWithId, + AclWithId: rawWithId, + SpaceHeaderWithId: rawHeaderWithId, + SpaceSettingsWithId: settingsRoot, } return } @@ -144,7 +171,7 @@ func storagePayloadForSpaceDerive(payload SpaceDerivePayload) (storagePayload st } // deriving and encrypting read key - readKey, err := aclrecordproto2.ACLReadKeyDerive(signPrivKey, encPrivKey) + readKey, err := aclrecordproto.ACLReadKeyDerive(signPrivKey, encPrivKey) if err != nil { return } @@ -160,29 +187,41 @@ func storagePayloadForSpaceDerive(payload SpaceDerivePayload) (storagePayload st } // preparing acl - aclRoot := &aclrecordproto2.ACLRoot{ + aclRoot := &aclrecordproto.ACLRoot{ Identity: identity, EncryptionKey: encPubKey, SpaceId: spaceId, EncryptedReadKey: encReadKey, - DerivationScheme: "", + DerivationScheme: SpaceDerivationScheme, CurrentReadKeyHash: readKeyHash, - Timestamp: time.Now().UnixNano(), } rawWithId, err := marshalACLRoot(aclRoot, payload.SigningKey) if err != nil { return } + builder := tree.NewChangeBuilder(common.NewKeychain(), nil) + _, settingsRoot, err := builder.BuildInitialContent(tree.InitialContent{ + AclHeadId: rawWithId.Id, + Identity: aclRoot.Identity, + SigningKey: payload.SigningKey, + SpaceId: spaceId, + ChangeType: SpaceSettingsChangeType, + }) + if err != nil { + return + } + // creating storage storagePayload = storage.SpaceStorageCreatePayload{ - AclWithId: rawWithId, - SpaceHeaderWithId: rawHeaderWithId, + AclWithId: rawWithId, + SpaceHeaderWithId: rawHeaderWithId, + SpaceSettingsWithId: settingsRoot, } return } -func marshalACLRoot(aclRoot *aclrecordproto2.ACLRoot, key signingkey.PrivKey) (rawWithId *aclrecordproto2.RawACLRecordWithId, err error) { +func marshalACLRoot(aclRoot *aclrecordproto.ACLRoot, key signingkey.PrivKey) (rawWithId *aclrecordproto.RawACLRecordWithId, err error) { marshalledRoot, err := aclRoot.Marshal() if err != nil { return @@ -191,7 +230,7 @@ func marshalACLRoot(aclRoot *aclrecordproto2.ACLRoot, key signingkey.PrivKey) (r if err != nil { return } - raw := &aclrecordproto2.RawACLRecord{ + raw := &aclrecordproto.RawACLRecord{ Payload: marshalledRoot, Signature: signature, } @@ -203,7 +242,7 @@ func marshalACLRoot(aclRoot *aclrecordproto2.ACLRoot, key signingkey.PrivKey) (r if err != nil { return } - rawWithId = &aclrecordproto2.RawACLRecordWithId{ + rawWithId = &aclrecordproto.RawACLRecordWithId{ Payload: marshalledRaw, Id: aclHeadId, } diff --git a/common/commonspace/settingsservice/service.go b/common/commonspace/settingsservice/service.go new file mode 100644 index 00000000..02c83bf5 --- /dev/null +++ b/common/commonspace/settingsservice/service.go @@ -0,0 +1,22 @@ +package settingsservice + +import ( + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/account" +) + +type Service interface { +} + +const deletionChangeType = "space.deletionlist" + +type service struct { + account account.Service +} + +func New() Service { + return nil +} + +type DeletedDocumentNotifiable interface { + NotifyDeleted(id string) +} diff --git a/common/commonspace/space.go b/common/commonspace/space.go index c63f854c..ef6845f4 100644 --- a/common/commonspace/space.go +++ b/common/commonspace/space.go @@ -141,6 +141,7 @@ func (s *space) Init(ctx context.Context) (err error) { if err != nil { return } + s.aclList = syncacl.NewSyncACL(aclList, s.syncService.StreamPool()) objectGetter := newCommonSpaceGetter(s.id, s.aclList, s.cache) s.syncService.Init(objectGetter) diff --git a/common/commonspace/synctree/synctree.go b/common/commonspace/synctree/synctree.go index 4121b3ae..2880d4d0 100644 --- a/common/commonspace/synctree/synctree.go +++ b/common/commonspace/synctree/synctree.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/app/logger" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/diffservice" + "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/settingsservice" spacestorage "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/storage" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/commonspace/syncservice/synchandler" @@ -18,15 +19,20 @@ import ( "github.com/gogo/protobuf/proto" ) -var ErrSyncTreeClosed = errors.New("sync tree is closed") +var ( + ErrSyncTreeClosed = errors.New("sync tree is closed") + ErrSyncTreeDeleted = errors.New("sync tree is deleted") +) // SyncTree sends head updates to sync service and also sends new changes to update listener type SyncTree struct { tree.ObjectTree synchandler.SyncHandler - syncClient SyncClient - listener updatelistener.UpdateListener - isClosed bool + syncClient SyncClient + listener updatelistener.UpdateListener + deletedNotifiable settingsservice.DeletedDocumentNotifiable + isClosed bool + isDeleted bool } var log = logger.NewNamed("commonspace.synctree").Sugar() @@ -37,25 +43,27 @@ var buildObjectTree = tree.BuildObjectTree var createSyncClient = newSyncClient type CreateDeps struct { - SpaceId string - Payload tree.ObjectTreeCreatePayload - Configuration nodeconf.Configuration - HeadNotifiable diffservice.HeadNotifiable - StreamPool syncservice.StreamPool - Listener updatelistener.UpdateListener - AclList list.ACLList - CreateStorage storage.TreeStorageCreatorFunc + SpaceId string + Payload tree.ObjectTreeCreatePayload + Configuration nodeconf.Configuration + HeadNotifiable diffservice.HeadNotifiable + StreamPool syncservice.StreamPool + Listener updatelistener.UpdateListener + AclList list.ACLList + CreateStorage storage.TreeStorageCreatorFunc + DeletedNotifiable settingsservice.DeletedDocumentNotifiable } type BuildDeps struct { - SpaceId string - StreamPool syncservice.StreamPool - Configuration nodeconf.Configuration - HeadNotifiable diffservice.HeadNotifiable - Listener updatelistener.UpdateListener - AclList list.ACLList - SpaceStorage spacestorage.SpaceStorage - TreeStorage storage.TreeStorage + SpaceId string + StreamPool syncservice.StreamPool + Configuration nodeconf.Configuration + HeadNotifiable diffservice.HeadNotifiable + Listener updatelistener.UpdateListener + AclList list.ACLList + SpaceStorage spacestorage.SpaceStorage + TreeStorage storage.TreeStorage + DeletedNotifiable settingsservice.DeletedDocumentNotifiable } func DeriveSyncTree(ctx context.Context, deps CreateDeps) (t tree.ObjectTree, err error) { @@ -70,9 +78,10 @@ func DeriveSyncTree(ctx context.Context, deps CreateDeps) (t tree.ObjectTree, er sharedFactory, deps.Configuration) syncTree := &SyncTree{ - ObjectTree: t, - syncClient: syncClient, - listener: deps.Listener, + ObjectTree: t, + syncClient: syncClient, + listener: deps.Listener, + deletedNotifiable: deps.DeletedNotifiable, } syncHandler := newSyncTreeHandler(syncTree, syncClient) syncTree.SyncHandler = syncHandler @@ -95,9 +104,10 @@ func CreateSyncTree(ctx context.Context, deps CreateDeps) (t tree.ObjectTree, er GetRequestFactory(), deps.Configuration) syncTree := &SyncTree{ - ObjectTree: t, - syncClient: syncClient, - listener: deps.Listener, + ObjectTree: t, + syncClient: syncClient, + listener: deps.Listener, + deletedNotifiable: deps.DeletedNotifiable, } syncHandler := newSyncTreeHandler(syncTree, syncClient) syncTree.SyncHandler = syncHandler @@ -175,9 +185,10 @@ func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t tr GetRequestFactory(), deps.Configuration) syncTree := &SyncTree{ - ObjectTree: t, - syncClient: syncClient, - listener: deps.Listener, + ObjectTree: t, + syncClient: syncClient, + listener: deps.Listener, + deletedNotifiable: deps.DeletedNotifiable, } syncHandler := newSyncTreeHandler(syncTree, syncClient) syncTree.SyncHandler = syncHandler @@ -196,8 +207,7 @@ func buildSyncTree(ctx context.Context, isFirstBuild bool, deps BuildDeps) (t tr } func (s *SyncTree) AddContent(ctx context.Context, content tree.SignableChangeContent) (res tree.AddResult, err error) { - if s.isClosed { // checkAlive err - err = ErrSyncTreeClosed + if err = s.checkAlive(); err != nil { return } res, err = s.ObjectTree.AddContent(ctx, content) @@ -210,8 +220,7 @@ func (s *SyncTree) AddContent(ctx context.Context, content tree.SignableChangeCo } func (s *SyncTree) AddRawChanges(ctx context.Context, changes ...*treechangeproto.RawTreeChangeWithId) (res tree.AddResult, err error) { - if s.isClosed { - err = ErrSyncTreeClosed + if err = s.checkAlive(); err != nil { return } res, err = s.ObjectTree.AddRawChanges(ctx, changes...) @@ -235,15 +244,44 @@ func (s *SyncTree) AddRawChanges(ctx context.Context, changes ...*treechangeprot return } +func (s *SyncTree) Delete() (err error) { + log.With("id", s.ID()).Debug("deleting sync tree") + s.Lock() + defer func() { + s.Unlock() + if err == nil { + s.deletedNotifiable.NotifyDeleted(s.ID()) + } + }() + if err = s.checkAlive(); err != nil { + return + } + err = s.ObjectTree.Delete() + if err != nil { + return + } + s.isDeleted = true + + return +} + func (s *SyncTree) Close() (err error) { log.With("id", s.ID()).Debug("closing sync tree") s.Lock() defer s.Unlock() - log.With("id", s.ID()).Debug("taken lock on sync tree") - if s.isClosed { - err = ErrSyncTreeClosed + if err = s.checkAlive(); err != nil { return } s.isClosed = true return } + +func (s *SyncTree) checkAlive() (err error) { + if s.isClosed { + err = ErrSyncTreeClosed + } + if s.isDeleted { + err = ErrSyncTreeDeleted + } + return +} diff --git a/common/pkg/acl/storage/inmemory.go b/common/pkg/acl/storage/inmemory.go index e9834f9a..87906b4a 100644 --- a/common/pkg/acl/storage/inmemory.go +++ b/common/pkg/acl/storage/inmemory.go @@ -142,6 +142,10 @@ func (t *inMemoryTreeStorage) GetRawChange(ctx context.Context, changeId string) return nil, fmt.Errorf("could not get change with id: %s", changeId) } +func (t *inMemoryTreeStorage) Delete() error { + return nil +} + type inMemoryStorageProvider struct { objects map[string]TreeStorage sync.RWMutex diff --git a/common/pkg/acl/storage/treestorage.go b/common/pkg/acl/storage/treestorage.go index 549e872a..dcb99fd0 100644 --- a/common/pkg/acl/storage/treestorage.go +++ b/common/pkg/acl/storage/treestorage.go @@ -14,6 +14,7 @@ type TreeStorage interface { AddRawChange(change *treechangeproto.RawTreeChangeWithId) error GetRawChange(ctx context.Context, id string) (*treechangeproto.RawTreeChangeWithId, error) HasChange(ctx context.Context, id string) (bool, error) + Delete() error } type TreeStorageCreatorFunc = func(payload TreeStorageCreatePayload) (TreeStorage, error) diff --git a/common/pkg/acl/tree/changebuilder.go b/common/pkg/acl/tree/changebuilder.go index 05d37721..a3c9d3e2 100644 --- a/common/pkg/acl/tree/changebuilder.go +++ b/common/pkg/acl/tree/changebuilder.go @@ -48,7 +48,7 @@ type changeBuilder struct { keys *common.Keychain } -func newChangeBuilder(keys *common.Keychain, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder { +func NewChangeBuilder(keys *common.Keychain, rootChange *treechangeproto.RawTreeChangeWithId) ChangeBuilder { return &changeBuilder{keys: keys, rootChange: rootChange} } diff --git a/common/pkg/acl/tree/objecttree.go b/common/pkg/acl/tree/objecttree.go index a89b3a29..52bbc36b 100644 --- a/common/pkg/acl/tree/objecttree.go +++ b/common/pkg/acl/tree/objecttree.go @@ -57,6 +57,7 @@ type ObjectTree interface { AddContent(ctx context.Context, content SignableChangeContent) (AddResult, error) AddRawChanges(ctx context.Context, changes ...*treechangeproto.RawTreeChangeWithId) (AddResult, error) + Delete() error Close() error } @@ -100,7 +101,7 @@ func defaultObjectTreeDeps( aclList list2.ACLList) objectTreeDeps { keychain := common.NewKeychain() - changeBuilder := newChangeBuilder(keychain, rootChange) + changeBuilder := NewChangeBuilder(keychain, rootChange) treeBuilder := newTreeBuilder(treeStorage, changeBuilder) return objectTreeDeps{ changeBuilder: changeBuilder, @@ -508,6 +509,10 @@ func (ot *objectTree) Close() error { return nil } +func (ot *objectTree) Delete() error { + return nil +} + func (ot *objectTree) SnapshotPath() []string { // TODO: Add error as return parameter if ot.snapshotPathIsActual() { diff --git a/common/pkg/acl/tree/objecttree_test.go b/common/pkg/acl/tree/objecttree_test.go index da9f2364..337f7903 100644 --- a/common/pkg/acl/tree/objecttree_test.go +++ b/common/pkg/acl/tree/objecttree_test.go @@ -116,7 +116,7 @@ func prepareTreeContext(t *testing.T, aclList list.ACLList) testTreeContext { treeStorage := changeCreator.createNewTreeStorage("0", aclList.Head().Id) root, _ := treeStorage.Root() changeBuilder := &mockChangeBuilder{ - originalBuilder: newChangeBuilder(nil, root), + originalBuilder: NewChangeBuilder(nil, root), } deps := objectTreeDeps{ changeBuilder: changeBuilder, diff --git a/common/pkg/acl/tree/objecttreefactory.go b/common/pkg/acl/tree/objecttreefactory.go index c148d930..a1aeb880 100644 --- a/common/pkg/acl/tree/objecttreefactory.go +++ b/common/pkg/acl/tree/objecttreefactory.go @@ -71,7 +71,7 @@ func createObjectTree( Seed: seed, } - _, raw, err := newChangeBuilder(common.NewKeychain(), nil).BuildInitialContent(cnt) + _, raw, err := NewChangeBuilder(common.NewKeychain(), nil).BuildInitialContent(cnt) if err != nil { return } diff --git a/node/storage/keys.go b/node/storage/keys.go index 77b58e22..5dabc5da 100644 --- a/node/storage/keys.go +++ b/node/storage/keys.go @@ -1,6 +1,7 @@ package storage import ( + "fmt" "github.com/anytypeio/go-anytype-infrastructure-experiments/common/pkg/acl/storage" "strings" ) @@ -25,6 +26,7 @@ func (a aclKeys) RawRecordKey(id string) []byte { type treeKeys struct { id string + prefix string headsKey []byte } @@ -32,6 +34,7 @@ func newTreeKeys(id string) treeKeys { return treeKeys{ id: id, headsKey: storage.JoinStringsToBytes("t", id, "heads"), + prefix: fmt.Sprintf("t/%s", id), } } @@ -43,6 +46,10 @@ func (t treeKeys) RawChangeKey(id string) []byte { return storage.JoinStringsToBytes("t", t.id, id) } +func (t treeKeys) isTreeRecordKey(key string) bool { + return strings.HasPrefix(key, t.prefix) && !strings.HasSuffix(key, "/heads") +} + type spaceKeys struct { headerKey []byte } diff --git a/node/storage/treestorage.go b/node/storage/treestorage.go index f35872ec..f3da3c83 100644 --- a/node/storage/treestorage.go +++ b/node/storage/treestorage.go @@ -135,3 +135,36 @@ func (t *treeStorage) GetRawChange(ctx context.Context, id string) (raw *treecha func (t *treeStorage) HasChange(ctx context.Context, id string) (bool, error) { return t.db.Has(t.keys.RawChangeKey(id)) } + +func (t *treeStorage) Delete() (err error) { + storedKeys, err := t.storedKeys() + if err != nil { + return + } + for _, k := range storedKeys { + err = t.db.Delete(k) + if err != nil { + return + } + } + return +} + +func (t *treeStorage) storedKeys() (keys [][]byte, err error) { + index := t.db.Items() + + key, _, err := index.Next() + for err == nil { + strKey := string(key) + if t.keys.isTreeRecordKey(strKey) { + keys = append(keys, key) + } + key, _, err = index.Next() + } + + if err != pogreb.ErrIterationDone { + return + } + err = nil + return +}