Add acltree in new package

This commit is contained in:
mcrakhman 2022-08-10 11:52:45 +02:00 committed by Mikhail Iudin
parent 307f517ee5
commit 110ec01b0c
No known key found for this signature in database
GPG Key ID: FAAAA8BAABDFF1C0
11 changed files with 1063 additions and 40 deletions

View File

@ -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{}
if changeWrapper.DecryptedModel != nil {
aclData = changeWrapper.DecryptedModel.(*aclpb.ACLChangeACLData)
} else {
err = proto.Unmarshal(change.ChangesData, aclData)
if err != nil {
return
}
changeWrapper.DecryptedModel = aclData
}
defer func() {
if err != nil {

View 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
View 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
}

View File

@ -22,12 +22,18 @@ type Change struct {
Id string
SnapshotId string
IsSnapshot bool
DecryptedChange []byte
DecryptedChange []byte // TODO: check if we need it
DecryptedModel interface{}
Content *aclpb.Change
Sign []byte
}
func (ch *Change) ProtoChange() *aclpb.ACLChange {
//TODO implement me
panic("implement me")
}
func (ch *Change) DecryptContents(key *symmetric.Key) error {
// if the document is already decrypted
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 {
return ch.DecryptedChange
}

View 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
}

View 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
}

View File

@ -4,7 +4,6 @@ import (
"bytes"
"crypto/md5"
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
"sort"
)
@ -289,34 +288,6 @@ func (t *Tree) updateHeads() {
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)) {
it := newIterator()
defer freeIterator(it)

View File

@ -223,8 +223,8 @@ func (tb *treeBuilder) makeVerifiedChange(change *aclpb.RawChange) (aclChange *a
return
}
func (tb *treeBuilder) makeUnverifiedACLChange(change *aclpb.RawChange) (aclChange *aclpb.ACLChange, err error) {
aclChange = new(aclpb.ACLChange)
func (tb *treeBuilder) makeUnverifiedACLChange(change *aclpb.RawChange) (aclChange *aclpb.Change, err error) {
aclChange = new(aclpb.Change)
err = proto.Unmarshal(change.Payload, aclChange)
return
}

11
pkg/acl/tree/treegraph.go Normal file
View 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")
}

View 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
}

View 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:]...)...)
}
}