1
0
Fork 0
mirror of https://github.com/anyproto/any-sync.git synced 2025-06-11 18:20:28 +09:00
any-sync/acl/acltree/acltree.go
2022-07-13 19:17:50 +03:00

325 lines
7.4 KiB
Go

package acltree
import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/acl/thread"
"sync"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
)
type AddResultSummary int
const (
AddResultSummaryNothing AddResultSummary = iota
AddResultSummaryAppend
AddResultSummaryRebuild
)
type AddResult struct {
OldHeads []string
Heads []string
// TODO: add summary for changes
Summary AddResultSummary
}
type TreeUpdateListener interface {
Update(tree ACLTree)
Rebuild(tree ACLTree)
}
type ACLTree interface {
ACLState() *ACLState
AddContent(f func(builder ChangeBuilder) error) (*Change, error)
AddChanges(changes ...*Change) (AddResult, error)
Heads() []string
Root() *Change
Iterate(func(change *Change) bool)
IterateFrom(string, func(change *Change) bool)
HasChange(string) bool
}
type aclTree struct {
thread thread.Thread
accountData *account.AccountData
updateListener TreeUpdateListener
fullTree *Tree
aclTreeFromStart *Tree // TODO: right now we don't use it, we can probably have only local var for now. This tree is built from start of the document
aclState *ACLState
treeBuilder *treeBuilder
aclTreeBuilder *aclTreeBuilder
aclStateBuilder *aclStateBuilder
snapshotValidator *snapshotValidator
changeBuilder *changeBuilder
sync.RWMutex
}
func BuildACLTree(
t thread.Thread,
acc *account.AccountData,
listener TreeUpdateListener) (ACLTree, error) {
decoder := keys.NewEd25519Decoder()
aclTreeBuilder := newACLTreeBuilder(t, decoder)
treeBuilder := newTreeBuilder(t, decoder)
snapshotValidator := newSnapshotValidator(decoder, acc)
aclStateBuilder := newACLStateBuilder(decoder, acc)
changeBuilder := newChangeBuilder()
aclTree := &aclTree{
thread: t,
accountData: acc,
fullTree: nil,
aclState: nil,
treeBuilder: treeBuilder,
aclTreeBuilder: aclTreeBuilder,
aclStateBuilder: aclStateBuilder,
snapshotValidator: snapshotValidator,
changeBuilder: changeBuilder,
updateListener: listener,
}
err := aclTree.rebuildFromThread(false)
if err != nil {
return nil, err
}
aclTree.removeOrphans()
t.SetHeads(aclTree.Heads())
listener.Rebuild(aclTree)
return aclTree, nil
}
// TODO: this is not used for now, in future we should think about not making full tree rebuild
//func (a *aclTree) rebuildFromTree(validateSnapshot bool) (err error) {
// if validateSnapshot {
// err = a.snapshotValidator.Init(a.aclTreeFromStart)
// if err != nil {
// return err
// }
//
// valid, err := a.snapshotValidator.ValidateSnapshot(a.fullTree.root)
// if err != nil {
// return err
// }
// if !valid {
// return a.rebuildFromThread(true)
// }
// }
//
// err = a.aclStateBuilder.Init(a.fullTree)
// if err != nil {
// return err
// }
//
// a.aclState, err = a.aclStateBuilder.Build()
// if err != nil {
// return err
// }
//
// return nil
//}
func (a *aclTree) removeOrphans() {
// removing attached or invalid orphans
var toRemove []string
for _, orphan := range a.thread.Orphans() {
if _, exists := a.fullTree.attached[orphan]; exists {
toRemove = append(toRemove, orphan)
}
if _, exists := a.fullTree.invalidChanges[orphan]; exists {
toRemove = append(toRemove, orphan)
}
}
a.thread.RemoveOrphans(toRemove...)
}
func (a *aclTree) rebuildFromThread(fromStart bool) error {
a.treeBuilder.Init()
a.aclTreeBuilder.Init()
var err error
a.fullTree, err = a.treeBuilder.Build(fromStart)
if err != nil {
return err
}
// TODO: remove this from context as this is used only to validate snapshot
a.aclTreeFromStart, err = a.aclTreeBuilder.Build()
if err != nil {
return err
}
if !fromStart {
err = a.snapshotValidator.Init(a.aclTreeFromStart)
if err != nil {
return err
}
valid, err := a.snapshotValidator.ValidateSnapshot(a.fullTree.root)
if err != nil {
return err
}
if !valid {
return a.rebuildFromThread(true)
}
}
// TODO: there is a question how we can validate not only that the full tree is built correctly
// but also that the ACL prev ids are not messed up. I think we should probably compare the resulting
// acl state with the acl state which is built in aclTreeFromStart
err = a.aclStateBuilder.Init(a.fullTree)
if err != nil {
return err
}
a.aclState, err = a.aclStateBuilder.Build()
if err != nil {
return err
}
return nil
}
func (a *aclTree) ACLState() *ACLState {
a.RLock()
defer a.RUnlock()
return a.aclState
}
func (a *aclTree) AddContent(build func(builder ChangeBuilder) error) (*Change, error) {
// TODO: add snapshot creation logic
a.Lock()
defer func() {
a.Unlock()
// TODO: should this be called in a separate goroutine to prevent accidental cycles (tree->updater->tree)
a.updateListener.Update(a)
}()
a.changeBuilder.Init(a.aclState, a.fullTree, a.accountData)
err := build(a.changeBuilder)
if err != nil {
return nil, err
}
ch, marshalled, err := a.changeBuilder.BuildAndApply()
if err != nil {
return nil, err
}
a.fullTree.AddFast(ch)
err = a.thread.AddRawChange(&thread.RawChange{
Payload: marshalled,
Signature: ch.Signature(),
Id: ch.Id,
})
if err != nil {
return nil, err
}
a.thread.SetHeads([]string{ch.Id})
return ch, nil
}
func (a *aclTree) AddChanges(changes ...*Change) (AddResult, error) {
a.Lock()
// TODO: make proper error handling, because there are a lot of corner cases where this will break
var err error
var mode Mode
defer func() {
if err != nil {
return
}
a.removeOrphans()
a.thread.SetHeads(a.fullTree.Heads())
a.Unlock()
switch mode {
case Append:
a.updateListener.Update(a)
case Rebuild:
a.updateListener.Rebuild(a)
default:
break
}
}()
for _, ch := range changes {
err = a.thread.AddChange(ch)
if err != nil {
return AddResult{}, err
}
a.thread.AddOrphans(ch.Id)
}
prevHeads := a.fullTree.Heads()
mode = a.fullTree.Add(changes...)
switch mode {
case Nothing:
return AddResult{
OldHeads: prevHeads,
Heads: prevHeads,
Summary: AddResultSummaryNothing,
}, nil
case Rebuild:
err = a.rebuildFromThread(false)
if err != nil {
return AddResult{}, err
}
return AddResult{
OldHeads: prevHeads,
Heads: a.fullTree.Heads(),
Summary: AddResultSummaryRebuild,
}, nil
default:
// just rebuilding the state from start without reloading everything from thread
// as an optimization we could've started from current heads, but I didn't implement that
a.aclState, err = a.aclStateBuilder.Build()
if err != nil {
return AddResult{}, err
}
return AddResult{
OldHeads: prevHeads,
Heads: a.fullTree.Heads(),
Summary: AddResultSummaryAppend,
}, nil
}
}
func (a *aclTree) Iterate(f func(change *Change) bool) {
a.RLock()
defer a.RUnlock()
a.fullTree.Iterate(a.fullTree.RootId(), f)
}
func (a *aclTree) IterateFrom(s string, f func(change *Change) bool) {
a.RLock()
defer a.RUnlock()
a.fullTree.Iterate(s, f)
}
func (a *aclTree) HasChange(s string) bool {
a.RLock()
defer a.RUnlock()
_, attachedExists := a.fullTree.attached[s]
_, unattachedExists := a.fullTree.unAttached[s]
_, invalidExists := a.fullTree.invalidChanges[s]
return attachedExists || unattachedExists || invalidExists
}
func (a *aclTree) Heads() []string {
a.RLock()
defer a.RUnlock()
return a.fullTree.Heads()
}
func (a *aclTree) Root() *Change {
a.RLock()
defer a.RUnlock()
return a.fullTree.Root()
}