1
0
Fork 0
mirror of https://github.com/anyproto/anytype-heart.git synced 2025-06-11 18:20:33 +09:00
anytype-heart/util/builtintemplate/builtintemplate.go
2023-11-01 16:33:21 +05:00

177 lines
5.5 KiB
Go

package builtintemplate
import (
"archive/zip"
"bytes"
"crypto/md5"
_ "embed"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"strings"
"github.com/anyproto/any-sync/app"
smartblock2 "github.com/anyproto/anytype-heart/core/block/editor/smartblock"
"github.com/anyproto/anytype-heart/core/block/editor/state"
"github.com/anyproto/anytype-heart/core/block/simple"
"github.com/anyproto/anytype-heart/core/block/simple/relation"
"github.com/anyproto/anytype-heart/core/block/source"
"github.com/anyproto/anytype-heart/core/domain"
"github.com/anyproto/anytype-heart/pb"
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
"github.com/anyproto/anytype-heart/space"
"github.com/anyproto/anytype-heart/util/pbtypes"
)
const CName = "builtintemplate"
//go:embed data/bundled_templates.zip
var templatesZip []byte
func New() BuiltinTemplate {
return new(builtinTemplate)
}
type BuiltinTemplate interface {
Hash() string
RegisterBuiltinTemplates(space space.Space) error
app.Component
}
type builtinTemplate struct {
source source.Service
objectStore objectstore.ObjectStore
generatedHash string
}
func (b *builtinTemplate) Init(a *app.App) (err error) {
b.source = app.MustComponent[source.Service](a)
b.objectStore = app.MustComponent[objectstore.ObjectStore](a)
b.makeGenHash(4)
return
}
func (b *builtinTemplate) makeGenHash(version uint32) {
h := md5.New()
h.Write(templatesZip)
binary.Write(h, binary.LittleEndian, version)
b.generatedHash = hex.EncodeToString(h.Sum(nil))
}
func (b *builtinTemplate) Name() (name string) {
return CName
}
func (b *builtinTemplate) RegisterBuiltinTemplates(space space.Space) error {
zr, err := zip.NewReader(bytes.NewReader(templatesZip), int64(len(templatesZip)))
if err != nil {
return fmt.Errorf("new reader: %w", err)
}
for _, zf := range zr.File {
rd, e := zf.Open()
if e != nil {
return e
}
if err = b.registerBuiltin(space, rd); err != nil {
return fmt.Errorf("register builtin: %w", err)
}
}
return nil
}
func (b *builtinTemplate) Hash() string {
return b.generatedHash
}
func (b *builtinTemplate) registerBuiltin(space space.Space, rd io.ReadCloser) (err error) {
defer rd.Close()
data, err := io.ReadAll(rd)
snapshot := &pb.ChangeSnapshot{}
if err = snapshot.Unmarshal(data); err != nil {
return
}
var id string
for _, block := range snapshot.Data.Blocks {
if block.GetSmartblock() != nil {
id = block.Id
break
}
}
st := state.NewDocFromSnapshot(id, snapshot).(*state.State)
st.SetRootId(id)
st.SetLocalDetail(bundle.RelationKeyTemplateIsBundled.String(), pbtypes.Bool(true))
st.RemoveDetail(bundle.RelationKeyCreator.String(), bundle.RelationKeyLastModifiedBy.String())
st.SetLocalDetail(bundle.RelationKeyCreator.String(), pbtypes.String(addr.AnytypeProfileId))
st.SetLocalDetail(bundle.RelationKeyLastModifiedBy.String(), pbtypes.String(addr.AnytypeProfileId))
st.SetLocalDetail(bundle.RelationKeySpaceId.String(), pbtypes.String(addr.AnytypeMarketplaceWorkspace))
err = b.setObjectTypes(st)
if err != nil {
return fmt.Errorf("set object types: %w", err)
}
// fix divergence between extra relations and simple block relations
st.Iterate(func(b simple.Block) (isContinue bool) {
if _, ok := b.(relation.Block); ok {
relKey := b.Model().GetRelation().Key
if !st.HasRelation(relKey) {
st.AddBundledRelations(domain.RelationKey(relKey))
}
}
return true
})
if err = b.validate(st); err != nil {
return
}
fullID := domain.FullID{SpaceID: space.Id(), ObjectID: id}
err = b.source.RegisterStaticSource(b.source.NewStaticSource(fullID, smartblock.SmartBlockTypeBundledTemplate, st.Copy(), nil))
if err != nil {
return fmt.Errorf("register static source: %w", err)
}
// Index
return space.Do(id, func(sb smartblock2.SmartBlock) error {
return sb.Apply(sb.NewState())
})
}
func (b *builtinTemplate) setObjectTypes(st *state.State) error {
targetObjectTypeID := pbtypes.GetString(st.Details(), bundle.RelationKeyTargetObjectType.String())
var targetObjectTypeKey domain.TypeKey
if strings.HasPrefix(targetObjectTypeID, addr.BundledObjectTypeURLPrefix) {
// todo: remove this hack after fixing bundled templates
targetObjectTypeKey = domain.TypeKey(strings.TrimPrefix(targetObjectTypeID, addr.BundledObjectTypeURLPrefix))
} else {
targetObjectType, err := b.objectStore.GetObjectType(targetObjectTypeID)
if err != nil {
return fmt.Errorf("get object type %s: %w", targetObjectTypeID, err)
}
targetObjectTypeKey = domain.TypeKey(targetObjectType.Key)
}
st.SetObjectTypeKeys([]domain.TypeKey{bundle.TypeKeyTemplate, targetObjectTypeKey})
return nil
}
func (b *builtinTemplate) validate(st *state.State) (err error) {
cd := st.CombinedDetails()
if st.ObjectTypeKey() != bundle.TypeKeyTemplate {
return fmt.Errorf("bundled template validation: %s unexpected object type: %v", st.RootId(), st.ObjectTypeKey())
}
if !pbtypes.GetBool(cd, bundle.RelationKeyTemplateIsBundled.String()) {
return fmt.Errorf("bundled template validation: %s not bundled", st.RootId())
}
targetObjectTypeID := pbtypes.GetString(cd, bundle.RelationKeyTargetObjectType.String())
if targetObjectTypeID == "" || domain.TypeKey(targetObjectTypeID) == st.ObjectTypeKey() {
return fmt.Errorf("bundled template validation: %s unexpected target object type: %v", st.RootId(), targetObjectTypeID)
}
// todo: update templates and return the validation
return nil
}