Add tree updater to ACL tree
This commit is contained in:
parent
b07a137fb9
commit
b411c3501e
@ -23,11 +23,17 @@ 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
|
||||
@ -36,6 +42,7 @@ type ACLTree interface {
|
||||
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
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
378
plaintextdocument/document.go
Normal file
378
plaintextdocument/document.go
Normal 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
|
||||
//}
|
||||
@ -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")
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package exampledocument
|
||||
package plaintextdocument
|
||||
|
||||
import "github.com/anytypeio/go-anytype-infrastructure-experiments/acltree"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package exampledocument
|
||||
package plaintextdocument
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -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")
|
||||
}
|
||||
59
plaintextdocument/plaintextdocstate.go
Normal file
59
plaintextdocument/plaintextdocstate.go
Normal 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
|
||||
}
|
||||
1
plaintextdocument/plaintextdocument.go
Normal file
1
plaintextdocument/plaintextdocument.go
Normal file
@ -0,0 +1 @@
|
||||
package plaintextdocument
|
||||
Loading…
x
Reference in New Issue
Block a user