mirror of
https://github.com/anyproto/any-sync.git
synced 2025-06-10 01:51:11 +09:00
258 lines
6.9 KiB
Go
258 lines
6.9 KiB
Go
package document
|
|
|
|
import (
|
|
"context"
|
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/app"
|
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
|
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
|
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/testchangepb"
|
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
|
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
|
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
|
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/account"
|
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/node"
|
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/sync/message"
|
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/service/treecache"
|
|
"github.com/anytypeio/go-anytype-infrastructure-experiments/syncproto"
|
|
"github.com/gogo/protobuf/proto"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var CName = "DocumentService"
|
|
|
|
var log = logger.NewNamed("documentservice")
|
|
|
|
type service struct {
|
|
messageService message.Service
|
|
treeCache treecache.Service
|
|
account account.Service
|
|
treeStorageProvider treestorage.Provider
|
|
// to create new documents we need to know all nodes
|
|
nodes []*node.Node
|
|
}
|
|
|
|
type Service interface {
|
|
UpdateDocumentTree(ctx context.Context, id, text string) error
|
|
CreateACLTree(ctx context.Context) (id string, err error)
|
|
CreateDocumentTree(ctx context.Context, aclTreeId string, text string) (id string, err error)
|
|
}
|
|
|
|
func New() app.Component {
|
|
return &service{}
|
|
}
|
|
|
|
func (s *service) Init(ctx context.Context, a *app.App) (err error) {
|
|
s.account = a.MustComponent(account.CName).(account.Service)
|
|
s.messageService = a.MustComponent(message.CName).(message.Service)
|
|
s.treeCache = a.MustComponent(treecache.CName).(treecache.Service)
|
|
// TODO: add TreeStorageProvider service
|
|
|
|
nodesService := a.MustComponent(node.CName).(node.Service)
|
|
s.nodes = nodesService.Nodes()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *service) Name() (name string) {
|
|
return CName
|
|
}
|
|
|
|
func (s *service) Run(ctx context.Context) (err error) {
|
|
return nil
|
|
}
|
|
|
|
func (s *service) Close(ctx context.Context) (err error) {
|
|
return nil
|
|
}
|
|
|
|
func (s *service) UpdateDocumentTree(ctx context.Context, id, text string) (err error) {
|
|
var (
|
|
ch *aclpb.RawChange
|
|
header *treepb.TreeHeader
|
|
snapshotPath []string
|
|
heads []string
|
|
)
|
|
log.With(zap.String("id", id), zap.String("text", text)).
|
|
Debug("updating document")
|
|
|
|
err = s.treeCache.Do(ctx, id, func(obj interface{}) error {
|
|
docTree := obj.(tree.DocTree)
|
|
err = s.treeCache.Do(ctx, docTree.Header().AclTreeId, func(obj interface{}) error {
|
|
aclTree := obj.(tree.ACLTree)
|
|
aclTree.RLock()
|
|
defer aclTree.RUnlock()
|
|
|
|
content := createAppendTextChange(text)
|
|
_, err := docTree.AddContent(ctx, aclTree, content, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
id = docTree.ID()
|
|
heads = docTree.Heads()
|
|
header = docTree.Header()
|
|
snapshotPath = docTree.SnapshotPath()
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.With(
|
|
zap.String("id", id),
|
|
zap.Strings("heads", heads),
|
|
zap.String("header", header.String())).
|
|
Debug("document updated in the database")
|
|
|
|
return s.messageService.SendToSpaceAsync("", syncproto.WrapHeadUpdate(&syncproto.SyncHeadUpdate{
|
|
Heads: heads,
|
|
Changes: []*aclpb.RawChange{ch},
|
|
TreeId: id,
|
|
SnapshotPath: snapshotPath,
|
|
TreeHeader: header,
|
|
}))
|
|
}
|
|
|
|
func (s *service) CreateACLTree(ctx context.Context) (id string, err error) {
|
|
acc := s.account.Account()
|
|
var (
|
|
ch *aclpb.RawChange
|
|
header *treepb.TreeHeader
|
|
snapshotPath []string
|
|
heads []string
|
|
)
|
|
|
|
t, err := tree.CreateNewTreeStorageWithACL(acc, func(builder tree.ACLChangeBuilder) error {
|
|
err := builder.UserAdd(acc.Identity, acc.EncKey.GetPublic(), aclpb.ACLChange_Admin)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// adding all predefined nodes to the document as admins
|
|
for _, n := range s.nodes {
|
|
err = builder.UserAdd(n.SigningKeyString, n.EncryptionKey, aclpb.ACLChange_Admin)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}, s.treeStorageProvider.CreateTreeStorage)
|
|
|
|
id, err = t.TreeID()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
header, err = t.Header()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
heads = []string{header.FirstChangeId}
|
|
snapshotPath = []string{header.FirstChangeId}
|
|
ch, err = t.GetChange(ctx, header.FirstChangeId)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = s.messageService.SendToSpaceAsync("", syncproto.WrapHeadUpdate(&syncproto.SyncHeadUpdate{
|
|
Heads: heads,
|
|
Changes: []*aclpb.RawChange{ch},
|
|
TreeId: id,
|
|
SnapshotPath: snapshotPath,
|
|
TreeHeader: header,
|
|
}))
|
|
return id, nil
|
|
}
|
|
|
|
func (s *service) CreateDocumentTree(ctx context.Context, aclTreeId string, text string) (id string, err error) {
|
|
acc := s.account.Account()
|
|
var (
|
|
ch *aclpb.RawChange
|
|
header *treepb.TreeHeader
|
|
snapshotPath []string
|
|
heads []string
|
|
)
|
|
err = s.treeCache.Do(ctx, aclTreeId, func(obj interface{}) error {
|
|
t := obj.(tree.ACLTree)
|
|
t.RLock()
|
|
defer t.RUnlock()
|
|
|
|
content := createInitialTextChange(text)
|
|
doc, err := tree.CreateNewTreeStorage(acc, t, content, s.treeStorageProvider.CreateTreeStorage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
id, err = doc.TreeID()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
header, err = doc.Header()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
heads = []string{header.FirstChangeId}
|
|
snapshotPath = []string{header.FirstChangeId}
|
|
ch, err = doc.GetChange(ctx, header.FirstChangeId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
log.With(zap.String("id", id), zap.String("text", text)).
|
|
Debug("creating document")
|
|
|
|
err = s.messageService.SendToSpaceAsync("", syncproto.WrapHeadUpdate(&syncproto.SyncHeadUpdate{
|
|
Heads: heads,
|
|
Changes: []*aclpb.RawChange{ch},
|
|
TreeId: id,
|
|
SnapshotPath: snapshotPath,
|
|
TreeHeader: header,
|
|
}))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return id, err
|
|
}
|
|
|
|
func createInitialTextChange(text string) proto.Marshaler {
|
|
return &testchangepb.PlainTextChangeData{
|
|
Content: []*testchangepb.PlainTextChangeContent{
|
|
createAppendTextChangeContent(text),
|
|
},
|
|
Snapshot: &testchangepb.PlainTextChangeSnapshot{Text: text},
|
|
}
|
|
}
|
|
|
|
func createAppendTextChange(text string) proto.Marshaler {
|
|
return &testchangepb.PlainTextChangeData{
|
|
Content: []*testchangepb.PlainTextChangeContent{
|
|
createAppendTextChangeContent(text),
|
|
},
|
|
}
|
|
}
|
|
|
|
func createAppendTextChangeContent(text string) *testchangepb.PlainTextChangeContent {
|
|
return &testchangepb.PlainTextChangeContent{
|
|
Value: &testchangepb.PlainTextChangeContentValueOfTextAppend{
|
|
TextAppend: &testchangepb.PlainTextChangeTextAppend{
|
|
Text: text,
|
|
},
|
|
},
|
|
}
|
|
}
|