WIP history tree
This commit is contained in:
parent
e0db25e2b3
commit
4a0ec1f793
43
commonspace/object/tree/objecttree/historytree.go
Normal file
43
commonspace/object/tree/objecttree/historytree.go
Normal file
@ -0,0 +1,43 @@
|
||||
package objecttree
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrLoadBeforeRoot = errors.New("can't load before root")
|
||||
|
||||
type HistoryTree interface {
|
||||
RWLocker
|
||||
|
||||
Id() string
|
||||
Root() *Change
|
||||
Heads() []string
|
||||
IterateFrom(id string, convert ChangeConvertFunc, iterate ChangeIterateFunc) error
|
||||
}
|
||||
|
||||
type historyTree struct {
|
||||
*objectTree
|
||||
}
|
||||
|
||||
func (h *historyTree) rebuildFromStorage(beforeId string, include bool) (err error) {
|
||||
ot := h.objectTree
|
||||
ot.treeBuilder.Reset()
|
||||
if beforeId == ot.Id() && !include {
|
||||
return ErrLoadBeforeRoot
|
||||
}
|
||||
|
||||
heads := []string{beforeId}
|
||||
if beforeId == "" {
|
||||
heads, err = ot.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if !include {
|
||||
beforeChange, err := ot.treeBuilder.loadChange(beforeId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
heads = beforeChange.PreviousIds
|
||||
}
|
||||
|
||||
ot.tree, err = ot.treeBuilder.build(heads, nil, nil)
|
||||
return
|
||||
}
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/acl/aclrecordproto"
|
||||
list2 "github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
list "github.com/anytypeio/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/keychain"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
|
||||
@ -75,7 +75,7 @@ type objectTree struct {
|
||||
validator ObjectTreeValidator
|
||||
rawChangeLoader *rawChangeLoader
|
||||
treeBuilder *treeBuilder
|
||||
aclList list2.AclList
|
||||
aclList list.AclList
|
||||
|
||||
id string
|
||||
rawRoot *treechangeproto.RawTreeChangeWithId
|
||||
@ -101,13 +101,13 @@ type objectTreeDeps struct {
|
||||
treeStorage treestorage.TreeStorage
|
||||
validator ObjectTreeValidator
|
||||
rawChangeLoader *rawChangeLoader
|
||||
aclList list2.AclList
|
||||
aclList list.AclList
|
||||
}
|
||||
|
||||
func defaultObjectTreeDeps(
|
||||
rootChange *treechangeproto.RawTreeChangeWithId,
|
||||
treeStorage treestorage.TreeStorage,
|
||||
aclList list2.AclList) objectTreeDeps {
|
||||
aclList list.AclList) objectTreeDeps {
|
||||
|
||||
keychain := keychain.NewKeychain()
|
||||
changeBuilder := NewChangeBuilder(keychain, rootChange)
|
||||
@ -208,7 +208,7 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt
|
||||
canWrite := state.HasPermission(content.Identity, aclrecordproto.AclUserPermissions_Writer) ||
|
||||
state.HasPermission(content.Identity, aclrecordproto.AclUserPermissions_Admin)
|
||||
if !canWrite {
|
||||
err = list2.ErrInsufficientPermissions
|
||||
err = list.ErrInsufficientPermissions
|
||||
return
|
||||
}
|
||||
|
||||
@ -471,7 +471,7 @@ func (ot *objectTree) IterateFrom(id string, convert ChangeConvertFunc, iterate
|
||||
}
|
||||
readKey, exists := ot.keys[c.ReadKeyHash]
|
||||
if !exists {
|
||||
err = list2.ErrNoReadKey
|
||||
err = list.ErrNoReadKey
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -111,6 +111,24 @@ func prepareAclList(t *testing.T) list.AclList {
|
||||
return aclList
|
||||
}
|
||||
|
||||
func prepareTreeDeps(aclList list.AclList) (*mockChangeCreator, objectTreeDeps) {
|
||||
changeCreator := &mockChangeCreator{}
|
||||
treeStorage := changeCreator.createNewTreeStorage("0", aclList.Head().Id)
|
||||
root, _ := treeStorage.Root()
|
||||
changeBuilder := &mockChangeBuilder{
|
||||
originalBuilder: NewChangeBuilder(nil, root),
|
||||
}
|
||||
deps := objectTreeDeps{
|
||||
changeBuilder: changeBuilder,
|
||||
treeBuilder: newTreeBuilder(treeStorage, changeBuilder),
|
||||
treeStorage: treeStorage,
|
||||
rawChangeLoader: newRawChangeLoader(treeStorage, changeBuilder),
|
||||
validator: &mockChangeValidator{},
|
||||
aclList: aclList,
|
||||
}
|
||||
return changeCreator, deps
|
||||
}
|
||||
|
||||
func prepareTreeContext(t *testing.T, aclList list.AclList) testTreeContext {
|
||||
changeCreator := &mockChangeCreator{}
|
||||
treeStorage := changeCreator.createNewTreeStorage("0", aclList.Head().Id)
|
||||
@ -542,4 +560,87 @@ func TestObjectTree(t *testing.T) {
|
||||
assert.Equal(t, ch, raw, "the changes in the storage should be the same")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test history tree not include", func(t *testing.T) {
|
||||
changeCreator, deps := prepareTreeDeps(aclList)
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
}
|
||||
deps.treeStorage.TransactionAdd(rawChanges, []string{"6"})
|
||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||
BeforeId: "6",
|
||||
IncludeBeforeId: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// check tree heads
|
||||
assert.Equal(t, []string{"3", "4", "5"}, hTree.Heads())
|
||||
|
||||
// check tree iterate
|
||||
var iterChangesId []string
|
||||
err = hTree.IterateFrom(hTree.Root().Id, nil, func(change *Change) bool {
|
||||
iterChangesId = append(iterChangesId, change.Id)
|
||||
return true
|
||||
})
|
||||
require.NoError(t, err, "iterate should be without error")
|
||||
assert.Equal(t, []string{"0", "1", "2", "3", "4", "5"}, iterChangesId)
|
||||
assert.Equal(t, "0", hTree.Root().Id)
|
||||
})
|
||||
|
||||
t.Run("test history tree include", func(t *testing.T) {
|
||||
changeCreator, deps := prepareTreeDeps(aclList)
|
||||
|
||||
rawChanges := []*treechangeproto.RawTreeChangeWithId{
|
||||
changeCreator.createRaw("1", aclList.Head().Id, "0", false, "0"),
|
||||
changeCreator.createRaw("2", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("3", aclList.Head().Id, "0", true, "2"),
|
||||
changeCreator.createRaw("4", aclList.Head().Id, "0", false, "2"),
|
||||
changeCreator.createRaw("5", aclList.Head().Id, "0", false, "1"),
|
||||
changeCreator.createRaw("6", aclList.Head().Id, "0", false, "3", "4", "5"),
|
||||
}
|
||||
deps.treeStorage.TransactionAdd(rawChanges, []string{"6"})
|
||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||
BeforeId: "6",
|
||||
IncludeBeforeId: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// check tree heads
|
||||
assert.Equal(t, []string{"6"}, hTree.Heads())
|
||||
|
||||
// check tree iterate
|
||||
var iterChangesId []string
|
||||
err = hTree.IterateFrom(hTree.Root().Id, nil, func(change *Change) bool {
|
||||
iterChangesId = append(iterChangesId, change.Id)
|
||||
return true
|
||||
})
|
||||
require.NoError(t, err, "iterate should be without error")
|
||||
assert.Equal(t, []string{"0", "1", "2", "3", "4", "5", "6"}, iterChangesId)
|
||||
assert.Equal(t, "0", hTree.Root().Id)
|
||||
})
|
||||
|
||||
t.Run("test history tree root", func(t *testing.T) {
|
||||
_, deps := prepareTreeDeps(aclList)
|
||||
hTree, err := buildHistoryTree(deps, HistoryTreeParams{
|
||||
BeforeId: "0",
|
||||
IncludeBeforeId: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// check tree heads
|
||||
assert.Equal(t, []string{"0"}, hTree.Heads())
|
||||
|
||||
// check tree iterate
|
||||
var iterChangesId []string
|
||||
err = hTree.IterateFrom(hTree.Root().Id, nil, func(change *Change) bool {
|
||||
iterChangesId = append(iterChangesId, change.Id)
|
||||
return true
|
||||
})
|
||||
require.NoError(t, err, "iterate should be without error")
|
||||
assert.Equal(t, []string{"0"}, iterChangesId)
|
||||
assert.Equal(t, "0", hTree.Root().Id)
|
||||
})
|
||||
}
|
||||
|
||||
@ -41,16 +41,20 @@ func (tb *treeBuilder) Reset() {
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) Build(theirHeads []string, newChanges []*Change) (*Tree, error) {
|
||||
var proposedHeads []string
|
||||
tb.cache = make(map[string]*Change)
|
||||
heads, err := tb.treeStorage.Heads()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tb.build(heads, theirHeads, newChanges)
|
||||
}
|
||||
|
||||
func (tb *treeBuilder) build(heads []string, theirHeads []string, newChanges []*Change) (*Tree, error) {
|
||||
var proposedHeads []string
|
||||
tb.cache = make(map[string]*Change)
|
||||
|
||||
// TODO: we can actually get this from tree (though not sure, that there would always be
|
||||
// an invariant where the tree has the closest common snapshot of heads)
|
||||
// so if optimization is critical we can change this to inject from tree directly
|
||||
// so if optimization is critical we can change this to inject from tree directly,
|
||||
// but then we have to be sure that invariant stays true
|
||||
oldBreakpoint, err := tb.findBreakpoint(heads, true)
|
||||
if err != nil {
|
||||
|
||||
@ -41,6 +41,15 @@ func BuildObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList)
|
||||
return buildObjectTree(deps)
|
||||
}
|
||||
|
||||
func BuildHistoryTree(params HistoryTreeParams) (HistoryTree, error) {
|
||||
rootChange, err := params.TreeStorage.Root()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deps := defaultObjectTreeDeps(rootChange, params.TreeStorage, params.AclList)
|
||||
return buildHistoryTree(deps, params)
|
||||
}
|
||||
|
||||
func CreateDerivedObjectTree(
|
||||
payload ObjectTreeCreatePayload,
|
||||
aclList list.AclList,
|
||||
@ -118,7 +127,6 @@ func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
|
||||
aclList: deps.aclList,
|
||||
changeBuilder: deps.changeBuilder,
|
||||
rawChangeLoader: deps.rawChangeLoader,
|
||||
tree: nil,
|
||||
keys: make(map[uint64]*symmetric.Key),
|
||||
newChangesBuf: make([]*Change, 0, 10),
|
||||
difSnapshotBuf: make([]*treechangeproto.RawTreeChangeWithId, 0, 10),
|
||||
@ -146,3 +154,38 @@ func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) {
|
||||
|
||||
return objTree, nil
|
||||
}
|
||||
|
||||
type HistoryTreeParams struct {
|
||||
TreeStorage treestorage.TreeStorage
|
||||
AclList list.AclList
|
||||
BeforeId string
|
||||
IncludeBeforeId bool
|
||||
}
|
||||
|
||||
func buildHistoryTree(deps objectTreeDeps, params HistoryTreeParams) (ht HistoryTree, err error) {
|
||||
objTree := &objectTree{
|
||||
treeStorage: deps.treeStorage,
|
||||
treeBuilder: deps.treeBuilder,
|
||||
validator: deps.validator,
|
||||
aclList: deps.aclList,
|
||||
changeBuilder: deps.changeBuilder,
|
||||
rawChangeLoader: deps.rawChangeLoader,
|
||||
keys: make(map[uint64]*symmetric.Key),
|
||||
newChangesBuf: make([]*Change, 0, 10),
|
||||
difSnapshotBuf: make([]*treechangeproto.RawTreeChangeWithId, 0, 10),
|
||||
notSeenIdxBuf: make([]int, 0, 10),
|
||||
newSnapshotsBuf: make([]*Change, 0, 10),
|
||||
}
|
||||
|
||||
hTree := historyTree{objectTree: objTree}
|
||||
err = hTree.rebuildFromStorage(params.BeforeId, params.IncludeBeforeId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objTree.id = objTree.treeStorage.Id()
|
||||
objTree.rawRoot, err = objTree.treeStorage.Root()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hTree, nil
|
||||
}
|
||||
@ -87,6 +87,7 @@ type Space interface {
|
||||
PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, listener updatelistener.UpdateListener) (t objecttree.ObjectTree, err error)
|
||||
BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (t objecttree.ObjectTree, err error)
|
||||
DeleteTree(ctx context.Context, id string) (err error)
|
||||
BuildHistoryTree(ctx context.Context, id string, opts HistoryTreeOpts) (t objecttree.HistoryTree, err error)
|
||||
|
||||
HeadSync() headsync.HeadSync
|
||||
SyncStatus() syncstatus.StatusUpdater
|
||||
@ -296,6 +297,11 @@ type BuildTreeOpts struct {
|
||||
WaitTreeRemoteSync bool
|
||||
}
|
||||
|
||||
type HistoryTreeOpts struct {
|
||||
BeforeId string
|
||||
Include bool
|
||||
}
|
||||
|
||||
func (s *space) BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (t objecttree.ObjectTree, err error) {
|
||||
if s.isClosed.Load() {
|
||||
err = ErrSpaceClosed
|
||||
@ -317,6 +323,24 @@ func (s *space) BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (t
|
||||
return synctree.BuildSyncTreeOrGetRemote(ctx, id, deps)
|
||||
}
|
||||
|
||||
func (s *space) BuildHistoryTree(ctx context.Context, id string, opts HistoryTreeOpts) (t objecttree.HistoryTree, err error) {
|
||||
if s.isClosed.Load() {
|
||||
err = ErrSpaceClosed
|
||||
return
|
||||
}
|
||||
|
||||
params := objecttree.HistoryTreeParams{
|
||||
AclList: s.aclList,
|
||||
BeforeId: opts.BeforeId,
|
||||
IncludeBeforeId: opts.Include,
|
||||
}
|
||||
params.TreeStorage, err = s.storage.TreeStorage(id)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
return objecttree.BuildHistoryTree(params)
|
||||
}
|
||||
|
||||
func (s *space) DeleteTree(ctx context.Context, id string) (err error) {
|
||||
return s.settingsObject.DeleteObject(id)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user