package builtintemplate import ( "archive/zip" "bytes" "context" "crypto/md5" _ "embed" "encoding/binary" "encoding/hex" "fmt" "io" "io/ioutil" "github.com/anytypeio/go-anytype-middleware/app" "github.com/anytypeio/go-anytype-middleware/core/block/editor/state" "github.com/anytypeio/go-anytype-middleware/core/block/simple" "github.com/anytypeio/go-anytype-middleware/core/block/simple/relation" "github.com/anytypeio/go-anytype-middleware/core/block/source" "github.com/anytypeio/go-anytype-middleware/pb" "github.com/anytypeio/go-anytype-middleware/pkg/lib/bundle" "github.com/anytypeio/go-anytype-middleware/pkg/lib/core/smartblock" "github.com/anytypeio/go-anytype-middleware/pkg/lib/localstore/addr" "github.com/anytypeio/go-anytype-middleware/pkg/lib/logging" "github.com/anytypeio/go-anytype-middleware/pkg/lib/pb/model" "github.com/anytypeio/go-anytype-middleware/pkg/lib/threads" "github.com/anytypeio/go-anytype-middleware/util/pbtypes" ) const CName = "builtintemplate" //go:embed data/bundled_templates.zip var templatesZip []byte var log = logging.Logger("anytype-mw-builtintemplate") func New() BuiltinTemplate { return new(builtinTemplate) } type BuiltinTemplate interface { Hash() string app.ComponentRunnable } type builtinTemplate struct { source source.Service generatedHash string } func (b *builtinTemplate) Init(a *app.App) (err error) { b.source = a.MustComponent(source.CName).(source.Service) b.makeGenHash(3) 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) Run(context.Context) (err error) { zr, err := zip.NewReader(bytes.NewReader(templatesZip), int64(len(templatesZip))) if err != nil { return } for _, zf := range zr.File { rd, e := zf.Open() if e != nil { return e } if err = b.registerBuiltin(rd); err != nil { return } } return } func (b *builtinTemplate) Hash() string { return b.generatedHash } func (b *builtinTemplate) registerBuiltin(rd io.ReadCloser) (err error) { defer rd.Close() data, err := ioutil.ReadAll(rd) snapshot := &pb.ChangeSnapshot{} if err = snapshot.Unmarshal(data); err != nil { return } st := state.NewDocFromSnapshot("", snapshot, state.DoNotMigrateTypes).(*state.State) id, err := threads.PatchSmartBlockType(st.RootId(), smartblock.SmartBlockTypeBundledTemplate) if err != nil { return } st.SetRootId(id) st = st.Copy() 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.RelationKeyWorkspaceId.String(), pbtypes.String(addr.AnytypeMarketplaceWorkspace)) st.SetObjectTypes([]string{bundle.TypeKeyTemplate.BundledURL(), pbtypes.Get(st.Details(), bundle.RelationKeyTargetObjectType.String()).GetStringValue()}) st.InjectDerivedDetails() // 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(bundle.RelationKey(relKey)) } } return true }) if err = b.validate(st.Copy()); err != nil { return } b.source.RegisterStaticSource(id, func() source.Source { return b.source.NewStaticSource(id, model.SmartBlockType_BundledTemplate, st.Copy(), nil) }) return } func (b *builtinTemplate) validate(st *state.State) (err error) { cd := st.CombinedDetails() if st.ObjectType() != bundle.TypeKeyTemplate.BundledURL() { return fmt.Errorf("bundled template validation: %s unexpected object type: %v", st.RootId(), st.ObjectType()) } if !pbtypes.GetBool(cd, bundle.RelationKeyTemplateIsBundled.String()) { return fmt.Errorf("bundled template validation: %s not bundled", st.RootId()) } if tt := pbtypes.GetString(cd, bundle.RelationKeyTargetObjectType.String()); tt == "" || tt == st.ObjectType() { return fmt.Errorf("bundled template validation: %s unexpected target object type: %v", st.RootId(), tt) } // todo: update templates and return the validation return nil var relKeys []string st.Iterate(func(b simple.Block) (isContinue bool) { if rb, ok := b.(relation.Block); ok { relKeys = append(relKeys, rb.Model().GetRelation().Key) } return true }) for _, rk := range relKeys { if !st.HasRelation(rk) { return fmt.Errorf("bundled template validation: relation '%v' exists in block but not in extra relations", rk) } } return nil } func (b *builtinTemplate) Close() (err error) { return }