Add acltree in new package
This commit is contained in:
parent
307f517ee5
commit
110ec01b0c
@ -50,12 +50,19 @@ func newACLState() *ACLState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *ACLState) applyChange(change *aclpb.Change) (err error) {
|
func (st *ACLState) applyChange(changeWrapper *Change) (err error) {
|
||||||
|
change := changeWrapper.Content
|
||||||
aclData := &aclpb.ACLChangeACLData{}
|
aclData := &aclpb.ACLChangeACLData{}
|
||||||
|
|
||||||
|
if changeWrapper.DecryptedModel != nil {
|
||||||
|
aclData = changeWrapper.DecryptedModel.(*aclpb.ACLChangeACLData)
|
||||||
|
} else {
|
||||||
err = proto.Unmarshal(change.ChangesData, aclData)
|
err = proto.Unmarshal(change.ChangesData, aclData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
changeWrapper.DecryptedModel = aclData
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
79
pkg/acl/tree/aclstatebuilder.go
Normal file
79
pkg/acl/tree/aclstatebuilder.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
|
||||||
|
)
|
||||||
|
|
||||||
|
type aclStateBuilder struct {
|
||||||
|
tree *Tree
|
||||||
|
identity string
|
||||||
|
key encryptionkey.PrivKey
|
||||||
|
decoder signingkey.PubKeyDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func newACLStateBuilderWithIdentity(decoder signingkey.PubKeyDecoder, accountData *account.AccountData) *aclStateBuilder {
|
||||||
|
return &aclStateBuilder{
|
||||||
|
decoder: decoder,
|
||||||
|
identity: accountData.Identity,
|
||||||
|
key: accountData.EncKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newACLStateBuilder() *aclStateBuilder {
|
||||||
|
return &aclStateBuilder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *aclStateBuilder) Init(tree *Tree) error {
|
||||||
|
sb.tree = tree
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *aclStateBuilder) Build() (*ACLState, error) {
|
||||||
|
state, _, err := sb.BuildBefore("")
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb *aclStateBuilder) BuildBefore(beforeId string) (*ACLState, bool, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
startChange = sb.tree.root
|
||||||
|
state *ACLState
|
||||||
|
foundId = false
|
||||||
|
)
|
||||||
|
|
||||||
|
if sb.decoder != nil {
|
||||||
|
state = newACLStateWithIdentity(sb.identity, sb.key, sb.decoder)
|
||||||
|
} else {
|
||||||
|
state = newACLState()
|
||||||
|
}
|
||||||
|
|
||||||
|
if beforeId == startChange.Id {
|
||||||
|
return state, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.tree.IterateSkip(sb.tree.root.Id, startChange.Id, func(c *Change) (isContinue bool) {
|
||||||
|
err = state.applyChange(c)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// the user can't make changes
|
||||||
|
if !state.hasPermission(c.Content.Identity, aclpb.ACLChange_Writer) && !state.hasPermission(c.Content.Identity, aclpb.ACLChange_Admin) {
|
||||||
|
err = fmt.Errorf("user %s cannot make changes", c.Content.Identity)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Id == beforeId {
|
||||||
|
foundId = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return state, foundId, err
|
||||||
|
}
|
||||||
466
pkg/acl/tree/acltree.go
Normal file
466
pkg/acl/tree/acltree.go
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
package tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AddResultSummary int
|
||||||
|
|
||||||
|
var ErrTreeWithoutIdentity = errors.New("acl tree is created without identity")
|
||||||
|
|
||||||
|
const (
|
||||||
|
AddResultSummaryNothing AddResultSummary = iota
|
||||||
|
AddResultSummaryAppend
|
||||||
|
AddResultSummaryRebuild
|
||||||
|
)
|
||||||
|
|
||||||
|
type AddResult struct {
|
||||||
|
OldHeads []string
|
||||||
|
Heads []string
|
||||||
|
Added []*aclpb.RawChange
|
||||||
|
// TODO: add summary for changes
|
||||||
|
Summary AddResultSummary
|
||||||
|
}
|
||||||
|
|
||||||
|
type TreeUpdateListener interface {
|
||||||
|
Update(tree ACLTree)
|
||||||
|
Rebuild(tree ACLTree)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NoOpListener struct{}
|
||||||
|
|
||||||
|
func (n NoOpListener) Update(tree ACLTree) {}
|
||||||
|
|
||||||
|
func (n NoOpListener) Rebuild(tree ACLTree) {}
|
||||||
|
|
||||||
|
type RWLocker interface {
|
||||||
|
sync.Locker
|
||||||
|
RLock()
|
||||||
|
RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrNoCommonSnapshot = errors.New("trees doesn't have a common snapshot")
|
||||||
|
|
||||||
|
type ACLTree interface {
|
||||||
|
RWLocker
|
||||||
|
ID() string
|
||||||
|
Header() *treepb.TreeHeader
|
||||||
|
ACLState() *ACLState
|
||||||
|
AddContent(ctx context.Context, f func(builder ChangeBuilder) error) (*aclpb.RawChange, error)
|
||||||
|
AddRawChanges(ctx context.Context, changes ...*aclpb.RawChange) (AddResult, error)
|
||||||
|
Heads() []string
|
||||||
|
Root() *Change
|
||||||
|
Iterate(func(change *Change) bool)
|
||||||
|
IterateFrom(string, func(change *Change) bool)
|
||||||
|
HasChange(string) bool
|
||||||
|
SnapshotPath() []string
|
||||||
|
ChangesAfterCommonSnapshot(snapshotPath []string) ([]*aclpb.RawChange, error)
|
||||||
|
Storage() treestorage.TreeStorage
|
||||||
|
DebugDump() (string, error)
|
||||||
|
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type aclTree struct {
|
||||||
|
treeStorage treestorage.TreeStorage
|
||||||
|
accountData *account.AccountData
|
||||||
|
updateListener TreeUpdateListener
|
||||||
|
|
||||||
|
id string
|
||||||
|
header *treepb.TreeHeader
|
||||||
|
tree *Tree
|
||||||
|
aclState *ACLState
|
||||||
|
|
||||||
|
treeBuilder *treeBuilder
|
||||||
|
aclStateBuilder *aclStateBuilder
|
||||||
|
changeBuilder *changeBuilder
|
||||||
|
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildACLTreeWithIdentity(
|
||||||
|
t treestorage.TreeStorage,
|
||||||
|
acc *account.AccountData,
|
||||||
|
listener TreeUpdateListener) (ACLTree, error) {
|
||||||
|
treeBuilder := newTreeBuilder(t, acc.Decoder)
|
||||||
|
aclStateBuilder := newACLStateBuilderWithIdentity(acc.Decoder, acc)
|
||||||
|
changeBuilder := newChangeBuilder()
|
||||||
|
|
||||||
|
aclTree := &aclTree{
|
||||||
|
treeStorage: t,
|
||||||
|
accountData: acc,
|
||||||
|
tree: nil,
|
||||||
|
aclState: nil,
|
||||||
|
treeBuilder: treeBuilder,
|
||||||
|
aclStateBuilder: aclStateBuilder,
|
||||||
|
changeBuilder: changeBuilder,
|
||||||
|
updateListener: listener,
|
||||||
|
}
|
||||||
|
err := aclTree.rebuildFromStorage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = aclTree.removeOrphans()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = t.SetHeads(aclTree.Heads())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aclTree.id, err = t.TreeID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aclTree.header, err = t.Header()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.Rebuild(aclTree)
|
||||||
|
|
||||||
|
return aclTree, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildACLTree(t treestorage.TreeStorage) {
|
||||||
|
// TODO: Add logic for building without identity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) removeOrphans() error {
|
||||||
|
// removing attached or invalid orphans
|
||||||
|
var toRemove []string
|
||||||
|
|
||||||
|
orphans, err := a.treeStorage.Orphans()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, orphan := range orphans {
|
||||||
|
if _, exists := a.tree.attached[orphan]; exists {
|
||||||
|
toRemove = append(toRemove, orphan)
|
||||||
|
}
|
||||||
|
if _, exists := a.tree.invalidChanges[orphan]; exists {
|
||||||
|
toRemove = append(toRemove, orphan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.treeStorage.RemoveOrphans(toRemove...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) rebuildFromStorage() (err error) {
|
||||||
|
a.treeBuilder.Init()
|
||||||
|
|
||||||
|
a.tree, err = a.treeBuilder.Build(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.aclStateBuilder.Init(a.tree)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.aclState, err = a.aclStateBuilder.Build()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) ID() string {
|
||||||
|
return a.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) Header() *treepb.TreeHeader {
|
||||||
|
return a.header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) ACLState() *ACLState {
|
||||||
|
return a.aclState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) Storage() treestorage.TreeStorage {
|
||||||
|
return a.treeStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) AddContent(ctx context.Context, build func(builder ChangeBuilder) error) (*aclpb.RawChange, error) {
|
||||||
|
if a.accountData == nil {
|
||||||
|
return nil, ErrTreeWithoutIdentity
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// 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.tree, 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.tree.AddFast(ch)
|
||||||
|
rawCh := &aclpb.RawChange{
|
||||||
|
Payload: marshalled,
|
||||||
|
Signature: ch.Signature(),
|
||||||
|
Id: ch.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.treeStorage.AddRawChange(rawCh)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.treeStorage.SetHeads([]string{ch.Id})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rawCh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) AddRawChanges(ctx context.Context, rawChanges ...*aclpb.RawChange) (AddResult, error) {
|
||||||
|
// TODO: make proper error handling, because there are a lot of corner cases where this will break
|
||||||
|
var err error
|
||||||
|
var mode Mode
|
||||||
|
|
||||||
|
var changes []*Change // TODO: = addChangesBuf[:0] ...
|
||||||
|
for _, ch := range rawChanges {
|
||||||
|
change, err := NewFromRawChange(ch)
|
||||||
|
// TODO: think what if we will have incorrect signatures on rawChanges, how everything will work
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
changes = append(changes, change)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.removeOrphans()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.treeStorage.SetHeads(a.tree.Heads())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mode {
|
||||||
|
case Append:
|
||||||
|
a.updateListener.Update(a)
|
||||||
|
case Rebuild:
|
||||||
|
a.updateListener.Rebuild(a)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
getAddedChanges := func() []*aclpb.RawChange {
|
||||||
|
var added []*aclpb.RawChange
|
||||||
|
for _, ch := range rawChanges {
|
||||||
|
if _, exists := a.tree.attached[ch.Id]; exists {
|
||||||
|
added = append(added, ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return added
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ch := range changes {
|
||||||
|
err = a.treeStorage.AddChange(ch)
|
||||||
|
if err != nil {
|
||||||
|
return AddResult{}, err
|
||||||
|
}
|
||||||
|
err = a.treeStorage.AddOrphans(ch.Id)
|
||||||
|
if err != nil {
|
||||||
|
return AddResult{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prevHeads := a.tree.Heads()
|
||||||
|
mode = a.tree.Add(changes...)
|
||||||
|
switch mode {
|
||||||
|
case Nothing:
|
||||||
|
return AddResult{
|
||||||
|
OldHeads: prevHeads,
|
||||||
|
Heads: prevHeads,
|
||||||
|
Summary: AddResultSummaryNothing,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case Rebuild:
|
||||||
|
err = a.rebuildFromStorage()
|
||||||
|
if err != nil {
|
||||||
|
return AddResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddResult{
|
||||||
|
OldHeads: prevHeads,
|
||||||
|
Heads: a.tree.Heads(),
|
||||||
|
Added: getAddedChanges(),
|
||||||
|
Summary: AddResultSummaryRebuild,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
// 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
|
||||||
|
a.aclState, err = a.aclStateBuilder.Build()
|
||||||
|
if err != nil {
|
||||||
|
return AddResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddResult{
|
||||||
|
OldHeads: prevHeads,
|
||||||
|
Heads: a.tree.Heads(),
|
||||||
|
Added: getAddedChanges(),
|
||||||
|
Summary: AddResultSummaryAppend,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) Iterate(f func(change *Change) bool) {
|
||||||
|
a.tree.Iterate(a.tree.RootId(), f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) IterateFrom(s string, f func(change *Change) bool) {
|
||||||
|
a.tree.Iterate(s, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) HasChange(s string) bool {
|
||||||
|
_, attachedExists := a.tree.attached[s]
|
||||||
|
_, unattachedExists := a.tree.unAttached[s]
|
||||||
|
_, invalidExists := a.tree.invalidChanges[s]
|
||||||
|
return attachedExists || unattachedExists || invalidExists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) Heads() []string {
|
||||||
|
return a.tree.Heads()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) Root() *Change {
|
||||||
|
return a.tree.Root()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) SnapshotPath() []string {
|
||||||
|
// TODO: think about caching this
|
||||||
|
|
||||||
|
var path []string
|
||||||
|
// TODO: think that the user may have not all of the snapshots locally
|
||||||
|
currentSnapshotId := a.tree.RootId()
|
||||||
|
for currentSnapshotId != "" {
|
||||||
|
sn, err := a.treeBuilder.loadChange(currentSnapshotId)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
path = append(path, currentSnapshotId)
|
||||||
|
currentSnapshotId = sn.SnapshotId
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) ChangesAfterCommonSnapshot(theirPath []string) ([]*aclpb.RawChange, error) {
|
||||||
|
// TODO: think about when the clients will have their full acl tree and thus full snapshots
|
||||||
|
// but no changes after some of the snapshots
|
||||||
|
|
||||||
|
var (
|
||||||
|
isNewDocument = len(theirPath) == 0
|
||||||
|
ourPath = a.SnapshotPath()
|
||||||
|
// by default returning everything we have
|
||||||
|
commonSnapshot = ourPath[len(ourPath)-1] // TODO: root snapshot, probably it is better to have a specific method in treestorage
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// if this is non-empty request
|
||||||
|
if !isNewDocument {
|
||||||
|
commonSnapshot, err = a.commonSnapshotForTwoPaths(ourPath, theirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var rawChanges []*aclpb.RawChange
|
||||||
|
// using custom load function to skip verification step and save raw changes
|
||||||
|
load := func(id string) (*Change, error) {
|
||||||
|
raw, err := a.treeStorage.GetChange(context.Background(), id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
aclChange, err := a.treeBuilder.makeUnverifiedACLChange(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := NewChange(id, aclChange)
|
||||||
|
rawChanges = append(rawChanges, raw)
|
||||||
|
return ch, nil
|
||||||
|
}
|
||||||
|
// we presume that we have everything after the common snapshot, though this may not be the case in case of clients and only ACL tree changes
|
||||||
|
log.With(
|
||||||
|
zap.Strings("heads", a.tree.Heads()),
|
||||||
|
zap.String("breakpoint", commonSnapshot),
|
||||||
|
zap.String("id", a.id)).
|
||||||
|
Debug("getting all changes from common snapshot")
|
||||||
|
_, err = a.treeBuilder.dfs(a.tree.Heads(), commonSnapshot, load)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isNewDocument {
|
||||||
|
// adding snapshot to raw changes
|
||||||
|
_, err = load(commonSnapshot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.With(
|
||||||
|
zap.Int("len(changes)", len(rawChanges)),
|
||||||
|
zap.String("id", a.id)).
|
||||||
|
Debug("returning all changes after common snapshot")
|
||||||
|
|
||||||
|
return rawChanges, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) DebugDump() (string, error) {
|
||||||
|
return a.tree.Graph(ACLDescriptionParser)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *aclTree) commonSnapshotForTwoPaths(ourPath []string, theirPath []string) (string, error) {
|
||||||
|
var i int
|
||||||
|
var j int
|
||||||
|
log.With(zap.Strings("our path", ourPath), zap.Strings("their path", theirPath)).
|
||||||
|
Debug("finding common snapshot for two paths")
|
||||||
|
OuterLoop:
|
||||||
|
// find starting point from the right
|
||||||
|
for i = len(ourPath) - 1; i >= 0; i-- {
|
||||||
|
for j = len(theirPath) - 1; j >= 0; j-- {
|
||||||
|
// most likely there would be only one comparison, because mostly the snapshot path will start from the root for nodes
|
||||||
|
if ourPath[i] == theirPath[j] {
|
||||||
|
break OuterLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i < 0 || j < 0 {
|
||||||
|
return "", ErrNoCommonSnapshot
|
||||||
|
}
|
||||||
|
// find last common element of the sequence moving from right to left
|
||||||
|
for i >= 0 && j >= 0 {
|
||||||
|
if ourPath[i] == theirPath[j] {
|
||||||
|
i--
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ourPath[i+1], nil
|
||||||
|
}
|
||||||
@ -22,12 +22,18 @@ type Change struct {
|
|||||||
Id string
|
Id string
|
||||||
SnapshotId string
|
SnapshotId string
|
||||||
IsSnapshot bool
|
IsSnapshot bool
|
||||||
DecryptedChange []byte
|
DecryptedChange []byte // TODO: check if we need it
|
||||||
|
DecryptedModel interface{}
|
||||||
|
|
||||||
Content *aclpb.Change
|
Content *aclpb.Change
|
||||||
Sign []byte
|
Sign []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch *Change) ProtoChange() *aclpb.ACLChange {
|
||||||
|
//TODO implement me
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
func (ch *Change) DecryptContents(key *symmetric.Key) error {
|
func (ch *Change) DecryptContents(key *symmetric.Key) error {
|
||||||
// if the document is already decrypted
|
// if the document is already decrypted
|
||||||
if ch.Content.CurrentReadKeyHash == 0 {
|
if ch.Content.CurrentReadKeyHash == 0 {
|
||||||
@ -65,10 +71,6 @@ func NewChange(id string, ch *aclpb.Change) *Change {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch *Change) ProtoChange() *aclpb.Change {
|
|
||||||
return ch.Content
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ch *Change) DecryptedChangeContent() []byte {
|
func (ch *Change) DecryptedChangeContent() []byte {
|
||||||
return ch.DecryptedChange
|
return ch.DecryptedChange
|
||||||
}
|
}
|
||||||
|
|||||||
163
pkg/acl/tree/changebuilder.go
Normal file
163
pkg/acl/tree/changebuilder.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
|
||||||
|
"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/encryptionkey"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
"hash/fnv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MarshalledChange = []byte
|
||||||
|
|
||||||
|
type ACLChangeBuilder interface {
|
||||||
|
UserAdd(identity string, encryptionKey encryptionkey.PubKey, permissions aclpb.ACLChangeUserPermissions) error
|
||||||
|
AddId(id string) // TODO: this is only for testing
|
||||||
|
SetMakeSnapshot(bool) // TODO: who should decide this? probably ACLTree so we can delete it
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeBuilder interface {
|
||||||
|
ACLChangeBuilder
|
||||||
|
AddChangeContent(marshaler proto.Marshaler) // user code should be responsible for making regular snapshots
|
||||||
|
}
|
||||||
|
|
||||||
|
type changeBuilder struct {
|
||||||
|
aclState *ACLState
|
||||||
|
tree *Tree
|
||||||
|
acc *account.AccountData
|
||||||
|
|
||||||
|
aclData *aclpb.ACLChangeACLData
|
||||||
|
changeContent proto.Marshaler
|
||||||
|
id string
|
||||||
|
makeSnapshot bool
|
||||||
|
readKey *symmetric.Key
|
||||||
|
readKeyHash uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newChangeBuilder() *changeBuilder {
|
||||||
|
return &changeBuilder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *changeBuilder) Init(state *ACLState, tree *Tree, acc *account.AccountData) {
|
||||||
|
c.aclState = state
|
||||||
|
c.tree = tree
|
||||||
|
c.acc = acc
|
||||||
|
|
||||||
|
c.aclData = &aclpb.ACLChangeACLData{}
|
||||||
|
// setting read key for further encryption etc
|
||||||
|
if state.currentReadKeyHash == 0 {
|
||||||
|
c.readKey, _ = symmetric.NewRandom()
|
||||||
|
|
||||||
|
hasher := fnv.New64()
|
||||||
|
hasher.Write(c.readKey.Bytes())
|
||||||
|
c.readKeyHash = hasher.Sum64()
|
||||||
|
} else {
|
||||||
|
c.readKey = c.aclState.userReadKeys[c.aclState.currentReadKeyHash]
|
||||||
|
c.readKeyHash = c.aclState.currentReadKeyHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *changeBuilder) AddId(id string) {
|
||||||
|
c.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *changeBuilder) SetMakeSnapshot(b bool) {
|
||||||
|
c.makeSnapshot = b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *changeBuilder) UserAdd(identity string, encryptionKey encryptionkey.PubKey, permissions aclpb.ACLChangeUserPermissions) error {
|
||||||
|
var allKeys []*symmetric.Key
|
||||||
|
if c.aclState.currentReadKeyHash != 0 {
|
||||||
|
for _, key := range c.aclState.userReadKeys {
|
||||||
|
allKeys = append(allKeys, key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
allKeys = append(allKeys, c.readKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
var encryptedKeys [][]byte
|
||||||
|
for _, k := range allKeys {
|
||||||
|
res, err := encryptionKey.Encrypt(k.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedKeys = append(encryptedKeys, res)
|
||||||
|
}
|
||||||
|
rawKey, err := encryptionKey.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ch := &aclpb.ACLChangeACLContentValue{
|
||||||
|
Value: &aclpb.ACLChangeACLContentValueValueOfUserAdd{
|
||||||
|
UserAdd: &aclpb.ACLChangeUserAdd{
|
||||||
|
Identity: identity,
|
||||||
|
EncryptionKey: rawKey,
|
||||||
|
EncryptedReadKeys: encryptedKeys,
|
||||||
|
Permissions: permissions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.aclData.AclContent = append(c.aclData.AclContent, ch)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *changeBuilder) BuildAndApply() (*Change, []byte, error) {
|
||||||
|
aclChange := &aclpb.ACLChange{
|
||||||
|
TreeHeadIds: c.tree.Heads(),
|
||||||
|
AclHeadIds: c.tree.ACLHeads(),
|
||||||
|
SnapshotBaseId: c.tree.RootId(),
|
||||||
|
AclData: c.aclData,
|
||||||
|
CurrentReadKeyHash: c.readKeyHash,
|
||||||
|
Timestamp: int64(time.Now().Nanosecond()),
|
||||||
|
Identity: c.acc.Identity,
|
||||||
|
}
|
||||||
|
err := c.aclState.applyChange(aclChange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.makeSnapshot {
|
||||||
|
c.aclData.AclSnapshot = c.aclState.makeSnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshalled []byte
|
||||||
|
if c.changeContent != nil {
|
||||||
|
marshalled, err = c.changeContent.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypted, err := c.aclState.userReadKeys[c.aclState.currentReadKeyHash].
|
||||||
|
Encrypt(marshalled)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
aclChange.ChangesData = encrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
fullMarshalledChange, err := proto.Marshal(aclChange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
signature, err := c.acc.SignKey.Sign(fullMarshalledChange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
id, err := cid.NewCIDFromBytes(fullMarshalledChange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ch := NewChange(id, aclChange)
|
||||||
|
ch.DecryptedDocumentChange = marshalled
|
||||||
|
ch.Sign = signature
|
||||||
|
|
||||||
|
return ch, fullMarshalledChange, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *changeBuilder) AddChangeContent(marshaler proto.Marshaler) {
|
||||||
|
c.changeContent = marshaler
|
||||||
|
}
|
||||||
45
pkg/acl/tree/descriptionparser.go
Normal file
45
pkg/acl/tree/descriptionparser.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DescriptionParser interface {
|
||||||
|
ParseChange(*Change) ([]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ACLDescriptionParser = aclDescriptionParser{}
|
||||||
|
|
||||||
|
type aclDescriptionParser struct{}
|
||||||
|
|
||||||
|
func (a aclDescriptionParser) ParseChange(changeWrapper *Change) (res []string, err error) {
|
||||||
|
change := changeWrapper.Content
|
||||||
|
aclData := &aclpb.ACLChangeACLData{}
|
||||||
|
|
||||||
|
if changeWrapper.DecryptedModel != nil {
|
||||||
|
aclData = changeWrapper.DecryptedModel.(*aclpb.ACLChangeACLData)
|
||||||
|
} else {
|
||||||
|
err = proto.Unmarshal(change.ChangesData, aclData)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var chSymbs []string
|
||||||
|
for _, chc := range aclData.AclContent {
|
||||||
|
tp := fmt.Sprintf("%T", chc.Value)
|
||||||
|
tp = strings.Replace(tp, "ACLChangeACLContentValueValueOf", "", 1)
|
||||||
|
res := ""
|
||||||
|
for _, ts := range tp {
|
||||||
|
if unicode.IsUpper(ts) {
|
||||||
|
res += string(ts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chSymbs = append(chSymbs, res)
|
||||||
|
}
|
||||||
|
return chSymbs, nil
|
||||||
|
}
|
||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
|
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -289,34 +288,6 @@ func (t *Tree) updateHeads() {
|
|||||||
sort.Strings(t.metaHeadIds)
|
sort.Strings(t.metaHeadIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tree) ACLHeads() []string {
|
|
||||||
var aclTreeHeads []string
|
|
||||||
for _, head := range t.Heads() {
|
|
||||||
if slice.FindPos(aclTreeHeads, head) != -1 { // do not scan known heads
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
precedingHeads := t.getPrecedingACLHeads(head)
|
|
||||||
|
|
||||||
for _, aclHead := range precedingHeads {
|
|
||||||
if slice.FindPos(aclTreeHeads, aclHead) != -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
aclTreeHeads = append(aclTreeHeads, aclHead)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return aclTreeHeads
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) getPrecedingACLHeads(head string) []string {
|
|
||||||
headChange := t.attached[head]
|
|
||||||
|
|
||||||
if headChange.Content.GetAclData() != nil {
|
|
||||||
return []string{head}
|
|
||||||
} else {
|
|
||||||
return headChange.Content.AclHeadIds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) iterate(start *Change, f func(c *Change) (isContinue bool)) {
|
func (t *Tree) iterate(start *Change, f func(c *Change) (isContinue bool)) {
|
||||||
it := newIterator()
|
it := newIterator()
|
||||||
defer freeIterator(it)
|
defer freeIterator(it)
|
||||||
|
|||||||
@ -223,8 +223,8 @@ func (tb *treeBuilder) makeVerifiedChange(change *aclpb.RawChange) (aclChange *a
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tb *treeBuilder) makeUnverifiedACLChange(change *aclpb.RawChange) (aclChange *aclpb.ACLChange, err error) {
|
func (tb *treeBuilder) makeUnverifiedACLChange(change *aclpb.RawChange) (aclChange *aclpb.Change, err error) {
|
||||||
aclChange = new(aclpb.ACLChange)
|
aclChange = new(aclpb.Change)
|
||||||
err = proto.Unmarshal(change.Payload, aclChange)
|
err = proto.Unmarshal(change.Payload, aclChange)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
11
pkg/acl/tree/treegraph.go
Normal file
11
pkg/acl/tree/treegraph.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//go:build ((!linux && !darwin) || android || ios || nographviz) && !amd64
|
||||||
|
// +build !linux,!darwin android ios nographviz
|
||||||
|
// +build !amd64
|
||||||
|
|
||||||
|
package tree
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func (t *Tree) Graph(parser DescriptionParser) (data []string, err error) {
|
||||||
|
return "", fmt.Errorf("not supported")
|
||||||
|
}
|
||||||
121
pkg/acl/tree/treegraph_nix.go
Normal file
121
pkg/acl/tree/treegraph_nix.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
//go:build (linux || darwin) && !android && !ios && !nographviz && (amd64 || arm64)
|
||||||
|
// +build linux darwin
|
||||||
|
// +build !android
|
||||||
|
// +build !ios
|
||||||
|
// +build !nographviz
|
||||||
|
// +build amd64 arm64
|
||||||
|
|
||||||
|
package tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/goccy/go-graphviz"
|
||||||
|
"github.com/goccy/go-graphviz/cgraph"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *Tree) Graph(parser DescriptionParser) (data string, err error) {
|
||||||
|
var order = make(map[string]string)
|
||||||
|
var seq = 0
|
||||||
|
t.Iterate(t.RootId(), func(c *Change) (isContinue bool) {
|
||||||
|
v := order[c.Id]
|
||||||
|
if v == "" {
|
||||||
|
order[c.Id] = fmt.Sprint(seq)
|
||||||
|
} else {
|
||||||
|
order[c.Id] = fmt.Sprintf("%s,%d", v, seq)
|
||||||
|
}
|
||||||
|
seq++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
g := graphviz.New()
|
||||||
|
defer g.Close()
|
||||||
|
graph, err := g.Graph()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = graph.Close()
|
||||||
|
}()
|
||||||
|
var nodes = make(map[string]*cgraph.Node)
|
||||||
|
var addChange = func(c *Change) error {
|
||||||
|
n, e := graph.CreateNode(c.Id)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
n.SetStyle(cgraph.FilledNodeStyle)
|
||||||
|
nodes[c.Id] = n
|
||||||
|
ord := order[c.Id]
|
||||||
|
if ord == "" {
|
||||||
|
ord = "miss"
|
||||||
|
}
|
||||||
|
chSymbs, err := parser.ParseChange(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
shortId := c.Id
|
||||||
|
label := fmt.Sprintf("Id: %s\nOrd: %s\nTime: %s\nChanges: %s\n",
|
||||||
|
shortId,
|
||||||
|
ord,
|
||||||
|
time.Unix(c.Content.Timestamp, 0).Format("02.01.06 15:04:05"),
|
||||||
|
strings.Join(chSymbs, ","),
|
||||||
|
)
|
||||||
|
n.SetLabel(label)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, c := range t.attached {
|
||||||
|
if err = addChange(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range t.unAttached {
|
||||||
|
if err = addChange(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var getNode = func(id string) (*cgraph.Node, error) {
|
||||||
|
if n, ok := nodes[id]; ok {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
n, err := graph.CreateNode(fmt.Sprintf("%s: not in Tree", id))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodes[id] = n
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
var addLinks = func(c *Change) error {
|
||||||
|
for _, prevId := range c.PreviousIds {
|
||||||
|
self, e := getNode(c.Id)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
prev, e := getNode(prevId)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
_, e = graph.CreateEdge("", self, prev)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, c := range t.attached {
|
||||||
|
if err = addLinks(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range t.unAttached {
|
||||||
|
if err = addLinks(c); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err = g.Render(graph, "dot", &buf); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
158
pkg/acl/tree/treeiterator.go
Normal file
158
pkg/acl/tree/treeiterator.go
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
package tree
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
var itPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &iterator{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIterator() *iterator {
|
||||||
|
return itPool.Get().(*iterator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func freeIterator(i *iterator) {
|
||||||
|
itPool.Put(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
type iterator struct {
|
||||||
|
compBuf []*Change
|
||||||
|
queue []*Change
|
||||||
|
doneMap map[*Change]struct{}
|
||||||
|
breakpoint *Change
|
||||||
|
f func(c *Change) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iterator) iterateSkip(start *Change, skipBefore *Change, f func(c *Change) (isContinue bool)) {
|
||||||
|
skipping := true
|
||||||
|
i.iterate(start, func(c *Change) (isContinue bool) {
|
||||||
|
if skipping && c != skipBefore {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
skipping = false
|
||||||
|
return f(c)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iterator) iterate(start *Change, f func(c *Change) (isContinue bool)) {
|
||||||
|
if start == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// reset
|
||||||
|
i.queue = i.queue[:0]
|
||||||
|
i.compBuf = i.compBuf[:0]
|
||||||
|
i.doneMap = make(map[*Change]struct{})
|
||||||
|
i.queue = append(i.queue, start)
|
||||||
|
i.breakpoint = nil
|
||||||
|
i.f = f
|
||||||
|
|
||||||
|
for len(i.queue) > 0 {
|
||||||
|
c := i.queue[0]
|
||||||
|
i.queue = i.queue[1:]
|
||||||
|
nl := len(c.Next)
|
||||||
|
if nl == 1 {
|
||||||
|
if !i.iterateLin(c) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i.breakpoint != nil {
|
||||||
|
i.toQueue(i.breakpoint)
|
||||||
|
i.breakpoint = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, done := i.doneMap[c]
|
||||||
|
if !done {
|
||||||
|
if !f(c) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i.doneMap[c] = struct{}{}
|
||||||
|
}
|
||||||
|
if nl != 0 {
|
||||||
|
for _, next := range c.Next {
|
||||||
|
i.toQueue(next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iterator) iterateLin(c *Change) bool {
|
||||||
|
for len(c.Next) == 1 {
|
||||||
|
_, done := i.doneMap[c]
|
||||||
|
if !done {
|
||||||
|
if !i.f(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
i.doneMap[c] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
c = c.Next[0]
|
||||||
|
if len(c.PreviousIds) > 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(c.Next) == 0 && len(c.PreviousIds) <= 1 {
|
||||||
|
if !i.f(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
i.doneMap[c] = struct{}{}
|
||||||
|
} else {
|
||||||
|
i.breakpoint = c
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iterator) comp(c1, c2 *Change) uint8 {
|
||||||
|
if c1.Id == c2.Id {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i.compBuf = i.compBuf[:0]
|
||||||
|
i.compBuf = append(i.compBuf, c1.Next...)
|
||||||
|
var uniq = make(map[*Change]struct{})
|
||||||
|
var appendUniqueToBuf = func(next []*Change) {
|
||||||
|
for _, n := range next {
|
||||||
|
if _, ok := uniq[n]; !ok {
|
||||||
|
i.compBuf = append(i.compBuf, n)
|
||||||
|
uniq[n] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var used int
|
||||||
|
for len(i.compBuf)-used > 0 {
|
||||||
|
l := len(i.compBuf) - used
|
||||||
|
for _, n := range i.compBuf[used:] {
|
||||||
|
delete(uniq, n)
|
||||||
|
if n.Id == c2.Id {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
appendUniqueToBuf(n.Next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
used += l
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *iterator) toQueue(c *Change) {
|
||||||
|
var pos = -1
|
||||||
|
For:
|
||||||
|
for idx, qc := range i.queue {
|
||||||
|
switch i.comp(c, qc) {
|
||||||
|
// exists
|
||||||
|
case 0:
|
||||||
|
return
|
||||||
|
//
|
||||||
|
case 1:
|
||||||
|
pos = idx
|
||||||
|
break For
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pos == -1 {
|
||||||
|
i.queue = append(i.queue, c)
|
||||||
|
} else if pos == 0 {
|
||||||
|
i.queue = append([]*Change{c}, i.queue...)
|
||||||
|
} else {
|
||||||
|
i.queue = append(i.queue[:pos], append([]*Change{c}, i.queue[pos:]...)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user