1
0
Fork 0
mirror of https://github.com/anyproto/anytype-heart.git synced 2025-06-08 05:47:07 +09:00
anytype-heart/space/techspace/techspace.go
2025-05-14 20:45:38 +02:00

402 lines
11 KiB
Go

package techspace
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/anyproto/any-sync/app"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/commonspace"
"github.com/anyproto/any-sync/commonspace/object/keyvalue/keyvaluestorage"
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
"github.com/anyproto/any-sync/net/peer"
"go.uber.org/zap"
editorsb "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/object/objectcache"
"github.com/anyproto/anytype-heart/core/block/object/payloadcreator"
"github.com/anyproto/anytype-heart/core/domain"
"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
"github.com/anyproto/anytype-heart/space/spaceinfo"
)
const CName = "client.space.techspace"
var log = logger.NewNamed(CName)
const spaceViewCheckTimeout = time.Second * 15
var (
ErrSpaceViewExists = errors.New("spaceView exists")
ErrSpaceViewNotExists = errors.New("spaceView not exists")
ErrAccountObjectNotExists = errors.New("accountObject not exists")
ErrNotASpaceView = errors.New("smartblock not a spaceView")
ErrNotAnAccountObject = errors.New("smartblock not an accountObject")
ErrNotStarted = errors.New("techspace not started")
)
type AccountObject interface {
editorsb.SmartBlock
SetSharedSpacesLimit(limit int) (err error)
SetProfileDetails(details *domain.Details) (err error)
MigrateIconImage(image string) (err error)
IsIconMigrated() (bool, error)
SetAnalyticsId(analyticsId string) (err error)
GetAnalyticsId() (string, error)
}
type TechSpace interface {
app.Component
Run(techCoreSpace commonspace.Space, objectCache objectcache.Cache, create bool) (err error)
Close(ctx context.Context) (err error)
WakeUpViews()
WaitViews() error
TechSpaceId() string
DoSpaceView(ctx context.Context, spaceID string, apply func(spaceView SpaceView) error) (err error)
DoAccountObject(ctx context.Context, apply func(accountObject AccountObject) error) (err error)
SpaceViewCreate(ctx context.Context, spaceId string, force bool, info spaceinfo.SpacePersistentInfo) (err error)
GetSpaceView(ctx context.Context, spaceId string) (SpaceView, error)
SpaceViewExists(ctx context.Context, spaceId string) (exists bool, err error)
SetLocalInfo(ctx context.Context, info spaceinfo.SpaceLocalInfo) (err error)
SetPersistentInfo(ctx context.Context, info spaceinfo.SpacePersistentInfo) (err error)
SpaceViewSetData(ctx context.Context, spaceId string, details *domain.Details) (err error)
SpaceViewId(id string) (string, error)
AccountObjectId() (string, error)
}
type SpaceView interface {
sync.Locker
GetPersistentInfo() spaceinfo.SpacePersistentInfo
GetLocalInfo() spaceinfo.SpaceLocalInfo
SetSpaceData(details *domain.Details) error
SetSpaceLocalInfo(info spaceinfo.SpaceLocalInfo) error
SetAccessType(acc spaceinfo.AccessType) error
SetAclIsEmpty(isEmpty bool) (err error)
SetOwner(ownerId string, createdDate int64) (err error)
SetSpacePersistentInfo(info spaceinfo.SpacePersistentInfo) error
GetSpaceDescription() (data spaceinfo.SpaceDescription)
SetSharedSpacesLimit(limits int) (err error)
GetSharedSpacesLimit() (limits int)
}
func New() TechSpace {
s := &techSpace{
viewIds: make(map[string]string),
}
s.ctx, s.ctxCancel = context.WithCancel(context.Background())
return s
}
type techSpace struct {
techCore commonspace.Space
objectCache objectcache.Cache
accountObjectId string
mu sync.Mutex
ctx context.Context
ctxCancel context.CancelFunc
idsWokenUp chan struct{}
isClosed bool
viewIds map[string]string
}
func (s *techSpace) Init(a *app.App) (err error) {
return nil
}
func (s *techSpace) Name() (name string) {
return CName
}
func (s *techSpace) Run(techCoreSpace commonspace.Space, objectCache objectcache.Cache, create bool) (err error) {
s.techCore = techCoreSpace
s.objectCache = objectCache
if !create {
exists, err := s.accountObjectExists(s.ctx)
if err != nil {
return err
}
if exists {
return nil
}
}
return s.accountObjectCreate(s.ctx)
}
func (s *techSpace) WakeUpViews() {
s.mu.Lock()
if s.isClosed || s.idsWokenUp != nil {
s.mu.Unlock()
return
}
s.idsWokenUp = make(chan struct{})
s.mu.Unlock()
go func() {
defer close(s.idsWokenUp)
s.wakeUpViews()
}()
}
func (s *techSpace) WaitViews() error {
s.mu.Lock()
idsWokenUp := s.idsWokenUp
s.mu.Unlock()
if idsWokenUp != nil {
select {
case <-idsWokenUp:
return nil
case <-s.ctx.Done():
return s.ctx.Err()
}
} else {
return ErrNotStarted
}
}
func (s *techSpace) wakeUpViews() {
for _, id := range s.techCore.StoredIds() {
select {
case <-s.ctx.Done():
return
default:
}
if _, err := s.objectCache.GetObject(s.ctx, id); err != nil {
log.Warn("wakeUp views: get object error", zap.String("objectId", id), zap.Error(err))
}
}
s.techCore.TreeSyncer().StartSync()
return
}
func (s *techSpace) TechSpaceId() string {
return s.techCore.Id()
}
func (s *techSpace) SetLocalInfo(ctx context.Context, info spaceinfo.SpaceLocalInfo) (err error) {
return s.DoSpaceView(ctx, info.SpaceId, func(spaceView SpaceView) error {
return spaceView.SetSpaceLocalInfo(info)
})
}
func (s *techSpace) SetPersistentInfo(ctx context.Context, info spaceinfo.SpacePersistentInfo) (err error) {
return s.DoSpaceView(ctx, info.SpaceID, func(spaceView SpaceView) error {
return spaceView.SetSpacePersistentInfo(info)
})
}
func (s *techSpace) SpaceViewCreate(ctx context.Context, spaceId string, force bool, info spaceinfo.SpacePersistentInfo) (err error) {
if force {
return s.spaceViewCreate(ctx, spaceId, info)
}
viewId, err := s.getViewIdLocked(ctx, spaceId)
if err != nil {
return err
}
_, err = s.objectCache.GetObject(ctx, viewId)
if err != nil { // TODO: check specific error
return s.spaceViewCreate(ctx, spaceId, info)
}
return ErrSpaceViewExists
}
func (s *techSpace) SpaceViewExists(ctx context.Context, spaceId string) (exists bool, err error) {
viewId, err := s.getViewIdLocked(ctx, spaceId)
if err != nil {
return
}
ctx, cancel := context.WithTimeout(ctx, spaceViewCheckTimeout)
defer cancel()
ctx = peer.CtxWithPeerId(ctx, peer.CtxResponsiblePeers)
_, getErr := s.objectCache.GetObject(ctx, viewId)
return getErr == nil, nil
}
func (s *techSpace) accountObjectExists(ctx context.Context) (exists bool, err error) {
objId, err := s.AccountObjectId()
if err != nil {
return
}
ctx, cancel := context.WithTimeout(ctx, spaceViewCheckTimeout)
defer cancel()
ctx = peer.CtxWithPeerId(ctx, peer.CtxResponsiblePeers)
_, getErr := s.objectCache.GetObject(ctx, objId)
return getErr == nil, nil
}
func (s *techSpace) GetSpaceView(ctx context.Context, spaceId string) (SpaceView, error) {
viewId, err := s.getViewIdLocked(ctx, spaceId)
if err != nil {
return nil, err
}
obj, err := s.objectCache.GetObject(ctx, viewId)
if err != nil {
return nil, err
}
spaceView, ok := obj.(SpaceView)
if !ok {
return nil, ErrNotASpaceView
}
return spaceView, nil
}
func (s *techSpace) SpaceViewSetData(ctx context.Context, spaceId string, details *domain.Details) (err error) {
return s.DoSpaceView(ctx, spaceId, func(spaceView SpaceView) error {
return spaceView.SetSpaceData(details)
})
}
func (s *techSpace) SpaceViewId(spaceId string) (string, error) {
return s.getViewIdLocked(context.TODO(), spaceId)
}
func (s *techSpace) spaceViewCreate(ctx context.Context, spaceID string, info spaceinfo.SpacePersistentInfo) (err error) {
uniqueKey, err := domain.NewUniqueKey(smartblock.SmartBlockTypeSpaceView, spaceID)
if err != nil {
return
}
initFunc := func(id string) *editorsb.InitContext {
st := state.NewDoc(id, nil).(*state.State)
info.UpdateDetails(st)
return &editorsb.InitContext{Ctx: ctx, SpaceID: s.techCore.Id(), State: st}
}
_, err = s.objectCache.DeriveTreeObject(ctx, objectcache.TreeDerivationParams{
Key: uniqueKey,
InitFunc: initFunc,
})
if err != nil {
return
}
return
}
func (s *techSpace) accountObjectCreate(ctx context.Context) (err error) {
uniqueKey, err := domain.NewUniqueKey(smartblock.SmartBlockTypeAccountObject, s.techCore.Id())
if err != nil {
return
}
initFunc := func(id string) *editorsb.InitContext {
st := state.NewDoc(id, nil).(*state.State)
return &editorsb.InitContext{Ctx: ctx, SpaceID: s.techCore.Id(), State: st}
}
_, err = s.objectCache.DeriveTreeObject(ctx, objectcache.TreeDerivationParams{
Key: uniqueKey,
InitFunc: initFunc,
})
if errors.Is(err, treestorage.ErrTreeExists) {
accId, err := s.AccountObjectId()
if err != nil {
return err
}
_, err = s.objectCache.GetObject(ctx, accId)
return err
}
return
}
func (s *techSpace) AccountObjectId() (string, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.accountObjectId != "" {
return s.accountObjectId, nil
}
uniqueKey, err := domain.NewUniqueKey(smartblock.SmartBlockTypeAccountObject, s.techCore.Id())
if err != nil {
return "", err
}
payload, err := s.objectCache.DeriveTreePayload(context.Background(), payloadcreator.PayloadDerivationParams{
Key: uniqueKey,
})
if err != nil {
return "", err
}
s.accountObjectId = payload.RootRawChange.Id
return payload.RootRawChange.Id, nil
}
func (s *techSpace) deriveSpaceViewID(ctx context.Context, spaceID string) (string, error) {
uniqueKey, err := domain.NewUniqueKey(smartblock.SmartBlockTypeSpaceView, spaceID)
if err != nil {
return "", err
}
payload, err := s.objectCache.DeriveTreePayload(ctx, payloadcreator.PayloadDerivationParams{
Key: uniqueKey,
})
if err != nil {
return "", err
}
return payload.RootRawChange.Id, nil
}
func (s *techSpace) DoSpaceView(ctx context.Context, spaceID string, apply func(spaceView SpaceView) error) (err error) {
viewId, err := s.getViewIdLocked(ctx, spaceID)
if err != nil {
return
}
obj, err := s.objectCache.GetObject(ctx, viewId)
if err != nil {
return ErrSpaceViewNotExists
}
spaceView, ok := obj.(SpaceView)
if !ok {
return ErrNotASpaceView
}
spaceView.Lock()
defer spaceView.Unlock()
return apply(spaceView)
}
func (s *techSpace) DoAccountObject(ctx context.Context, apply func(accountObject AccountObject) error) (err error) {
id, err := s.AccountObjectId()
if err != nil {
return err
}
obj, err := s.objectCache.GetObject(ctx, id)
if err != nil {
return fmt.Errorf("account object not exists %w: %w", ErrAccountObjectNotExists, err)
}
accountObject, ok := obj.(AccountObject)
if !ok {
return ErrNotAnAccountObject
}
accountObject.Lock()
defer accountObject.Unlock()
return apply(accountObject)
}
func (s *techSpace) getViewIdLocked(ctx context.Context, spaceId string) (viewId string, err error) {
s.mu.Lock()
defer s.mu.Unlock()
if viewId = s.viewIds[spaceId]; viewId != "" {
return
}
if viewId, err = s.deriveSpaceViewID(ctx, spaceId); err != nil {
return
}
s.viewIds[spaceId] = viewId
return
}
func (s *techSpace) KeyValueStore() keyvaluestorage.Storage {
return s.techCore.KeyValue().DefaultStore()
}
func (s *techSpace) Close(ctx context.Context) (err error) {
s.ctxCancel()
s.mu.Lock()
s.isClosed = true
wokenUp := s.idsWokenUp
s.mu.Unlock()
if wokenUp != nil {
<-s.idsWokenUp
}
return nil
}