mirror of
https://github.com/anyproto/any-sync.git
synced 2025-06-09 17:45:03 +09:00
Make better dependencies for object tree
This commit is contained in:
parent
d3e62b418a
commit
266fd9436c
7 changed files with 238 additions and 96 deletions
119
pkg/acl/tree/changebuilder.go
Normal file
119
pkg/acl/tree/changebuilder.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const componentBuilder = "tree.changebuilder"
|
||||||
|
|
||||||
|
type BuilderContent struct {
|
||||||
|
treeHeadIds []string
|
||||||
|
aclHeadId string
|
||||||
|
snapshotBaseId string
|
||||||
|
currentReadKeyHash uint64
|
||||||
|
identity string
|
||||||
|
isSnapshot bool
|
||||||
|
signingKey signingkey.PrivKey
|
||||||
|
readKey *symmetric.Key
|
||||||
|
content proto.Marshaler
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeBuilder interface {
|
||||||
|
ConvertFromRaw(rawChange *aclpb.RawChange) (ch *Change, err error)
|
||||||
|
ConvertFromRawAndVerify(rawChange *aclpb.RawChange) (ch *Change, err error)
|
||||||
|
BuildContent(payload BuilderContent) (ch *Change, raw *aclpb.RawChange, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type changeBuilder struct {
|
||||||
|
keys *keychain
|
||||||
|
}
|
||||||
|
|
||||||
|
func newChangeBuilder(keys *keychain) *changeBuilder {
|
||||||
|
return &changeBuilder{keys: keys}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *changeBuilder) ConvertFromRaw(rawChange *aclpb.RawChange) (ch *Change, err error) {
|
||||||
|
unmarshalled := &aclpb.Change{}
|
||||||
|
err = proto.Unmarshal(rawChange.Payload, unmarshalled)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ch = NewChange(rawChange.Id, unmarshalled, rawChange.Signature)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *changeBuilder) ConvertFromRawAndVerify(rawChange *aclpb.RawChange) (ch *Change, err error) {
|
||||||
|
unmarshalled := &aclpb.Change{}
|
||||||
|
ch, err = c.ConvertFromRaw(rawChange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
identityKey, err := c.keys.getOrAdd(unmarshalled.Identity)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := identityKey.Verify(rawChange.Payload, rawChange.Signature)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !res {
|
||||||
|
err = ErrIncorrectSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *changeBuilder) BuildContent(payload BuilderContent) (ch *Change, raw *aclpb.RawChange, err error) {
|
||||||
|
aclChange := &aclpb.Change{
|
||||||
|
TreeHeadIds: payload.treeHeadIds,
|
||||||
|
AclHeadId: payload.aclHeadId,
|
||||||
|
SnapshotBaseId: payload.snapshotBaseId,
|
||||||
|
CurrentReadKeyHash: payload.currentReadKeyHash,
|
||||||
|
Timestamp: int64(time.Now().Nanosecond()),
|
||||||
|
Identity: payload.identity,
|
||||||
|
IsSnapshot: payload.isSnapshot,
|
||||||
|
}
|
||||||
|
marshalledData, err := payload.content.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypted, err := payload.readKey.Encrypt(marshalledData)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
aclChange.ChangesData = encrypted
|
||||||
|
|
||||||
|
fullMarshalledChange, err := proto.Marshal(aclChange)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
signature, err := payload.signingKey.Sign(fullMarshalledChange)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := cid.NewCIDFromBytes(fullMarshalledChange)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ch = NewChange(id, aclChange, signature)
|
||||||
|
ch.ParsedModel = payload.content
|
||||||
|
|
||||||
|
raw = &aclpb.RawChange{
|
||||||
|
Payload: fullMarshalledChange,
|
||||||
|
Signature: signature,
|
||||||
|
Id: id,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -6,19 +6,19 @@ import (
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DocTreeValidator interface {
|
type ObjectTreeValidator interface {
|
||||||
ValidateTree(tree *Tree, aclList list.ACLList) error
|
ValidateTree(tree *Tree, aclList list.ACLList) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type docTreeValidator struct{}
|
type objectTreeValidator struct{}
|
||||||
|
|
||||||
func newTreeValidator() DocTreeValidator {
|
func newTreeValidator() ObjectTreeValidator {
|
||||||
return &docTreeValidator{}
|
return &objectTreeValidator{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *docTreeValidator) ValidateTree(tree *Tree, aclList list.ACLList) (err error) {
|
func (v *objectTreeValidator) ValidateTree(tree *Tree, aclList list.ACLList) (err error) {
|
||||||
// TODO: add validation logic where we check that the change refers to correct acl heads
|
aclList.RLock()
|
||||||
// that means that more recent changes should refer to more recent acl heads
|
defer aclList.RUnlock()
|
||||||
var (
|
var (
|
||||||
perm list.UserPermissionPair
|
perm list.UserPermissionPair
|
||||||
state = aclList.ACLState()
|
state = aclList.ACLState()
|
||||||
|
|
|
@ -13,6 +13,7 @@ type keychain struct {
|
||||||
func newKeychain() *keychain {
|
func newKeychain() *keychain {
|
||||||
return &keychain{
|
return &keychain{
|
||||||
decoder: signingkey.NewEDPubKeyDecoder(),
|
decoder: signingkey.NewEDPubKeyDecoder(),
|
||||||
|
keys: make(map[string]signingkey.PubKey),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,9 @@ import (
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
|
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ObjectTreeUpdateListener interface {
|
type ObjectTreeUpdateListener interface {
|
||||||
|
@ -64,24 +61,24 @@ type ObjectTree interface {
|
||||||
Storage() storage.TreeStorage
|
Storage() storage.TreeStorage
|
||||||
DebugDump() (string, error)
|
DebugDump() (string, error)
|
||||||
|
|
||||||
AddContent(ctx context.Context, aclList list.ACLList, content SignableChangeContent) (*aclpb.RawChange, error)
|
AddContent(ctx context.Context, content SignableChangeContent) (*aclpb.RawChange, error)
|
||||||
AddRawChanges(ctx context.Context, aclList list.ACLList, changes ...*aclpb.RawChange) (AddResult, error)
|
AddRawChanges(ctx context.Context, changes ...*aclpb.RawChange) (AddResult, error)
|
||||||
|
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type objectTree struct {
|
type objectTree struct {
|
||||||
treeStorage storage.TreeStorage
|
treeStorage storage.TreeStorage
|
||||||
|
changeBuilder ChangeBuilder
|
||||||
updateListener ObjectTreeUpdateListener
|
updateListener ObjectTreeUpdateListener
|
||||||
|
validator ObjectTreeValidator
|
||||||
|
treeBuilder *treeBuilder
|
||||||
|
aclList list.ACLList
|
||||||
|
|
||||||
id string
|
id string
|
||||||
header *aclpb.Header
|
header *aclpb.Header
|
||||||
tree *Tree
|
tree *Tree
|
||||||
|
|
||||||
treeBuilder *treeBuilder
|
|
||||||
validator DocTreeValidator
|
|
||||||
kch *keychain
|
|
||||||
|
|
||||||
// buffers
|
// buffers
|
||||||
difSnapshotBuf []*aclpb.RawChange
|
difSnapshotBuf []*aclpb.RawChange
|
||||||
tmpChangesBuf []*Change
|
tmpChangesBuf []*Change
|
||||||
|
@ -93,26 +90,52 @@ type objectTree struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildObjectTree(treeStorage storage.TreeStorage, listener ObjectTreeUpdateListener, aclList list.ACLList) (ObjectTree, error) {
|
type objectTreeDeps struct {
|
||||||
treeBuilder := newTreeBuilder(treeStorage)
|
changeBuilder ChangeBuilder
|
||||||
validator := newTreeValidator()
|
treeBuilder *treeBuilder
|
||||||
|
treeStorage storage.TreeStorage
|
||||||
|
updateListener ObjectTreeUpdateListener
|
||||||
|
validator ObjectTreeValidator
|
||||||
|
aclList list.ACLList
|
||||||
|
}
|
||||||
|
|
||||||
objTree := &objectTree{
|
func defaultObjectTreeDeps(
|
||||||
treeStorage: treeStorage,
|
treeStorage storage.TreeStorage,
|
||||||
tree: nil,
|
listener ObjectTreeUpdateListener,
|
||||||
|
aclList list.ACLList) objectTreeDeps {
|
||||||
|
|
||||||
|
keychain := newKeychain()
|
||||||
|
changeBuilder := newChangeBuilder(keychain)
|
||||||
|
treeBuilder := newTreeBuilder(treeStorage, changeBuilder)
|
||||||
|
return objectTreeDeps{
|
||||||
|
changeBuilder: changeBuilder,
|
||||||
treeBuilder: treeBuilder,
|
treeBuilder: treeBuilder,
|
||||||
validator: validator,
|
treeStorage: treeStorage,
|
||||||
updateListener: listener,
|
updateListener: listener,
|
||||||
|
validator: newTreeValidator(),
|
||||||
|
aclList: aclList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
|
||||||
|
objTree := &objectTree{
|
||||||
|
treeStorage: deps.treeStorage,
|
||||||
|
updateListener: deps.updateListener,
|
||||||
|
treeBuilder: deps.treeBuilder,
|
||||||
|
validator: deps.validator,
|
||||||
|
aclList: deps.aclList,
|
||||||
|
changeBuilder: deps.changeBuilder,
|
||||||
|
tree: nil,
|
||||||
tmpChangesBuf: make([]*Change, 0, 10),
|
tmpChangesBuf: make([]*Change, 0, 10),
|
||||||
difSnapshotBuf: make([]*aclpb.RawChange, 0, 10),
|
difSnapshotBuf: make([]*aclpb.RawChange, 0, 10),
|
||||||
notSeenIdxBuf: make([]int, 0, 10),
|
notSeenIdxBuf: make([]int, 0, 10),
|
||||||
kch: newKeychain(),
|
|
||||||
}
|
}
|
||||||
err := objTree.rebuildFromStorage(aclList, nil)
|
|
||||||
|
err := objTree.rebuildFromStorage(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
storageHeads, err := treeStorage.Heads()
|
storageHeads, err := objTree.treeStorage.Heads()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -123,30 +146,35 @@ func BuildObjectTree(treeStorage storage.TreeStorage, listener ObjectTreeUpdateL
|
||||||
if !slice.UnsortedEquals(storageHeads, objTree.tree.Heads()) {
|
if !slice.UnsortedEquals(storageHeads, objTree.tree.Heads()) {
|
||||||
log.With(zap.Strings("storage", storageHeads), zap.Strings("rebuilt", objTree.tree.Heads())).
|
log.With(zap.Strings("storage", storageHeads), zap.Strings("rebuilt", objTree.tree.Heads())).
|
||||||
Errorf("the heads in storage and objTree are different")
|
Errorf("the heads in storage and objTree are different")
|
||||||
err = treeStorage.SetHeads(objTree.tree.Heads())
|
err = objTree.treeStorage.SetHeads(objTree.tree.Heads())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
objTree.id, err = treeStorage.ID()
|
objTree.id, err = objTree.treeStorage.ID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
objTree.header, err = treeStorage.Header()
|
objTree.header, err = objTree.treeStorage.Header()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if listener != nil {
|
if objTree.updateListener != nil {
|
||||||
listener.Rebuild(objTree)
|
objTree.updateListener.Rebuild(objTree)
|
||||||
}
|
}
|
||||||
|
|
||||||
return objTree, nil
|
return objTree, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ot *objectTree) rebuildFromStorage(aclList list.ACLList, newChanges []*Change) (err error) {
|
func BuildObjectTree(treeStorage storage.TreeStorage, listener ObjectTreeUpdateListener, aclList list.ACLList) (ObjectTree, error) {
|
||||||
ot.treeBuilder.Init(ot.kch)
|
deps := defaultObjectTreeDeps(treeStorage, listener, aclList)
|
||||||
|
return buildObjectTree(deps)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ot *objectTree) rebuildFromStorage(newChanges []*Change) (err error) {
|
||||||
|
ot.treeBuilder.Reset()
|
||||||
|
|
||||||
ot.tree, err = ot.treeBuilder.Build(newChanges)
|
ot.tree, err = ot.treeBuilder.Build(newChanges)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -157,7 +185,7 @@ func (ot *objectTree) rebuildFromStorage(aclList list.ACLList, newChanges []*Cha
|
||||||
// but obviously they are not roots, because of the way how we construct the tree
|
// but obviously they are not roots, because of the way how we construct the tree
|
||||||
ot.tree.clearPossibleRoots()
|
ot.tree.clearPossibleRoots()
|
||||||
|
|
||||||
return ot.validator.ValidateTree(ot.tree, aclList)
|
return ot.validator.ValidateTree(ot.tree, ot.aclList)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ot *objectTree) ID() string {
|
func (ot *objectTree) ID() string {
|
||||||
|
@ -172,83 +200,54 @@ func (ot *objectTree) Storage() storage.TreeStorage {
|
||||||
return ot.treeStorage
|
return ot.treeStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ot *objectTree) AddContent(ctx context.Context, aclList list.ACLList, content SignableChangeContent) (rawChange *aclpb.RawChange, err error) {
|
func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeContent) (rawChange *aclpb.RawChange, err error) {
|
||||||
|
ot.aclList.Lock()
|
||||||
defer func() {
|
defer func() {
|
||||||
|
ot.aclList.Unlock()
|
||||||
if ot.updateListener != nil {
|
if ot.updateListener != nil {
|
||||||
ot.updateListener.Update(ot)
|
ot.updateListener.Update(ot)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
state := aclList.ACLState() // special method for own keys
|
|
||||||
aclChange := &aclpb.Change{
|
|
||||||
TreeHeadIds: ot.tree.Heads(),
|
|
||||||
AclHeadId: aclList.Head().Id,
|
|
||||||
SnapshotBaseId: ot.tree.RootId(),
|
|
||||||
CurrentReadKeyHash: state.CurrentReadKeyHash(),
|
|
||||||
Timestamp: int64(time.Now().Nanosecond()),
|
|
||||||
Identity: content.Identity,
|
|
||||||
IsSnapshot: content.IsSnapshot,
|
|
||||||
}
|
|
||||||
|
|
||||||
marshalledData, err := content.Proto.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
state := ot.aclList.ACLState() // special method for own keys
|
||||||
readKey, err := state.CurrentReadKey()
|
readKey, err := state.CurrentReadKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypted, err := readKey.Encrypt(marshalledData)
|
payload := BuilderContent{
|
||||||
if err != nil {
|
treeHeadIds: ot.tree.Heads(),
|
||||||
return nil, err
|
aclHeadId: ot.aclList.Head().Id,
|
||||||
|
snapshotBaseId: ot.tree.RootId(),
|
||||||
|
currentReadKeyHash: state.CurrentReadKeyHash(),
|
||||||
|
identity: content.Identity,
|
||||||
|
isSnapshot: content.IsSnapshot,
|
||||||
|
signingKey: content.Key,
|
||||||
|
readKey: readKey,
|
||||||
|
content: content.Proto,
|
||||||
}
|
}
|
||||||
aclChange.ChangesData = encrypted
|
objChange, rawChange, err := ot.changeBuilder.BuildContent(payload)
|
||||||
|
|
||||||
fullMarshalledChange, err := proto.Marshal(aclChange)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
signature, err := content.Key.Sign(fullMarshalledChange)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := cid.NewCIDFromBytes(fullMarshalledChange)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
docChange := NewChange(id, aclChange, signature)
|
|
||||||
docChange.ParsedModel = content
|
|
||||||
|
|
||||||
if content.IsSnapshot {
|
if content.IsSnapshot {
|
||||||
// clearing tree, because we already fixed everything in the last snapshot
|
// clearing tree, because we already fixed everything in the last snapshot
|
||||||
ot.tree = &Tree{}
|
ot.tree = &Tree{}
|
||||||
}
|
}
|
||||||
err = ot.tree.AddMergedHead(docChange)
|
err = ot.tree.AddMergedHead(objChange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
rawChange = &aclpb.RawChange{
|
|
||||||
Payload: fullMarshalledChange,
|
|
||||||
Signature: docChange.Signature(),
|
|
||||||
Id: docChange.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ot.treeStorage.AddRawChange(rawChange)
|
err = ot.treeStorage.AddRawChange(rawChange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ot.treeStorage.SetHeads([]string{docChange.Id})
|
err = ot.treeStorage.SetHeads([]string{objChange.Id})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ot *objectTree) AddRawChanges(ctx context.Context, aclList list.ACLList, rawChanges ...*aclpb.RawChange) (addResult AddResult, err error) {
|
func (ot *objectTree) AddRawChanges(ctx context.Context, rawChanges ...*aclpb.RawChange) (addResult AddResult, err error) {
|
||||||
var mode Mode
|
var mode Mode
|
||||||
mode, addResult, err = ot.addRawChanges(ctx, aclList, rawChanges...)
|
mode, addResult, err = ot.addRawChanges(ctx, rawChanges...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -285,14 +284,14 @@ func (ot *objectTree) AddRawChanges(ctx context.Context, aclList list.ACLList, r
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ot *objectTree) addRawChanges(ctx context.Context, aclList list.ACLList, rawChanges ...*aclpb.RawChange) (mode Mode, addResult AddResult, err error) {
|
func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*aclpb.RawChange) (mode Mode, addResult AddResult, err error) {
|
||||||
// resetting buffers
|
// resetting buffers
|
||||||
ot.tmpChangesBuf = ot.tmpChangesBuf[:0]
|
ot.tmpChangesBuf = ot.tmpChangesBuf[:0]
|
||||||
ot.notSeenIdxBuf = ot.notSeenIdxBuf[:0]
|
ot.notSeenIdxBuf = ot.notSeenIdxBuf[:0]
|
||||||
ot.difSnapshotBuf = ot.difSnapshotBuf[:0]
|
ot.difSnapshotBuf = ot.difSnapshotBuf[:0]
|
||||||
ot.newSnapshotsBuf = ot.newSnapshotsBuf[:0]
|
ot.newSnapshotsBuf = ot.newSnapshotsBuf[:0]
|
||||||
|
|
||||||
// this will be returned to client so we shouldn't use buffer here
|
// this will be returned to client, so we shouldn't use buffer here
|
||||||
prevHeadsCopy := make([]string, 0, len(ot.tree.Heads()))
|
prevHeadsCopy := make([]string, 0, len(ot.tree.Heads()))
|
||||||
copy(prevHeadsCopy, ot.tree.Heads())
|
copy(prevHeadsCopy, ot.tree.Heads())
|
||||||
|
|
||||||
|
@ -303,7 +302,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, aclList list.ACLList, r
|
||||||
}
|
}
|
||||||
|
|
||||||
var change *Change
|
var change *Change
|
||||||
change, err = newVerifiedChangeFromRaw(ch, ot.kch)
|
change, err = ot.changeBuilder.ConvertFromRawAndVerify(ch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -370,10 +369,10 @@ func (ot *objectTree) addRawChanges(ctx context.Context, aclList list.ACLList, r
|
||||||
// checking if we have some changes with different snapshot and then rebuilding
|
// checking if we have some changes with different snapshot and then rebuilding
|
||||||
for _, ch := range ot.tmpChangesBuf {
|
for _, ch := range ot.tmpChangesBuf {
|
||||||
if isOldSnapshot(ch) {
|
if isOldSnapshot(ch) {
|
||||||
err = ot.rebuildFromStorage(aclList, ot.tmpChangesBuf)
|
err = ot.rebuildFromStorage(ot.tmpChangesBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// rebuilding without new changes
|
// rebuilding without new changes
|
||||||
ot.rebuildFromStorage(aclList, nil)
|
ot.rebuildFromStorage(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,7 +400,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, aclList list.ACLList, r
|
||||||
default:
|
default:
|
||||||
// just rebuilding the state from start without reloading everything from tree storage
|
// just rebuilding the state from start without reloading everything from tree storage
|
||||||
// as an optimization we could've started from current heads, but I didn't implement that
|
// as an optimization we could've started from current heads, but I didn't implement that
|
||||||
err = ot.validator.ValidateTree(ot.tree, aclList)
|
err = ot.validator.ValidateTree(ot.tree, ot.aclList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rollback()
|
rollback()
|
||||||
err = ErrHasInvalidChanges
|
err = ErrHasInvalidChanges
|
||||||
|
|
21
pkg/acl/tree/objecttree_test.go
Normal file
21
pkg/acl/tree/objecttree_test.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestObjectTree(t *testing.T) {
|
||||||
|
a := &app.App{}
|
||||||
|
inmemory := storage.NewInMemoryTreeStorage(...)
|
||||||
|
app.RegisterWithType[storage.TreeStorage](a, inmemory)
|
||||||
|
app.RegisterWithType[]()
|
||||||
|
|
||||||
|
a.Start(context.Background())
|
||||||
|
objectTree := app.MustComponentWithType[ObjectTree](a).(ObjectTree)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -115,6 +115,7 @@ func (t *Tree) Add(changes ...*Change) (mode Mode) {
|
||||||
return Append
|
return Append
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveInvalidChange removes all the changes that are descendants of id
|
||||||
func (t *Tree) RemoveInvalidChange(id string) {
|
func (t *Tree) RemoveInvalidChange(id string) {
|
||||||
stack := []string{id}
|
stack := []string{id}
|
||||||
// removing all children of this id (either next or unattached)
|
// removing all children of this id (either next or unattached)
|
||||||
|
|
|
@ -17,25 +17,26 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type treeBuilder struct {
|
type treeBuilder struct {
|
||||||
cache map[string]*Change
|
|
||||||
kch *keychain
|
|
||||||
tree *Tree
|
|
||||||
treeStorage storage.TreeStorage
|
treeStorage storage.TreeStorage
|
||||||
|
builder ChangeBuilder
|
||||||
|
|
||||||
|
cache map[string]*Change
|
||||||
|
tree *Tree
|
||||||
|
|
||||||
// buffers
|
// buffers
|
||||||
idStack []string
|
idStack []string
|
||||||
loadBuffer []*Change
|
loadBuffer []*Change
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTreeBuilder(t storage.TreeStorage) *treeBuilder {
|
func newTreeBuilder(storage storage.TreeStorage, builder ChangeBuilder) *treeBuilder {
|
||||||
return &treeBuilder{
|
return &treeBuilder{
|
||||||
treeStorage: t,
|
treeStorage: storage,
|
||||||
|
builder: builder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *treeBuilder) Init(kch *keychain) {
|
func (tb *treeBuilder) Reset() {
|
||||||
tb.cache = make(map[string]*Change)
|
tb.cache = make(map[string]*Change)
|
||||||
tb.kch = kch
|
|
||||||
tb.tree = &Tree{}
|
tb.tree = &Tree{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +132,7 @@ func (tb *treeBuilder) loadChange(id string) (ch *Change, err error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ch, err = newVerifiedChangeFromRaw(change, tb.kch)
|
ch, err = tb.builder.ConvertFromRawAndVerify(change)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue