Add tree updater to ACL tree

This commit is contained in:
mcrakhman 2022-07-11 11:28:58 +02:00
parent cc802ebc69
commit 5b9d403b7d
No known key found for this signature in database
GPG Key ID: DED12CFEF5B8396B
11 changed files with 478 additions and 398 deletions

View File

@ -23,19 +23,26 @@ type AddResult struct {
Summary AddResultSummary
}
type TreeUpdateListener interface {
Update(tree ACLTree)
Rebuild(tree ACLTree)
}
type ACLTree interface {
ACLState() *ACLState
AddContent(f func(builder ChangeBuilder)) (*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
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
@ -50,7 +57,10 @@ type aclTree struct {
sync.Mutex
}
func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) {
func BuildACLTree(
t thread.Thread,
acc *account.AccountData,
listener TreeUpdateListener) (ACLTree, error) {
decoder := keys.NewEd25519Decoder()
aclTreeBuilder := newACLTreeBuilder(t, decoder)
treeBuilder := newTreeBuilder(t, decoder)
@ -68,6 +78,7 @@ func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) {
aclStateBuilder: aclStateBuilder,
snapshotValidator: snapshotValidator,
changeBuilder: changeBuilder,
updateListener: listener,
}
err := aclTree.rebuildFromThread(false)
if err != nil {
@ -183,6 +194,7 @@ func (a *aclTree) AddContent(build func(builder ChangeBuilder)) (*Change, error)
}
a.thread.SetHeads([]string{ch.Id})
a.updateListener.Update(a)
return ch, nil
}
@ -190,8 +202,8 @@ func (a *aclTree) AddChanges(changes ...*Change) (AddResult, error) {
a.Lock()
defer a.Unlock()
// TODO: make proper error handling, because there are a lot of corner cases where this will break
var aclChanges []*Change
var err error
var mode Mode
defer func() {
if err != nil {
@ -209,13 +221,17 @@ func (a *aclTree) AddChanges(changes ...*Change) (AddResult, error) {
}
}
a.thread.RemoveOrphans(toRemove...)
switch mode {
case Append:
a.updateListener.Update(a)
case Rebuild:
a.updateListener.Rebuild(a)
default:
break
}
}()
for _, ch := range changes {
if ch.IsACLChange() {
aclChanges = append(aclChanges, ch)
break
}
err = a.thread.AddChange(ch)
if err != nil {
return AddResult{}, err
@ -224,7 +240,7 @@ func (a *aclTree) AddChanges(changes ...*Change) (AddResult, error) {
}
prevHeads := a.fullTree.Heads()
mode := a.fullTree.Add(changes...)
mode = a.fullTree.Add(changes...)
switch mode {
case Nothing:
return AddResult{
@ -284,3 +300,9 @@ func (a *aclTree) Heads() []string {
defer a.Unlock()
return a.fullTree.Heads()
}
func (a *aclTree) Root() *Change {
a.Lock()
defer a.Unlock()
return a.fullTree.Root()
}

View File

@ -1,9 +0,0 @@
package exampledocument
type DocumentState interface {
ApplyChange(change []byte, id string) (DocumentState, error)
}
type InitialStateProvider interface {
ProvideFromInitialChange(change []byte, id string) (DocumentState, error)
}

View File

@ -1,303 +0,0 @@
package exampledocument
import (
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/acltree"
"github.com/anytypeio/go-anytype-infrastructure-experiments/data/pb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/thread"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
"github.com/gogo/protobuf/proto"
)
type AccountData struct {
Identity string
SignKey threadmodels.SigningPrivKey
EncKey threadmodels.EncryptionPrivKey
}
type Document struct {
// TODO: ensure that every operation on Document is synchronized
thread threadmodels.Thread
stateProvider InitialStateProvider
accountData *AccountData
decoder threadmodels.SigningPubKeyDecoder
treeBuilder *acltree.treeBuilder
aclTreeBuilder *acltree.aclTreeBuilder
aclStateBuilder *acltree.aclStateBuilder
snapshotValidator *acltree.snapshotValidator
docStateBuilder *acltree.documentStateBuilder
docContext *acltree.documentContext
}
type UpdateResult int
const (
UpdateResultNoAction UpdateResult = iota
UpdateResultAppend
UpdateResultRebuild
)
type CreateChangePayload struct {
ChangesData proto.Marshaler
ACLData *pb.ACLChangeACLData
Id string // TODO: this is just for testing, because id should be created automatically from content
}
func NewDocument(
thread threadmodels.Thread,
stateProvider InitialStateProvider,
accountData *AccountData) *Document {
decoder := threadmodels.NewEd25519Decoder()
return &Document{
thread: thread,
stateProvider: stateProvider,
accountData: accountData,
decoder: decoder,
aclTreeBuilder: acltree.newACLTreeBuilder(thread, decoder),
treeBuilder: acltree.newTreeBuilder(thread, decoder),
snapshotValidator: acltree.newSnapshotValidator(decoder, accountData),
aclStateBuilder: acltree.newACLStateBuilder(decoder, accountData),
docStateBuilder: acltree.newDocumentStateBuilder(stateProvider),
docContext: &acltree.documentContext{},
}
}
//sync layer -> Object cache -> document -> Update (..raw changes)
//client layer -> Object cache -> document -> CreateChange(...)
//
// smartblock -> CreateChange(payload)
// SmartTree iterate etc
func (d *Document) CreateChange(payload *CreateChangePayload) error {
// TODO: add snapshot creation logic
marshalled, err := payload.ChangesData.Marshal()
if err != nil {
return err
}
encrypted, err := d.docContext.aclState.userReadKeys[d.docContext.aclState.currentReadKeyHash].
Encrypt(marshalled)
if err != nil {
return err
}
aclChange := &pb.ACLChange{
TreeHeadIds: d.docContext.fullTree.Heads(),
AclHeadIds: d.getACLHeads(),
SnapshotBaseId: d.docContext.fullTree.RootId(),
AclData: payload.ACLData,
ChangesData: encrypted,
CurrentReadKeyHash: d.docContext.aclState.currentReadKeyHash,
Timestamp: 0,
Identity: d.accountData.Identity,
}
// TODO: add CID creation logic based on content
ch := acltree.NewChange(payload.Id, aclChange)
ch.DecryptedDocumentChange = marshalled
fullMarshalledChange, err := proto.Marshal(aclChange)
if err != nil {
return err
}
signature, err := d.accountData.SignKey.Sign(fullMarshalledChange)
if err != nil {
return err
}
if aclChange.AclData != nil {
// we can apply change right away without going through builder, because
err = d.docContext.aclState.ApplyChange(payload.Id, aclChange)
if err != nil {
return err
}
}
d.docContext.fullTree.AddFast(ch)
err = d.thread.AddChange(&thread.RawChange{
Payload: marshalled,
Signature: signature,
Id: payload.Id,
})
if err != nil {
return err
}
d.thread.SetHeads([]string{ch.Id})
d.thread.SetMaybeHeads([]string{ch.Id})
return nil
}
func (d *Document) Update(changes ...*thread.RawChange) (DocumentState, UpdateResult, error) {
var treeChanges []*acltree.Change
var foundACLChange bool
for _, ch := range changes {
aclChange, err := d.treeBuilder.makeVerifiedACLChange(ch)
if err != nil {
return nil, UpdateResultNoAction, fmt.Errorf("change with id %s is incorrect: %w", ch.Id, err)
}
treeChange := d.treeBuilder.changeCreator(ch.Id, aclChange)
treeChanges = append(treeChanges, treeChange)
// this already sets PossibleHeads to include new changes
// TODO: change this behaviour as non-obvious, because it is not evident from the interface
err = d.thread.AddChange(ch)
if err != nil {
return nil, UpdateResultNoAction, fmt.Errorf("change with id %s cannot be added: %w", ch.Id, err)
}
if treeChange.IsACLChange() {
foundACLChange = true
}
}
if foundACLChange {
res, err := d.Build()
return res, UpdateResultRebuild, err
}
prevHeads := d.docContext.fullTree.Heads()
mode := d.docContext.fullTree.Add(treeChanges...)
switch mode {
case acltree.Nothing:
return d.docContext.docState, UpdateResultNoAction, nil
case acltree.Rebuild:
res, err := d.Build()
return res, UpdateResultRebuild, err
default:
break
}
// TODO: we should still check if the user making those changes are able to write using "aclState"
// decrypting everything, because we have no new keys
for _, ch := range treeChanges {
if ch.Content.GetChangesData() != nil {
key, exists := d.docContext.aclState.userReadKeys[ch.Content.CurrentReadKeyHash]
if !exists {
err := fmt.Errorf("failed to find key with hash: %d", ch.Content.CurrentReadKeyHash)
return nil, UpdateResultNoAction, err
}
err := ch.DecryptContents(key)
if err != nil {
err = fmt.Errorf("failed to decrypt contents for hash: %d", ch.Content.CurrentReadKeyHash)
return nil, UpdateResultNoAction, err
}
}
}
// because for every new change we know it was after any of the previous heads
// each of previous heads must have same "Next" nodes
// so it doesn't matter which one we choose
// so we choose first one
newState, err := d.docStateBuilder.appendFrom(prevHeads[0], d.docContext.docState)
if err != nil {
res, _ := d.Build()
return res, UpdateResultRebuild, fmt.Errorf("could not add changes to state, rebuilded")
}
// setting all heads
d.thread.SetHeads(d.docContext.fullTree.Heads())
// this should be the entrypoint when we build the document
d.thread.SetMaybeHeads(d.docContext.fullTree.Heads())
return newState, UpdateResultAppend, nil
}
func (d *Document) Build() (DocumentState, error) {
return d.build(false)
}
// TODO: this should not be the responsibility of Document, move it somewhere else after testing
func (d *Document) getACLHeads() []string {
var aclTreeHeads []string
for _, head := range d.docContext.fullTree.Heads() {
if slice.FindPos(aclTreeHeads, head) != -1 { // do not scan known heads
continue
}
precedingHeads := d.getPrecedingACLHeads(head)
for _, aclHead := range precedingHeads {
if slice.FindPos(aclTreeHeads, aclHead) != -1 {
continue
}
aclTreeHeads = append(aclTreeHeads, aclHead)
}
}
return aclTreeHeads
}
func (d *Document) getPrecedingACLHeads(head string) []string {
headChange := d.docContext.fullTree.attached[head]
if headChange.Content.GetAclData() != nil {
return []string{head}
} else {
return headChange.Content.AclHeadIds
}
}
func (d *Document) build(fromStart bool) (DocumentState, error) {
d.treeBuilder.init()
d.aclTreeBuilder.init()
var err error
d.docContext.fullTree, err = d.treeBuilder.build(fromStart)
if err != nil {
return nil, err
}
// TODO: remove this from context as this is used only to validate snapshot
d.docContext.aclTree, err = d.aclTreeBuilder.build()
if err != nil {
return nil, err
}
if !fromStart {
err = d.snapshotValidator.init(d.docContext.aclTree)
if err != nil {
return nil, err
}
valid, err := d.snapshotValidator.validateSnapshot(d.docContext.fullTree.root)
if err != nil {
return nil, err
}
if !valid {
return d.build(true)
}
}
err = d.aclStateBuilder.init(d.docContext.fullTree)
if err != nil {
return nil, err
}
d.docContext.aclState, err = d.aclStateBuilder.build()
if err != nil {
return nil, err
}
// tree should be exposed
d.docStateBuilder.init(d.docContext.aclState, d.docContext.fullTree)
d.docContext.docState, err = d.docStateBuilder.build()
if err != nil {
return nil, err
}
// setting all heads
d.thread.SetHeads(d.docContext.fullTree.Heads())
// this should be the entrypoint when we build the document
d.thread.SetMaybeHeads(d.docContext.fullTree.Heads())
return d.docContext.docState, nil
}
func (d *Document) State() DocumentState {
return nil
}

View File

@ -1,68 +0,0 @@
package exampledocument
import (
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/data/pb"
"github.com/gogo/protobuf/proto"
)
// TestDocumentState -> testutils
// ThreadBuilder -> testutils
// move protos to test utils
type PlainTextDocumentState struct {
LastChangeId string
Text string
}
func NewPlainTextDocumentState(text string, id string) *PlainTextDocumentState {
return &PlainTextDocumentState{
LastChangeId: id,
Text: text,
}
}
func (p *PlainTextDocumentState) ApplyChange(change []byte, id string) (DocumentState, error) {
var changesData pb.PlainTextChangeData
err := proto.Unmarshal(change, &changesData)
if err != nil {
return nil, err
}
for _, content := range changesData.GetContent() {
err = p.applyChange(content)
if err != nil {
return nil, err
}
}
p.LastChangeId = id
return p, nil
}
func (p *PlainTextDocumentState) applyChange(ch *pb.PlainTextChangeContent) error {
switch {
case ch.GetTextAppend() != nil:
text := ch.GetTextAppend().GetText()
p.Text += "|" + text
}
return nil
}
type PlainTextDocumentStateProvider struct{}
func NewPlainTextDocumentStateProvider() *PlainTextDocumentStateProvider {
return &PlainTextDocumentStateProvider{}
}
func (p *PlainTextDocumentStateProvider) ProvideFromInitialChange(change []byte, id string) (DocumentState, error) {
var changesData pb.PlainTextChangeData
err := proto.Unmarshal(change, &changesData)
if err != nil {
return nil, err
}
if changesData.GetSnapshot() == nil {
return nil, fmt.Errorf("could not create state from empty snapshot")
}
return NewPlainTextDocumentState(changesData.GetSnapshot().GetText(), id), nil
}

View File

@ -0,0 +1,378 @@
package plaintextdocument
import (
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/acltree"
"github.com/anytypeio/go-anytype-infrastructure-experiments/thread"
)
type PlainTextDocument struct {
heads []string
aclTree acltree.ACLTree
state *DocumentState
}
func (p *PlainTextDocument) Update(tree acltree.ACLTree) {
var err error
defer func() {
if err != nil {
fmt.Println("rebuild has returned error:", err)
}
}()
prevHeads := p.heads
p.heads = tree.Heads()
startId := prevHeads[0]
tree.IterateFrom(startId, func(change *acltree.Change) (isContinue bool) {
if change.Id == startId {
return true
}
if change.DecryptedDocumentChange != nil {
p.state, err = p.state.ApplyChange(change.DecryptedDocumentChange, change.Id)
if err != nil {
return false
}
}
return true
})
}
func (p *PlainTextDocument) Rebuild(tree acltree.ACLTree) {
p.heads = tree.Heads()
var startId string
var err error
defer func() {
if err != nil {
fmt.Println("rebuild has returned error:", err)
}
}()
rootChange := tree.Root()
if rootChange.DecryptedDocumentChange == nil {
err = fmt.Errorf("root doesn't have decrypted change")
return
}
state, err := BuildDocumentStateFromChange(rootChange.DecryptedDocumentChange, rootChange.Id)
if err != nil {
return
}
startId = rootChange.Id
tree.Iterate(func(change *acltree.Change) (isContinue bool) {
if startId == change.Id {
return true
}
if change.DecryptedDocumentChange != nil {
state, err = state.ApplyChange(change.DecryptedDocumentChange, change.Id)
if err != nil {
return false
}
}
return true
})
if err != nil {
return
}
p.state = state
}
func NewPlainTextDocument(t thread.Thread, acc *account.AccountData) (*PlainTextDocument, error) {
tree, e
}
//
//type AccountData struct {
// Identity string
// SignKey threadmodels.SigningPrivKey
// EncKey threadmodels.EncryptionPrivKey
//}
//
//type Document struct {
// // TODO: ensure that every operation on Document is synchronized
// thread threadmodels.Thread
// stateProvider InitialStateProvider
// accountData *AccountData
// decoder threadmodels.SigningPubKeyDecoder
//
// treeBuilder *acltree.treeBuilder
// aclTreeBuilder *acltree.aclTreeBuilder
// aclStateBuilder *acltree.aclStateBuilder
// snapshotValidator *acltree.snapshotValidator
// docStateBuilder *acltree.documentStateBuilder
//
// docContext *acltree.documentContext
//}
//
//type UpdateResult int
//
//const (
// UpdateResultNoAction UpdateResult = iota
// UpdateResultAppend
// UpdateResultRebuild
//)
//
//type CreateChangePayload struct {
// ChangesData proto.Marshaler
// ACLData *pb.ACLChangeACLData
// Id string // TODO: this is just for testing, because id should be created automatically from content
//}
//
//func NewDocument(
// thread threadmodels.Thread,
// stateProvider InitialStateProvider,
// accountData *AccountData) *Document {
//
// decoder := threadmodels.NewEd25519Decoder()
// return &Document{
// thread: thread,
// stateProvider: stateProvider,
// accountData: accountData,
// decoder: decoder,
// aclTreeBuilder: acltree.newACLTreeBuilder(thread, decoder),
// treeBuilder: acltree.newTreeBuilder(thread, decoder),
// snapshotValidator: acltree.newSnapshotValidator(decoder, accountData),
// aclStateBuilder: acltree.newACLStateBuilder(decoder, accountData),
// docStateBuilder: acltree.newDocumentStateBuilder(stateProvider),
// docContext: &acltree.documentContext{},
// }
//}
//
////sync layer -> Object cache -> document -> Update (..raw changes)
////client layer -> Object cache -> document -> CreateChange(...)
////
//
//// smartblock -> CreateChange(payload)
//// SmartTree iterate etc
//func (d *Document) CreateChange(payload *CreateChangePayload) error {
// // TODO: add snapshot creation logic
// marshalled, err := payload.ChangesData.Marshal()
// if err != nil {
// return err
// }
//
// encrypted, err := d.docContext.aclState.userReadKeys[d.docContext.aclState.currentReadKeyHash].
// Encrypt(marshalled)
// if err != nil {
// return err
// }
//
// aclChange := &pb.ACLChange{
// TreeHeadIds: d.docContext.fullTree.Heads(),
// AclHeadIds: d.getACLHeads(),
// SnapshotBaseId: d.docContext.fullTree.RootId(),
// AclData: payload.ACLData,
// ChangesData: encrypted,
// CurrentReadKeyHash: d.docContext.aclState.currentReadKeyHash,
// Timestamp: 0,
// Identity: d.accountData.Identity,
// }
//
// // TODO: add CID creation logic based on content
// ch := acltree.NewChange(payload.Id, aclChange)
// ch.DecryptedDocumentChange = marshalled
//
// fullMarshalledChange, err := proto.Marshal(aclChange)
// if err != nil {
// return err
// }
// signature, err := d.accountData.SignKey.Sign(fullMarshalledChange)
// if err != nil {
// return err
// }
//
// if aclChange.AclData != nil {
// // we can apply change right away without going through builder, because
// err = d.docContext.aclState.ApplyChange(payload.Id, aclChange)
// if err != nil {
// return err
// }
// }
// d.docContext.fullTree.AddFast(ch)
//
// err = d.thread.AddChange(&thread.RawChange{
// Payload: marshalled,
// Signature: signature,
// Id: payload.Id,
// })
// if err != nil {
// return err
// }
//
// d.thread.SetHeads([]string{ch.Id})
// d.thread.SetMaybeHeads([]string{ch.Id})
//
// return nil
//}
//
//func (d *Document) Update(changes ...*thread.RawChange) (DocumentState, UpdateResult, error) {
// var treeChanges []*acltree.Change
//
// var foundACLChange bool
// for _, ch := range changes {
// aclChange, err := d.treeBuilder.makeVerifiedACLChange(ch)
// if err != nil {
// return nil, UpdateResultNoAction, fmt.Errorf("change with id %s is incorrect: %w", ch.Id, err)
// }
//
// treeChange := d.treeBuilder.changeCreator(ch.Id, aclChange)
// treeChanges = append(treeChanges, treeChange)
//
// // this already sets PossibleHeads to include new changes
// // TODO: change this behaviour as non-obvious, because it is not evident from the interface
// err = d.thread.AddChange(ch)
// if err != nil {
// return nil, UpdateResultNoAction, fmt.Errorf("change with id %s cannot be added: %w", ch.Id, err)
// }
// if treeChange.IsACLChange() {
// foundACLChange = true
// }
// }
//
// if foundACLChange {
// res, err := d.Build()
// return res, UpdateResultRebuild, err
// }
//
// prevHeads := d.docContext.fullTree.Heads()
// mode := d.docContext.fullTree.Add(treeChanges...)
// switch mode {
// case acltree.Nothing:
// return d.docContext.docState, UpdateResultNoAction, nil
// case acltree.Rebuild:
// res, err := d.Build()
// return res, UpdateResultRebuild, err
// default:
// break
// }
//
// // TODO: we should still check if the user making those changes are able to write using "aclState"
// // decrypting everything, because we have no new keys
// for _, ch := range treeChanges {
// if ch.Content.GetChangesData() != nil {
// key, exists := d.docContext.aclState.userReadKeys[ch.Content.CurrentReadKeyHash]
// if !exists {
// err := fmt.Errorf("failed to find key with hash: %d", ch.Content.CurrentReadKeyHash)
// return nil, UpdateResultNoAction, err
// }
//
// err := ch.DecryptContents(key)
// if err != nil {
// err = fmt.Errorf("failed to decrypt contents for hash: %d", ch.Content.CurrentReadKeyHash)
// return nil, UpdateResultNoAction, err
// }
// }
// }
//
// // because for every new change we know it was after any of the previous heads
// // each of previous heads must have same "Next" nodes
// // so it doesn't matter which one we choose
// // so we choose first one
// newState, err := d.docStateBuilder.appendFrom(prevHeads[0], d.docContext.docState)
// if err != nil {
// res, _ := d.Build()
// return res, UpdateResultRebuild, fmt.Errorf("could not add changes to state, rebuilded")
// }
//
// // setting all heads
// d.thread.SetHeads(d.docContext.fullTree.Heads())
// // this should be the entrypoint when we build the document
// d.thread.SetMaybeHeads(d.docContext.fullTree.Heads())
//
// return newState, UpdateResultAppend, nil
//}
//
//func (d *Document) Build() (DocumentState, error) {
// return d.build(false)
//}
//
//// TODO: this should not be the responsibility of Document, move it somewhere else after testing
//func (d *Document) getACLHeads() []string {
// var aclTreeHeads []string
// for _, head := range d.docContext.fullTree.Heads() {
// if slice.FindPos(aclTreeHeads, head) != -1 { // do not scan known heads
// continue
// }
// precedingHeads := d.getPrecedingACLHeads(head)
//
// for _, aclHead := range precedingHeads {
// if slice.FindPos(aclTreeHeads, aclHead) != -1 {
// continue
// }
// aclTreeHeads = append(aclTreeHeads, aclHead)
// }
// }
// return aclTreeHeads
//}
//
//func (d *Document) getPrecedingACLHeads(head string) []string {
// headChange := d.docContext.fullTree.attached[head]
//
// if headChange.Content.GetAclData() != nil {
// return []string{head}
// } else {
// return headChange.Content.AclHeadIds
// }
//}
//
//func (d *Document) build(fromStart bool) (DocumentState, error) {
// d.treeBuilder.init()
// d.aclTreeBuilder.init()
//
// var err error
// d.docContext.fullTree, err = d.treeBuilder.build(fromStart)
// if err != nil {
// return nil, err
// }
//
// // TODO: remove this from context as this is used only to validate snapshot
// d.docContext.aclTree, err = d.aclTreeBuilder.build()
// if err != nil {
// return nil, err
// }
//
// if !fromStart {
// err = d.snapshotValidator.init(d.docContext.aclTree)
// if err != nil {
// return nil, err
// }
//
// valid, err := d.snapshotValidator.validateSnapshot(d.docContext.fullTree.root)
// if err != nil {
// return nil, err
// }
// if !valid {
// return d.build(true)
// }
// }
// err = d.aclStateBuilder.init(d.docContext.fullTree)
// if err != nil {
// return nil, err
// }
//
// d.docContext.aclState, err = d.aclStateBuilder.build()
// if err != nil {
// return nil, err
// }
//
// // tree should be exposed
//
// d.docStateBuilder.init(d.docContext.aclState, d.docContext.fullTree)
// d.docContext.docState, err = d.docStateBuilder.build()
// if err != nil {
// return nil, err
// }
//
// // setting all heads
// d.thread.SetHeads(d.docContext.fullTree.Heads())
// // this should be the entrypoint when we build the document
// d.thread.SetMaybeHeads(d.docContext.fullTree.Heads())
//
// return d.docContext.docState, nil
//}
//
//func (d *Document) State() DocumentState {
// return nil
//}

View File

@ -1,4 +1,4 @@
package exampledocument
package plaintextdocument
import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/testutils/threadbuilder"
@ -22,7 +22,7 @@ func TestDocument_Build(t *testing.T) {
t.Fatal(err)
}
st := res.(*PlainTextDocumentState)
st := res.(*DocumentState)
assert.Equal(t, st.Text, "some text|first")
}
@ -42,11 +42,11 @@ func TestDocument_Update(t *testing.T) {
t.Fatal(err)
}
st := res.(*PlainTextDocumentState)
st := res.(*DocumentState)
assert.Equal(t, st.Text, "some text|first")
rawChs := thread.GetUpdatedChanges()
res, updateResult, err := doc.Update(rawChs...)
assert.Equal(t, updateResult, UpdateResultAppend)
assert.Equal(t, res.(*PlainTextDocumentState).Text, "some text|first|second")
assert.Equal(t, res.(*DocumentState).Text, "some text|first|second")
}

View File

@ -1,4 +1,4 @@
package exampledocument
package plaintextdocument
import "github.com/anytypeio/go-anytype-infrastructure-experiments/acltree"

View File

@ -1,4 +1,4 @@
package exampledocument
package plaintextdocument
import (
"fmt"

View File

@ -1,4 +1,4 @@
package exampledocument
package plaintextdocument
import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/acltree"
@ -23,7 +23,7 @@ func TestDocumentStateBuilder_UserJoinBuild(t *testing.T) {
t.Fatalf("should build acl aclState without err: %v", err)
}
st := ctx.DocState.(*PlainTextDocumentState)
st := ctx.DocState.(*DocumentState)
assert.Equal(t, st.Text, "some text|first")
}
@ -43,6 +43,6 @@ func TestDocumentStateBuilder_UserRemoveBuild(t *testing.T) {
t.Fatalf("should build acl aclState without err: %v", err)
}
st := ctx.DocState.(*PlainTextDocumentState)
st := ctx.DocState.(*DocumentState)
assert.Equal(t, st.Text, "some text|first")
}

View File

@ -0,0 +1,59 @@
package plaintextdocument
import (
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/testutils/testchanges/pb"
"github.com/gogo/protobuf/proto"
)
type DocumentState struct {
LastChangeId string
Text string
}
func NewDocumentState(text string, id string) *DocumentState {
return &DocumentState{
LastChangeId: id,
Text: text,
}
}
func BuildDocumentStateFromChange(change []byte, id string) (*DocumentState, error) {
var changesData pb.PlainTextChangeData
err := proto.Unmarshal(change, &changesData)
if err != nil {
return nil, err
}
if changesData.GetSnapshot() == nil {
return nil, fmt.Errorf("could not create state from empty snapshot")
}
return NewDocumentState(changesData.GetSnapshot().GetText(), id), nil
}
func (p *DocumentState) ApplyChange(change []byte, id string) (*DocumentState, error) {
var changesData pb.PlainTextChangeData
err := proto.Unmarshal(change, &changesData)
if err != nil {
return nil, err
}
for _, content := range changesData.GetContent() {
err = p.applyChange(content)
if err != nil {
return nil, err
}
}
p.LastChangeId = id
return p, nil
}
func (p *DocumentState) applyChange(ch *pb.PlainTextChangeContent) error {
switch {
case ch.GetTextAppend() != nil:
text := ch.GetTextAppend().GetText()
p.Text += "|" + text
}
return nil
}

View File

@ -0,0 +1 @@
package plaintextdocument