mirror of
https://github.com/anyproto/anytype-heart.git
synced 2025-06-11 10:18:28 +09:00
GO-2421 Merge branch 'main' of github.com:anyproto/anytype-heart into go-2421-add-details-to-response-of-blocklinkcreatewithobject
This commit is contained in:
commit
8266bc64ef
44 changed files with 1283 additions and 949 deletions
|
@ -75,6 +75,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/objectstore"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/logging"
|
||||
"github.com/anyproto/anytype-heart/space"
|
||||
"github.com/anyproto/anytype-heart/space/deletioncontroller"
|
||||
"github.com/anyproto/anytype-heart/space/spacecore"
|
||||
"github.com/anyproto/anytype-heart/space/spacecore/clientserver"
|
||||
"github.com/anyproto/anytype-heart/space/spacecore/credentialprovider"
|
||||
|
@ -228,6 +229,7 @@ func Bootstrap(a *app.App, components ...app.Component) {
|
|||
Register(source.New()).
|
||||
Register(spacefactory.New()).
|
||||
Register(space.New()).
|
||||
Register(deletioncontroller.New()).
|
||||
Register(invitestore.New()).
|
||||
Register(fileobject.New()).
|
||||
Register(acl.New()).
|
||||
|
|
|
@ -4,11 +4,15 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/gogo/protobuf/jsonpb"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
"github.com/anyproto/anytype-heart/core/block/source"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/database"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
|
@ -19,10 +23,22 @@ import (
|
|||
|
||||
func (s *Service) DebugRouter(r chi.Router) {
|
||||
r.Get("/objects", debug.JSONHandler(s.debugListObjects))
|
||||
r.Get("/tree/{id}", debug.JSONHandler(s.debugTree))
|
||||
r.Get("/objects_per_space/{spaceId}", debug.JSONHandler(s.debugListObjectsPerSpace))
|
||||
r.Get("/objects/{id}", debug.JSONHandler(s.debugGetObject))
|
||||
}
|
||||
|
||||
type debugTree struct {
|
||||
Id string
|
||||
Changes []debugChange
|
||||
}
|
||||
|
||||
type debugChange struct {
|
||||
Change json.RawMessage
|
||||
Identity string
|
||||
Timestamp string
|
||||
}
|
||||
|
||||
type debugObject struct {
|
||||
ID string
|
||||
Details json.RawMessage
|
||||
|
@ -84,6 +100,36 @@ func (s *Service) debugGetObject(req *http.Request) (debugObject, error) {
|
|||
return s.getDebugObject(id)
|
||||
}
|
||||
|
||||
func (s *Service) debugTree(req *http.Request) (debugTree, error) {
|
||||
id := chi.URLParam(req, "id")
|
||||
|
||||
result := debugTree{
|
||||
Id: id,
|
||||
}
|
||||
err := Do(s, id, func(sb smartblock.SmartBlock) error {
|
||||
ot := sb.Tree()
|
||||
return ot.IterateRoot(source.UnmarshalChange, func(change *objecttree.Change) bool {
|
||||
change.Next = nil
|
||||
raw, err := json.Marshal(change)
|
||||
if err != nil {
|
||||
log.Error("debug tree: marshal change", zap.Error(err))
|
||||
return false
|
||||
}
|
||||
ts := time.Unix(change.Timestamp, 0)
|
||||
ch := debugChange{
|
||||
Change: raw,
|
||||
Timestamp: ts.Format(time.RFC3339),
|
||||
}
|
||||
if change.Identity != nil {
|
||||
ch.Identity = change.Identity.Account()
|
||||
}
|
||||
result.Changes = append(result.Changes, ch)
|
||||
return true
|
||||
})
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *Service) getDebugObject(id string) (debugObject, error) {
|
||||
var obj debugObject
|
||||
err := Do(s, id, func(sb smartblock.SmartBlock) error {
|
||||
|
|
|
@ -36,6 +36,7 @@ func (p *participant) Init(ctx *smartblock.InitContext) (err error) {
|
|||
template.InitTemplate(ctx.State,
|
||||
template.WithEmpty,
|
||||
template.WithTitle,
|
||||
template.WithDescription,
|
||||
template.WithFeaturedRelations,
|
||||
template.WithAddedFeaturedRelation(bundle.RelationKeyType),
|
||||
template.WithAddedFeaturedRelation(bundle.RelationKeyBacklinks),
|
||||
|
|
|
@ -604,7 +604,6 @@ func (sb *smartBlock) Apply(s *state.State, flags ...ApplyFlag) (err error) {
|
|||
if lastModifiedFromState > 0 {
|
||||
lastModified = time.Unix(lastModifiedFromState, 0)
|
||||
}
|
||||
s.SetLocalDetail(bundle.RelationKeyLastModifiedDate.String(), pbtypes.Int64(lastModified.Unix()))
|
||||
|
||||
if existingCreatedDate := pbtypes.GetInt64(s.LocalDetails(), bundle.RelationKeyCreatedDate.String()); existingCreatedDate == 0 || existingCreatedDate > lastModified.Unix() {
|
||||
// this can happen if we don't have creation date in the root change
|
||||
|
@ -612,9 +611,6 @@ func (sb *smartBlock) Apply(s *state.State, flags ...ApplyFlag) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
s.SetLocalDetail(bundle.RelationKeyLastModifiedBy.String(), pbtypes.String(sb.currentParticipantId))
|
||||
s.SetLocalDetail(bundle.RelationKeyLastModifiedDate.String(), pbtypes.Int64(lastModified.Unix()))
|
||||
|
||||
sb.beforeStateApply(s)
|
||||
|
||||
if !keepInternalFlags {
|
||||
|
@ -653,6 +649,11 @@ func (sb *smartBlock) Apply(s *state.State, flags ...ApplyFlag) (err error) {
|
|||
return nil
|
||||
}
|
||||
pushChange := func() error {
|
||||
if !sb.source.ReadOnly() {
|
||||
// We can set details directly in object's state, they'll be indexed correctly
|
||||
st.SetLocalDetail(bundle.RelationKeyLastModifiedBy.String(), pbtypes.String(sb.currentParticipantId))
|
||||
st.SetLocalDetail(bundle.RelationKeyLastModifiedDate.String(), pbtypes.Int64(lastModified.Unix()))
|
||||
}
|
||||
fileDetailsKeys := st.FileRelationKeys()
|
||||
var fileDetailsKeysFiltered []string
|
||||
for _, ch := range changes {
|
||||
|
@ -928,12 +929,21 @@ func (sb *smartBlock) injectCreationInfo(s *state.State) error {
|
|||
|
||||
if creatorIdentityObjectId != "" {
|
||||
s.SetDetailAndBundledRelation(bundle.RelationKeyCreator, pbtypes.String(creatorIdentityObjectId))
|
||||
} else {
|
||||
// For derived objects we set current identity
|
||||
s.SetDetailAndBundledRelation(bundle.RelationKeyCreator, pbtypes.String(sb.currentParticipantId))
|
||||
}
|
||||
|
||||
if originalCreated := s.OriginalCreatedTimestamp(); originalCreated > 0 {
|
||||
// means we have imported object, so we need to set original created date
|
||||
s.SetDetailAndBundledRelation(bundle.RelationKeyCreatedDate, pbtypes.Float64(float64(originalCreated)))
|
||||
s.SetDetailAndBundledRelation(bundle.RelationKeyAddedDate, pbtypes.Float64(float64(treeCreatedDate)))
|
||||
// Only set AddedDate once because we have a side effect with treeCreatedDate:
|
||||
// - When we import object, treeCreateDate is set to time.Now()
|
||||
// - But after push it is changed to original modified date
|
||||
// - So after account recovery we will get treeCreateDate = original modified date, which is not equal to AddedDate
|
||||
if pbtypes.GetInt64(s.Details(), bundle.RelationKeyAddedDate.String()) == 0 {
|
||||
s.SetDetailAndBundledRelation(bundle.RelationKeyAddedDate, pbtypes.Float64(float64(treeCreatedDate)))
|
||||
}
|
||||
} else {
|
||||
s.SetDetailAndBundledRelation(bundle.RelationKeyCreatedDate, pbtypes.Float64(float64(treeCreatedDate)))
|
||||
}
|
||||
|
|
|
@ -144,6 +144,7 @@ func (s *State) Merge(s2 *State) *State {
|
|||
}
|
||||
|
||||
func (s *State) ApplyChange(changes ...*pb.ChangeContent) (err error) {
|
||||
defer s.resetParentIdsCache()
|
||||
for _, ch := range changes {
|
||||
if err = s.applyChange(ch); err != nil {
|
||||
return
|
||||
|
@ -174,6 +175,7 @@ func (s *State) GetAndUnsetFileKeys() (keys []pb.ChangeFileKeys) {
|
|||
|
||||
// ApplyChangeIgnoreErr should be called with changes from the single pb.Change
|
||||
func (s *State) ApplyChangeIgnoreErr(changes ...*pb.ChangeContent) {
|
||||
defer s.resetParentIdsCache()
|
||||
for _, ch := range changes {
|
||||
if err := s.applyChange(ch); err != nil {
|
||||
log.With("objectID", s.RootId()).Warnf("error while applying change %T: %v; ignore", ch.Value, err)
|
||||
|
@ -389,15 +391,10 @@ func (s *State) changeBlockUpdate(update *pb.ChangeBlockUpdate) error {
|
|||
}
|
||||
|
||||
func (s *State) changeBlockMove(move *pb.ChangeBlockMove) error {
|
||||
ns := s.NewState()
|
||||
for _, id := range move.Ids {
|
||||
ns.Unlink(id)
|
||||
s.Unlink(id)
|
||||
}
|
||||
if err := ns.InsertTo(move.TargetId, move.Position, move.Ids...); err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, err := ApplyStateFastOne(ns)
|
||||
return err
|
||||
return s.InsertTo(move.TargetId, move.Position, move.Ids...)
|
||||
}
|
||||
|
||||
func (s *State) changeStoreKeySet(set *pb.ChangeStoreKeySet) error {
|
||||
|
|
|
@ -104,7 +104,7 @@ func (s *State) normalizeChildren(b simple.Block) {
|
|||
m := b.Model()
|
||||
for _, cid := range m.ChildrenIds {
|
||||
if !s.Exists(cid) {
|
||||
m.ChildrenIds = slice.RemoveMut(m.ChildrenIds, cid)
|
||||
s.setChildrenIds(m, slice.RemoveMut(m.ChildrenIds, cid))
|
||||
s.normalizeChildren(b)
|
||||
return
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ func (s *State) normalizeTree() (err error) {
|
|||
if s.Pick(headerId) != nil && slice.FindPos(s.Pick(s.RootId()).Model().ChildrenIds, headerId) != 0 {
|
||||
s.Unlink(headerId)
|
||||
root := s.Get(s.RootId()).Model()
|
||||
root.ChildrenIds = append([]string{headerId}, root.ChildrenIds...)
|
||||
s.setChildrenIds(root, append([]string{headerId}, root.ChildrenIds...))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -229,9 +229,9 @@ func (s *State) wrapChildrenToDiv(id string) (nextId string) {
|
|||
}
|
||||
|
||||
div := s.newDiv()
|
||||
div.Model().ChildrenIds = parent.ChildrenIds
|
||||
s.setChildrenIds(div.Model(), parent.ChildrenIds)
|
||||
s.Add(div)
|
||||
parent.ChildrenIds = []string{div.Model().Id}
|
||||
s.setChildrenIds(parent, []string{div.Model().Id})
|
||||
return parent.Id
|
||||
}
|
||||
|
||||
|
@ -245,9 +245,8 @@ func (s *State) divBalance(d1, d2 *model.Block) (overflow bool) {
|
|||
|
||||
d1ChildrenIds := make([]string, div)
|
||||
copy(d1ChildrenIds, d1.ChildrenIds[:div])
|
||||
|
||||
d2.ChildrenIds = d1.ChildrenIds[div:]
|
||||
d1.ChildrenIds = d1ChildrenIds
|
||||
s.setChildrenIds(d2, d1.ChildrenIds[div:])
|
||||
s.setChildrenIds(d1, d1ChildrenIds)
|
||||
return len(d2.ChildrenIds) > maxChildrenThreshold
|
||||
}
|
||||
|
||||
|
@ -305,7 +304,7 @@ func (s *State) removeDuplicates() {
|
|||
idx = idx - i
|
||||
chIds = append(chIds[:idx], chIds[idx+1:]...)
|
||||
}
|
||||
b.Model().ChildrenIds = chIds
|
||||
s.setChildrenIds(b.Model(), chIds)
|
||||
}
|
||||
handledBlocks[b.Model().Id] = struct{}{}
|
||||
return true
|
||||
|
@ -384,7 +383,7 @@ func CleanupLayouts(s *State) (removedCount int) {
|
|||
}
|
||||
}
|
||||
}
|
||||
b.Model().ChildrenIds = result
|
||||
s.setChildrenIds(b.Model(), result)
|
||||
return
|
||||
}
|
||||
cleanup(s.RootId())
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/simple"
|
||||
"github.com/anyproto/anytype-heart/core/block/simple/base"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
|
@ -57,37 +56,6 @@ func TestState_Normalize(t *testing.T) {
|
|||
assert.Empty(t, hist)
|
||||
})
|
||||
|
||||
t.Run("lastmodifieddate should not change", func(t *testing.T) {
|
||||
r := NewDoc("1", map[string]simple.Block{
|
||||
"1": base.NewBase(&model.Block{Id: "1"}),
|
||||
})
|
||||
|
||||
r.(*State).SetLastModified(1, "abc")
|
||||
|
||||
s := r.NewState()
|
||||
s.Add(simple.New(&model.Block{Id: "2"}))
|
||||
s.InsertTo("1", model.Block_Inner, "2")
|
||||
|
||||
s.SetLastModified(2, "abc")
|
||||
msgs, hist, err := ApplyState(s, true)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, msgs, 3) // BlockSetChildrenIds, BlockAdd, ObjectDetailsAmend(lastmodifieddate)
|
||||
assert.Len(t, s.changes, 1) // BlockCreate
|
||||
assert.Len(t, hist.Add, 1)
|
||||
assert.Len(t, hist.Change, 1)
|
||||
assert.Nil(t, hist.Details)
|
||||
|
||||
s = s.NewState()
|
||||
s.SetLastModified(3, "abc")
|
||||
msgs, hist, err = ApplyState(s, true)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, msgs, 0) // last modified should be reverted and not msg should be produced
|
||||
assert.Len(t, s.changes, 0)
|
||||
assert.Len(t, hist.Add, 0)
|
||||
assert.Len(t, hist.Change, 0)
|
||||
assert.Nil(t, hist.Details)
|
||||
})
|
||||
|
||||
t.Run("clean missing children", func(t *testing.T) {
|
||||
r := NewDoc("root", map[string]simple.Block{
|
||||
"root": simple.New(&model.Block{Id: "root", ChildrenIds: []string{"one"}}),
|
||||
|
@ -279,7 +247,7 @@ func TestState_Normalize(t *testing.T) {
|
|||
}
|
||||
_, _, err = ApplyState(r.NewState(), true)
|
||||
require.NoError(t, err)
|
||||
//t.Log(r.String())
|
||||
// t.Log(r.String())
|
||||
|
||||
root := r.Pick(ev.RootId).Model()
|
||||
for _, childId := range root.ChildrenIds {
|
||||
|
@ -361,7 +329,7 @@ func TestState_Normalize(t *testing.T) {
|
|||
assert.Equal(t, len(expectedRemovedIDs), expectedRemovedIDsCount)
|
||||
})
|
||||
|
||||
//t.Run("normalize size - big details", func(t *testing.T) {
|
||||
// t.Run("normalize size - big details", func(t *testing.T) {
|
||||
// //given
|
||||
// blocks := map[string]simple.Block{
|
||||
// "root": simple.New(&model.Block{
|
||||
|
@ -379,9 +347,9 @@ func TestState_Normalize(t *testing.T) {
|
|||
// //then
|
||||
// assert.Less(t, blockSizeLimit, s.blocks["root"].Model().Size())
|
||||
// assert.Error(t, err)
|
||||
//})
|
||||
// })
|
||||
//
|
||||
//t.Run("normalize size - big content", func(t *testing.T) {
|
||||
// t.Run("normalize size - big content", func(t *testing.T) {
|
||||
// //given
|
||||
// blocks := map[string]simple.Block{
|
||||
// "root": simple.New(&model.Block{
|
||||
|
@ -399,9 +367,9 @@ func TestState_Normalize(t *testing.T) {
|
|||
// //then
|
||||
// assert.Less(t, blockSizeLimit, s.blocks["root"].Model().Size())
|
||||
// assert.Error(t, err)
|
||||
//})
|
||||
// })
|
||||
//
|
||||
//t.Run("normalize size - no error", func(t *testing.T) {
|
||||
// t.Run("normalize size - no error", func(t *testing.T) {
|
||||
// //given
|
||||
// blocks := map[string]simple.Block{
|
||||
// "root": simple.New(&model.Block{
|
||||
|
@ -422,7 +390,7 @@ func TestState_Normalize(t *testing.T) {
|
|||
// //then
|
||||
// assert.Less(t, s.blocks["root"].Model().Size(), blockSizeLimit)
|
||||
// assert.NoError(t, err)
|
||||
//})
|
||||
// })
|
||||
}
|
||||
|
||||
func TestCleanupLayouts(t *testing.T) {
|
||||
|
@ -484,17 +452,17 @@ func BenchmarkNormalize(b *testing.B) {
|
|||
|
||||
func TestShortenDetailsToLimit(t *testing.T) {
|
||||
t.Run("shorten description", func(t *testing.T) {
|
||||
//given
|
||||
// given
|
||||
details := map[string]*types.Value{
|
||||
bundle.RelationKeyName.String(): pbtypes.String("my page"),
|
||||
bundle.RelationKeyDescription.String(): pbtypes.String(strings.Repeat("a", detailSizeLimit+10)),
|
||||
bundle.RelationKeyWidthInPixels.String(): pbtypes.Int64(20),
|
||||
}
|
||||
|
||||
//when
|
||||
// when
|
||||
shortenDetailsToLimit("", details)
|
||||
|
||||
//then
|
||||
// then
|
||||
assert.Len(t, details[bundle.RelationKeyName.String()].GetStringValue(), 7)
|
||||
assert.Less(t, len(details[bundle.RelationKeyDescription.String()].GetStringValue()), detailSizeLimit)
|
||||
})
|
||||
|
@ -502,25 +470,25 @@ func TestShortenDetailsToLimit(t *testing.T) {
|
|||
|
||||
func TestShortenValueOnN(t *testing.T) {
|
||||
t.Run("string value", func(t *testing.T) {
|
||||
//given
|
||||
// given
|
||||
value := pbtypes.String("abrakadabra")
|
||||
|
||||
//when
|
||||
// when
|
||||
value, left := shortenValueByN(value, 7)
|
||||
|
||||
//then
|
||||
// then
|
||||
assert.Equal(t, 0, left)
|
||||
assert.Equal(t, "abra", value.GetStringValue())
|
||||
})
|
||||
|
||||
t.Run("string list", func(t *testing.T) {
|
||||
//given
|
||||
// given
|
||||
value := pbtypes.StringList([]string{"Liberté", "Égalité", "Fraternité"})
|
||||
|
||||
//when
|
||||
// when
|
||||
value, left := shortenValueByN(value, 15)
|
||||
|
||||
//then
|
||||
// then
|
||||
expected := pbtypes.StringList([]string{"", "É", "Fraternité"})
|
||||
|
||||
assert.Equal(t, 0, left)
|
||||
|
@ -528,13 +496,13 @@ func TestShortenValueOnN(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("cut off all strings", func(t *testing.T) {
|
||||
//given
|
||||
// given
|
||||
value := pbtypes.StringList([]string{"😂", "😄", "🥰", "😔", "😰", "😥", "🥕", "🍅", "🌶"})
|
||||
|
||||
//when
|
||||
// when
|
||||
value, left := shortenValueByN(value, 100)
|
||||
|
||||
//then
|
||||
// then
|
||||
assert.Equal(t, 100-(9*4), left)
|
||||
assert.Equal(t, 0, countStringsLength(value))
|
||||
})
|
||||
|
|
|
@ -55,19 +55,20 @@ func (s *State) InsertTo(targetId string, reqPos model.BlockPosition, ids ...str
|
|||
switch reqPos {
|
||||
case model.Block_Bottom:
|
||||
pos = targetPos + 1
|
||||
targetParentM.ChildrenIds = slice.Insert(targetParentM.ChildrenIds, pos, ids...)
|
||||
s.insertChildrenIds(targetParentM, pos, ids...)
|
||||
case model.Block_Top:
|
||||
pos = targetPos
|
||||
targetParentM.ChildrenIds = slice.Insert(targetParentM.ChildrenIds, pos, ids...)
|
||||
s.insertChildrenIds(targetParentM, pos, ids...)
|
||||
case model.Block_Left, model.Block_Right:
|
||||
if err = s.moveFromSide(target, s.Get(targetParentM.Id), reqPos, ids...); err != nil {
|
||||
return
|
||||
}
|
||||
case model.Block_Inner:
|
||||
target.Model().ChildrenIds = append(target.Model().ChildrenIds, ids...)
|
||||
s.prependChildrenIds(target.Model(), ids...)
|
||||
case model.Block_Replace:
|
||||
pos = targetPos + 1
|
||||
if len(ids) > 0 && len(s.Get(ids[0]).Model().ChildrenIds) == 0 {
|
||||
id0Block := s.Get(ids[0]).Model()
|
||||
if len(ids) > 0 && len(id0Block.ChildrenIds) == 0 {
|
||||
var idsIsChild bool
|
||||
if targetChild := target.Model().ChildrenIds; len(targetChild) > 0 {
|
||||
for _, id := range ids {
|
||||
|
@ -78,13 +79,13 @@ func (s *State) InsertTo(targetId string, reqPos model.BlockPosition, ids ...str
|
|||
}
|
||||
}
|
||||
if !idsIsChild {
|
||||
s.Get(ids[0]).Model().ChildrenIds = target.Model().ChildrenIds
|
||||
s.setChildrenIds(id0Block, target.Model().ChildrenIds)
|
||||
}
|
||||
}
|
||||
targetParentM.ChildrenIds = slice.Insert(targetParentM.ChildrenIds, pos, ids...)
|
||||
s.insertChildrenIds(targetParentM, pos, ids...)
|
||||
s.Unlink(target.Model().Id)
|
||||
case model.Block_InnerFirst:
|
||||
target.Model().ChildrenIds = append(ids, target.Model().ChildrenIds...)
|
||||
s.appendChildrenIds(target.Model(), ids...)
|
||||
default:
|
||||
return fmt.Errorf("unexpected position")
|
||||
}
|
||||
|
@ -160,16 +161,7 @@ func (s *State) moveFromSide(target, parent simple.Block, pos model.BlockPositio
|
|||
}
|
||||
target = s.Get(row.Model().ChildrenIds[0])
|
||||
}
|
||||
column := simple.New(&model.Block{
|
||||
Id: "cd-" + opId,
|
||||
ChildrenIds: ids,
|
||||
Content: &model.BlockContentOfLayout{
|
||||
Layout: &model.BlockContentLayout{
|
||||
Style: model.BlockContentLayout_Column,
|
||||
},
|
||||
},
|
||||
})
|
||||
s.Add(column)
|
||||
column := s.addNewColumn(opId, ids)
|
||||
|
||||
targetPos := slice.FindPos(row.Model().ChildrenIds, target.Model().Id)
|
||||
if targetPos == -1 {
|
||||
|
@ -180,12 +172,73 @@ func (s *State) moveFromSide(target, parent simple.Block, pos model.BlockPositio
|
|||
if pos == model.Block_Right {
|
||||
columnPos += 1
|
||||
}
|
||||
row.Model().ChildrenIds = slice.Insert(row.Model().ChildrenIds, columnPos, column.Model().Id)
|
||||
s.insertChildrenIds(row.Model(), columnPos, column.Model().Id)
|
||||
s.changesStructureIgnoreIds = append(s.changesStructureIgnoreIds, "cd-"+opId, "ct-"+opId, "r-"+opId, row.Model().Id)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *State) wrapToRow(opId string, parent, b simple.Block) (row simple.Block, err error) {
|
||||
column := s.addNewBlockAndWrapToColumn(opId, b)
|
||||
row = s.addNewColumnToRow(opId, column)
|
||||
pos := slice.FindPos(parent.Model().ChildrenIds, b.Model().Id)
|
||||
if pos == -1 {
|
||||
return nil, fmt.Errorf("creating row: can't find child[%s] in given parent[%s]", b.Model().Id, parent.Model().Id)
|
||||
}
|
||||
parent.Model().ChildrenIds[pos] = row.Model().Id
|
||||
return
|
||||
}
|
||||
|
||||
func (s *State) setChildrenIds(parent *model.Block, childrenIds []string) {
|
||||
parent.ChildrenIds = childrenIds
|
||||
if s.isParentIdsCacheEnabled {
|
||||
cache := s.getParentIdsCache()
|
||||
for _, childId := range childrenIds {
|
||||
cache[childId] = parent.Id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) prependChildrenIds(block *model.Block, ids ...string) {
|
||||
s.setChildrenIds(block, append(block.ChildrenIds, ids...))
|
||||
}
|
||||
|
||||
func (s *State) appendChildrenIds(block *model.Block, ids ...string) {
|
||||
s.setChildrenIds(block, append(ids, block.ChildrenIds...))
|
||||
}
|
||||
|
||||
func (s *State) insertChildrenIds(block *model.Block, pos int, ids ...string) {
|
||||
s.setChildrenIds(block, slice.Insert(block.ChildrenIds, pos, ids...))
|
||||
}
|
||||
|
||||
func (s *State) addNewColumn(opId string, ids []string) simple.Block {
|
||||
column := simple.New(&model.Block{
|
||||
Id: "cd-" + opId,
|
||||
ChildrenIds: ids,
|
||||
Content: &model.BlockContentOfLayout{
|
||||
Layout: &model.BlockContentLayout{
|
||||
Style: model.BlockContentLayout_Column,
|
||||
},
|
||||
},
|
||||
})
|
||||
s.Add(column)
|
||||
return column
|
||||
}
|
||||
|
||||
func (s *State) addNewColumnToRow(opId string, column simple.Block) simple.Block {
|
||||
row := simple.New(&model.Block{
|
||||
Id: "r-" + opId,
|
||||
ChildrenIds: []string{column.Model().Id},
|
||||
Content: &model.BlockContentOfLayout{
|
||||
Layout: &model.BlockContentLayout{
|
||||
Style: model.BlockContentLayout_Row,
|
||||
},
|
||||
},
|
||||
})
|
||||
s.Add(row)
|
||||
return row
|
||||
}
|
||||
|
||||
func (s *State) addNewBlockAndWrapToColumn(opId string, b simple.Block) simple.Block {
|
||||
column := simple.New(&model.Block{
|
||||
Id: "ct-" + opId,
|
||||
ChildrenIds: []string{b.Model().Id},
|
||||
|
@ -196,20 +249,5 @@ func (s *State) wrapToRow(opId string, parent, b simple.Block) (row simple.Block
|
|||
},
|
||||
})
|
||||
s.Add(column)
|
||||
row = simple.New(&model.Block{
|
||||
Id: "r-" + opId,
|
||||
ChildrenIds: []string{column.Model().Id},
|
||||
Content: &model.BlockContentOfLayout{
|
||||
Layout: &model.BlockContentLayout{
|
||||
Style: model.BlockContentLayout_Row,
|
||||
},
|
||||
},
|
||||
})
|
||||
s.Add(row)
|
||||
pos := slice.FindPos(parent.Model().ChildrenIds, b.Model().Id)
|
||||
if pos == -1 {
|
||||
return nil, fmt.Errorf("creating row: can't find child[%s] in given parent[%s]", b.Model().Id, parent.Model().Id)
|
||||
}
|
||||
parent.Model().ChildrenIds[pos] = row.Model().Id
|
||||
return
|
||||
return column
|
||||
}
|
||||
|
|
|
@ -133,6 +133,8 @@ type State struct {
|
|||
groupId string
|
||||
noObjectType bool
|
||||
originalCreatedTimestamp int64 // pass here from snapshots when importing objects
|
||||
parentIdsCache map[string]string
|
||||
isParentIdsCacheEnabled bool
|
||||
}
|
||||
|
||||
func (s *State) MigrationVersion() uint32 {
|
||||
|
@ -201,6 +203,7 @@ func (s *State) Add(b simple.Block) (ok bool) {
|
|||
if s.Pick(id) == nil {
|
||||
s.blocks[id] = b
|
||||
s.blockInit(b)
|
||||
s.setChildrenIds(b.Model(), b.Model().ChildrenIds)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -210,11 +213,21 @@ func (s *State) Set(b simple.Block) {
|
|||
if !s.Exists(b.Model().Id) {
|
||||
s.Add(b)
|
||||
} else {
|
||||
s.removeFromCache(s.Pick(b.Model().Id).Model().ChildrenIds...)
|
||||
s.setChildrenIds(b.Model(), b.Model().ChildrenIds)
|
||||
s.blocks[b.Model().Id] = b
|
||||
s.blockInit(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) removeFromCache(ids ...string) {
|
||||
if s.isParentIdsCacheEnabled {
|
||||
for _, id := range ids {
|
||||
delete(s.parentIdsCache, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) Get(id string) (b simple.Block) {
|
||||
if b = s.blocks[id]; b != nil {
|
||||
return
|
||||
|
@ -269,6 +282,7 @@ func (s *State) Unlink(id string) (ok bool) {
|
|||
if parent := s.GetParentOf(id); parent != nil {
|
||||
parentM := parent.Model()
|
||||
parentM.ChildrenIds = slice.RemoveMut(parentM.ChildrenIds, id)
|
||||
s.removeFromCache(id)
|
||||
return true
|
||||
}
|
||||
return
|
||||
|
@ -308,6 +322,13 @@ func (s *State) HasParent(id, parentId string) bool {
|
|||
}
|
||||
|
||||
func (s *State) PickParentOf(id string) (res simple.Block) {
|
||||
if s.isParentIdsCacheEnabled {
|
||||
if parentId, ok := s.getParentIdsCache()[id]; ok {
|
||||
return s.Pick(parentId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
s.Iterate(func(b simple.Block) bool {
|
||||
if slice.FindPos(b.Model().ChildrenIds, id) != -1 {
|
||||
res = b
|
||||
|
@ -318,6 +339,24 @@ func (s *State) PickParentOf(id string) (res simple.Block) {
|
|||
return
|
||||
}
|
||||
|
||||
func (s *State) resetParentIdsCache() {
|
||||
s.parentIdsCache = nil
|
||||
s.isParentIdsCacheEnabled = false
|
||||
}
|
||||
|
||||
func (s *State) getParentIdsCache() map[string]string {
|
||||
if s.parentIdsCache == nil {
|
||||
s.parentIdsCache = make(map[string]string)
|
||||
s.Iterate(func(block simple.Block) bool {
|
||||
for _, id := range block.Model().ChildrenIds {
|
||||
s.parentIdsCache[id] = block.Model().Id
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return s.parentIdsCache
|
||||
}
|
||||
|
||||
func (s *State) IsChild(parentId, childId string) bool {
|
||||
for {
|
||||
parent := s.PickParentOf(childId)
|
||||
|
@ -422,6 +461,7 @@ func ApplyStateFastOne(s *State) (msgs []simple.EventMessage, action undo.Action
|
|||
}
|
||||
|
||||
func (s *State) apply(fast, one, withLayouts bool) (msgs []simple.EventMessage, action undo.Action, err error) {
|
||||
defer s.resetParentIdsCache()
|
||||
if s.parent != nil && (s.parent.parent != nil || fast) {
|
||||
s.intermediateApply()
|
||||
if one {
|
||||
|
@ -648,24 +688,6 @@ func (s *State) apply(fast, one, withLayouts bool) (msgs []simple.EventMessage,
|
|||
s.parent.relationLinks = s.relationLinks
|
||||
}
|
||||
|
||||
if len(msgs) == 0 && action.IsEmpty() && s.parent != nil {
|
||||
// revert lastModified update if we don't have any actual changes being made
|
||||
prevModifiedDate := pbtypes.Get(s.parent.LocalDetails(), bundle.RelationKeyLastModifiedDate.String())
|
||||
prevModifiedBy := pbtypes.Get(s.parent.LocalDetails(), bundle.RelationKeyLastModifiedBy.String())
|
||||
if s.localDetails != nil {
|
||||
if _, isNull := prevModifiedDate.GetKind().(*types.Value_NullValue); prevModifiedDate == nil || isNull {
|
||||
log.With("objectID", s.rootId).Debugf("failed to revert prev modifed date: prev date is nil")
|
||||
} else {
|
||||
s.localDetails.Fields[bundle.RelationKeyLastModifiedDate.String()] = prevModifiedDate
|
||||
}
|
||||
if _, isNull := prevModifiedBy.GetKind().(*types.Value_NullValue); prevModifiedBy == nil || isNull {
|
||||
log.With("objectID", s.rootId).Debugf("failed to revert prev modifed by: prev value is nil")
|
||||
} else {
|
||||
s.localDetails.Fields[bundle.RelationKeyLastModifiedBy.String()] = prevModifiedBy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.parent != nil && s.localDetails != nil {
|
||||
prev := s.parent.LocalDetails()
|
||||
if diff := pbtypes.StructDiff(prev, s.localDetails); diff != nil {
|
||||
|
@ -693,7 +715,6 @@ func (s *State) apply(fast, one, withLayouts bool) (msgs []simple.EventMessage,
|
|||
}
|
||||
|
||||
msgs = s.processTrailingDuplicatedEvents(msgs)
|
||||
|
||||
log.Debugf("middle: state apply: %d affected; %d for remove; %d copied; %d changes; for a %v", len(affectedIds), len(toRemove), len(s.blocks), len(s.changes), time.Since(st))
|
||||
return
|
||||
}
|
||||
|
|
|
@ -27,6 +27,9 @@ func (s *service) InstallBundledObjects(
|
|||
sourceObjectIds []string,
|
||||
isNewSpace bool,
|
||||
) (ids []string, objects []*types.Struct, err error) {
|
||||
if space.IsReadOnly() {
|
||||
return
|
||||
}
|
||||
|
||||
marketplaceSpace, err := s.spaceService.Get(ctx, addr.AnytypeMarketplaceWorkspace)
|
||||
if err != nil {
|
||||
|
|
|
@ -64,6 +64,7 @@ type treeSyncer struct {
|
|||
headPools map[string]*executor
|
||||
treeManager treemanager.TreeManager
|
||||
isRunning bool
|
||||
isSyncing bool
|
||||
}
|
||||
|
||||
func NewTreeSyncer(spaceId string) treesyncer.TreeSyncer {
|
||||
|
@ -80,6 +81,7 @@ func NewTreeSyncer(spaceId string) treesyncer.TreeSyncer {
|
|||
}
|
||||
|
||||
func (t *treeSyncer) Init(a *app.App) (err error) {
|
||||
t.isSyncing = true
|
||||
t.treeManager = app.MustComponent[treemanager.TreeManager](a)
|
||||
return nil
|
||||
}
|
||||
|
@ -119,6 +121,19 @@ func (t *treeSyncer) StartSync() {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *treeSyncer) StopSync() {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
t.isRunning = false
|
||||
t.isSyncing = false
|
||||
}
|
||||
|
||||
func (t *treeSyncer) ShouldSync(peerId string) bool {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
return t.isSyncing
|
||||
}
|
||||
|
||||
func (t *treeSyncer) SyncAll(ctx context.Context, peerId string, existing, missing []string) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/state"
|
||||
"github.com/anyproto/anytype-heart/pb"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/bundle"
|
||||
"github.com/anyproto/anytype-heart/pkg/lib/core/smartblock"
|
||||
)
|
||||
|
||||
type identityService interface {
|
||||
SpaceId() string
|
||||
GetDetails(ctx context.Context, identity string) (details *types.Struct, err error)
|
||||
}
|
||||
|
||||
func NewIdentity(identityService identityService, id string) (s Source) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &identity{
|
||||
identityService: identityService,
|
||||
id: id,
|
||||
closingCtx: ctx,
|
||||
closingCtxFunc: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
type identity struct {
|
||||
identityService identityService
|
||||
closingCtx context.Context
|
||||
closingCtxFunc context.CancelFunc
|
||||
id string
|
||||
}
|
||||
|
||||
func (v *identity) ListIds() ([]string, error) {
|
||||
// todo: later
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func (v *identity) ReadOnly() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (v *identity) Id() string {
|
||||
return v.id
|
||||
}
|
||||
|
||||
func (v *identity) SpaceID() string {
|
||||
return v.identityService.SpaceId()
|
||||
}
|
||||
|
||||
func (v *identity) Type() smartblock.SmartBlockType {
|
||||
return smartblock.SmartBlockTypeIdentity
|
||||
}
|
||||
|
||||
func (v *identity) detailsToState(details *types.Struct) (doc state.Doc) {
|
||||
t := state.NewDoc(v.id, nil).(*state.State)
|
||||
t.SetObjectTypeKey(bundle.TypeKeyProfile)
|
||||
t.SetDetails(details)
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (v *identity) ReadDoc(ctx context.Context, receiver ChangeReceiver, empty bool) (doc state.Doc, err error) {
|
||||
details, err := v.identityService.GetDetails(ctx, v.id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v.detailsToState(details), nil
|
||||
}
|
||||
|
||||
func (v *identity) ReadMeta(ctx context.Context, r ChangeReceiver) (doc state.Doc, err error) {
|
||||
return v.ReadDoc(ctx, r, false)
|
||||
}
|
||||
|
||||
func (v *identity) Close() (err error) {
|
||||
v.closingCtxFunc()
|
||||
return
|
||||
}
|
||||
|
||||
func (v *identity) Heads() []string {
|
||||
return []string{"todo"}
|
||||
}
|
||||
|
||||
func (s *identity) GetFileKeysSnapshot() []*pb.ChangeFileKeys {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *identity) PushChange(params PushChangeParams) (id string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (s *identity) GetCreationInfo() (creatorObjectId string, createdDate int64, err error) {
|
||||
return s.id, 0, nil
|
||||
}
|
|
@ -6,7 +6,9 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
||||
"github.com/gogo/protobuf/types"
|
||||
|
||||
"github.com/anyproto/any-sync/accountservice"
|
||||
|
@ -65,7 +67,6 @@ type service struct {
|
|||
spaceCoreService spacecore.SpaceCoreService
|
||||
storageService storage.ClientStorage
|
||||
fileService files.Service
|
||||
identityService identityService
|
||||
objectStore objectstore.ObjectStore
|
||||
fileObjectMigrator fileObjectMigrator
|
||||
|
||||
|
@ -82,7 +83,6 @@ func (s *service) Init(a *app.App) (err error) {
|
|||
s.fileStore = app.MustComponent[filestore.FileStore](a)
|
||||
s.spaceCoreService = app.MustComponent[spacecore.SpaceCoreService](a)
|
||||
s.storageService = a.MustComponent(spacestorage.CName).(storage.ClientStorage)
|
||||
s.identityService = app.MustComponent[identityService](a)
|
||||
|
||||
s.fileService = app.MustComponent[files.Service](a)
|
||||
s.objectStore = app.MustComponent[objectstore.ObjectStore](a)
|
||||
|
@ -102,7 +102,10 @@ type BuildOptions struct {
|
|||
func (b *BuildOptions) BuildTreeOpts() objecttreebuilder.BuildTreeOpts {
|
||||
return objecttreebuilder.BuildTreeOpts{
|
||||
Listener: b.Listener,
|
||||
TreeBuilder: objecttree.BuildKeyVerifiableObjectTree,
|
||||
TreeBuilder: objecttree.BuildKeyFilterableObjectTree,
|
||||
TreeValidator: func(payload treestorage.TreeStorageCreatePayload, buildFunc objecttree.BuildObjectTreeFunc, aclList list.AclList) (retPayload treestorage.TreeStorageCreatePayload, err error) {
|
||||
return objecttree.ValidateFilterRawTree(payload, aclList)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,8 +137,6 @@ func (s *service) newSource(ctx context.Context, space Space, id string, buildOp
|
|||
return NewBundledObjectType(id), nil
|
||||
case smartblock.SmartBlockTypeBundledRelation:
|
||||
return NewBundledRelation(id), nil
|
||||
case smartblock.SmartBlockTypeIdentity:
|
||||
return NewIdentity(s.identityService, id), nil
|
||||
case smartblock.SmartBlockTypeParticipant:
|
||||
participantState := state.NewDoc(id, nil).(*state.State)
|
||||
// Set object type here in order to derive value of Type relation in smartblock.Init
|
||||
|
|
|
@ -599,7 +599,6 @@ func BuildState(spaceId string, initState *state.State, ot objecttree.ReadableOb
|
|||
}
|
||||
|
||||
if lastChange != nil && !st.IsTheHeaderChange() {
|
||||
// todo: why do we don't need to set last modified for the header change?
|
||||
st.SetLastModified(lastChange.Timestamp, domain.NewParticipantId(spaceId, lastChange.Identity.Account()))
|
||||
}
|
||||
st.SetMigrationVersion(lastMigrationVersion)
|
||||
|
|
|
@ -10,31 +10,28 @@ import (
|
|||
)
|
||||
|
||||
type StaticSourceParams struct {
|
||||
Id domain.FullID
|
||||
SbType smartblock.SmartBlockType
|
||||
State *state.State
|
||||
CreatorId string
|
||||
PushChange func(p PushChangeParams) (string, error)
|
||||
Id domain.FullID
|
||||
SbType smartblock.SmartBlockType
|
||||
State *state.State
|
||||
CreatorId string
|
||||
}
|
||||
|
||||
func (s *service) NewStaticSource(params StaticSourceParams) SourceWithType {
|
||||
return &static{
|
||||
id: params.Id,
|
||||
sbType: params.SbType,
|
||||
doc: params.State,
|
||||
s: s,
|
||||
creatorId: params.CreatorId,
|
||||
pushChange: params.PushChange,
|
||||
id: params.Id,
|
||||
sbType: params.SbType,
|
||||
doc: params.State,
|
||||
s: s,
|
||||
creatorId: params.CreatorId,
|
||||
}
|
||||
}
|
||||
|
||||
type static struct {
|
||||
id domain.FullID
|
||||
sbType smartblock.SmartBlockType
|
||||
doc *state.State
|
||||
creatorId string
|
||||
pushChange func(p PushChangeParams) (string, error)
|
||||
s *service
|
||||
id domain.FullID
|
||||
sbType smartblock.SmartBlockType
|
||||
doc *state.State
|
||||
creatorId string
|
||||
s *service
|
||||
}
|
||||
|
||||
func (s *static) Id() string {
|
||||
|
@ -50,7 +47,7 @@ func (s *static) Type() smartblock.SmartBlockType {
|
|||
}
|
||||
|
||||
func (s *static) ReadOnly() bool {
|
||||
return s.pushChange == nil
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *static) ReadDoc(ctx context.Context, receiver ChangeReceiver, empty bool) (doc state.Doc, err error) {
|
||||
|
@ -58,9 +55,6 @@ func (s *static) ReadDoc(ctx context.Context, receiver ChangeReceiver, empty boo
|
|||
}
|
||||
|
||||
func (s *static) PushChange(params PushChangeParams) (id string, err error) {
|
||||
if s.pushChange != nil {
|
||||
return s.pushChange(params)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/pb/model"
|
||||
"github.com/anyproto/anytype-heart/space"
|
||||
"github.com/anyproto/anytype-heart/util/pbtypes"
|
||||
"github.com/anyproto/anytype-heart/util/slice"
|
||||
)
|
||||
|
||||
const CName = "history"
|
||||
|
@ -75,25 +74,25 @@ func (h *history) Show(id domain.FullID, versionID string) (bs *model.ObjectView
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.SetDetailAndBundledRelation(bundle.RelationKeyId, pbtypes.String(id.ObjectID))
|
||||
s.SetDetailAndBundledRelation(bundle.RelationKeySpaceId, pbtypes.String(id.SpaceID))
|
||||
typeId, err := space.GetTypeIdByKey(context.Background(), s.ObjectTypeKey())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("get type id by key: %w", err)
|
||||
}
|
||||
s.SetDetailAndBundledRelation(bundle.RelationKeyType, pbtypes.String(typeId))
|
||||
|
||||
dependentObjectIDs := objectlink.DependentObjectIDs(s, space, true, true, false, true, false)
|
||||
// nolint:errcheck
|
||||
metaD, _ := h.objectStore.QueryByID(dependentObjectIDs)
|
||||
details := make([]*model.ObjectViewDetailsSet, 0, len(metaD))
|
||||
|
||||
metaD = append(metaD, database.Record{Details: s.CombinedDetails()})
|
||||
uniqueObjTypes := s.ObjectTypeKeys()
|
||||
for _, m := range metaD {
|
||||
details = append(details, &model.ObjectViewDetailsSet{
|
||||
Id: pbtypes.GetString(m.Details, bundle.RelationKeyId.String()),
|
||||
Details: m.Details,
|
||||
})
|
||||
|
||||
if typeKey := domain.TypeKey(pbtypes.GetString(m.Details, bundle.RelationKeyType.String())); typeKey != "" {
|
||||
if slice.FindPos(uniqueObjTypes, typeKey) == -1 {
|
||||
// todo: what is happening here?
|
||||
uniqueObjTypes = append(uniqueObjTypes, typeKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relations, err := h.objectStore.FetchRelationByLinks(id.SpaceID, s.PickRelationLinks())
|
||||
|
@ -113,10 +112,6 @@ func (h *history) Versions(id domain.FullID, lastVersionId string, limit int) (r
|
|||
if limit <= 0 {
|
||||
limit = 100
|
||||
}
|
||||
profileId, profileName, err := h.getProfileInfo(id.SpaceID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var includeLastId = true
|
||||
|
||||
reverse := func(vers []*pb.RpcHistoryVersion) []*pb.RpcHistoryVersion {
|
||||
|
@ -134,11 +129,11 @@ func (h *history) Versions(id domain.FullID, lastVersionId string, limit int) (r
|
|||
var data []*pb.RpcHistoryVersion
|
||||
|
||||
e = tree.IterateFrom(tree.Root().Id, source.UnmarshalChange, func(c *objecttree.Change) (isContinue bool) {
|
||||
participantId := domain.NewParticipantId(id.SpaceID, c.Identity.Account())
|
||||
data = append(data, &pb.RpcHistoryVersion{
|
||||
Id: c.Id,
|
||||
PreviousIds: c.PreviousIds,
|
||||
AuthorId: profileId,
|
||||
AuthorName: profileName,
|
||||
AuthorId: participantId,
|
||||
Time: c.Timestamp,
|
||||
})
|
||||
return true
|
||||
|
@ -228,28 +223,13 @@ func (h *history) buildState(id domain.FullID, versionId string) (st *state.Stat
|
|||
|
||||
st.BlocksInit(st)
|
||||
if ch, e := tree.GetChange(versionId); e == nil {
|
||||
profileId, profileName, e := h.getProfileInfo(id.SpaceID)
|
||||
if e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
participantId := domain.NewParticipantId(id.SpaceID, ch.Identity.Account())
|
||||
ver = &pb.RpcHistoryVersion{
|
||||
Id: ch.Id,
|
||||
PreviousIds: ch.PreviousIds,
|
||||
AuthorId: profileId,
|
||||
AuthorName: profileName,
|
||||
AuthorId: participantId,
|
||||
Time: ch.Timestamp,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (h *history) getProfileInfo(spaceId string) (profileId, profileName string, err error) {
|
||||
profileId = h.accountService.MyParticipantId(spaceId)
|
||||
lp, err := h.accountService.ProfileInfo()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
profileName = lp.Name
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/coordinator/coordinatorclient"
|
||||
"github.com/anyproto/any-sync/identityrepo/identityrepoproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
|
@ -47,10 +46,6 @@ type Service interface {
|
|||
// UnregisterIdentitiesInSpace removes all identity observers in the space
|
||||
UnregisterIdentitiesInSpace(spaceId string)
|
||||
|
||||
// GetDetails returns the last store details of the identity and provides a way to receive updates via updateHook
|
||||
GetDetails(ctx context.Context, identity string) (details *types.Struct, err error)
|
||||
// SpaceId returns the spaceId used to store the identities in the objectStore
|
||||
SpaceId() string
|
||||
app.ComponentRunnable
|
||||
}
|
||||
|
||||
|
@ -63,19 +58,25 @@ type observer struct {
|
|||
initialized bool
|
||||
}
|
||||
|
||||
type identityRepoClient interface {
|
||||
app.Component
|
||||
IdentityRepoPut(ctx context.Context, identity string, data []*identityrepoproto.Data) (err error)
|
||||
IdentityRepoGet(ctx context.Context, identities []string, kinds []string) (res []*identityrepoproto.DataWithIdentity, err error)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
dbProvider datastore.Datastore
|
||||
db *badger.DB
|
||||
spaceService space.Service
|
||||
objectStore objectstore.ObjectStore
|
||||
accountService account.Service
|
||||
spaceIdDeriver spaceIdDeriver
|
||||
coordinatorClient coordinatorclient.CoordinatorClient
|
||||
fileAclService fileacl.Service
|
||||
closing chan struct{}
|
||||
startedCh chan struct{}
|
||||
techSpaceId string
|
||||
personalSpaceId string
|
||||
dbProvider datastore.Datastore
|
||||
db *badger.DB
|
||||
spaceService space.Service
|
||||
objectStore objectstore.ObjectStore
|
||||
accountService account.Service
|
||||
spaceIdDeriver spaceIdDeriver
|
||||
identityRepoClient identityRepoClient
|
||||
fileAclService fileacl.Service
|
||||
closing chan struct{}
|
||||
startedCh chan struct{}
|
||||
techSpaceId string
|
||||
personalSpaceId string
|
||||
|
||||
myIdentity string
|
||||
currentProfileDetailsLock sync.RWMutex
|
||||
|
@ -110,7 +111,7 @@ func (s *service) Init(a *app.App) (err error) {
|
|||
s.accountService = app.MustComponent[account.Service](a)
|
||||
s.spaceIdDeriver = app.MustComponent[spaceIdDeriver](a)
|
||||
s.spaceService = app.MustComponent[space.Service](a)
|
||||
s.coordinatorClient = app.MustComponent[coordinatorclient.CoordinatorClient](a)
|
||||
s.identityRepoClient = app.MustComponent[identityRepoClient](a)
|
||||
s.fileAclService = app.MustComponent[fileacl.Service](a)
|
||||
s.dbProvider = app.MustComponent[datastore.Datastore](a)
|
||||
return
|
||||
|
@ -152,51 +153,6 @@ func (s *service) Close(ctx context.Context) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *service) SpaceId() string {
|
||||
return s.techSpaceId
|
||||
}
|
||||
|
||||
func (s *service) GetDetails(ctx context.Context, profileId string) (details *types.Struct, err error) {
|
||||
rec, err := s.objectStore.GetDetails(profileId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rec.Details, nil
|
||||
}
|
||||
|
||||
func getDetailsFromProfile(id, spaceId string, details *types.Struct) *types.Struct {
|
||||
name := pbtypes.GetString(details, bundle.RelationKeyName.String())
|
||||
description := pbtypes.GetString(details, bundle.RelationKeyDescription.String())
|
||||
image := pbtypes.GetString(details, bundle.RelationKeyIconImage.String())
|
||||
profileId := pbtypes.GetString(details, bundle.RelationKeyId.String())
|
||||
d := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyName.String(): pbtypes.String(name),
|
||||
bundle.RelationKeyDescription.String(): pbtypes.String(description),
|
||||
bundle.RelationKeyId.String(): pbtypes.String(id),
|
||||
bundle.RelationKeyIsReadonly.String(): pbtypes.Bool(true),
|
||||
bundle.RelationKeyIsArchived.String(): pbtypes.Bool(false),
|
||||
bundle.RelationKeyIsHidden.String(): pbtypes.Bool(false),
|
||||
bundle.RelationKeySpaceId.String(): pbtypes.String(spaceId),
|
||||
bundle.RelationKeyType.String(): pbtypes.String(bundle.TypeKeyProfile.BundledURL()),
|
||||
bundle.RelationKeyIdentityProfileLink.String(): pbtypes.String(profileId),
|
||||
bundle.RelationKeyLayout.String(): pbtypes.Float64(float64(model.ObjectType_profile)),
|
||||
bundle.RelationKeyLastModifiedBy.String(): pbtypes.String(id),
|
||||
}}
|
||||
|
||||
if image != "" {
|
||||
d.Fields[bundle.RelationKeyIconImage.String()] = pbtypes.String(image)
|
||||
}
|
||||
|
||||
// deprecated, but we have existing profiles which use this, so let's it be up for clients to decide either to render it or not
|
||||
iconOption := pbtypes.Get(details, bundle.RelationKeyIconOption.String())
|
||||
if iconOption != nil {
|
||||
d.Fields[bundle.RelationKeyIconOption.String()] = iconOption
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func (s *service) runLocalProfileSubscriptions(ctx context.Context) (err error) {
|
||||
uniqueKey, err := domain.NewUniqueKey(coresb.SmartBlockTypeProfilePage, "")
|
||||
if err != nil {
|
||||
|
@ -290,9 +246,10 @@ func (s *service) cacheProfileDetails(details *types.Struct) {
|
|||
s.currentProfileDetailsLock.Unlock()
|
||||
|
||||
identityProfile := &model.IdentityProfile{
|
||||
Identity: s.myIdentity,
|
||||
Name: pbtypes.GetString(details, bundle.RelationKeyName.String()),
|
||||
IconCid: pbtypes.GetString(details, bundle.RelationKeyIconImage.String()),
|
||||
Identity: s.myIdentity,
|
||||
Name: pbtypes.GetString(details, bundle.RelationKeyName.String()),
|
||||
Description: pbtypes.GetString(details, bundle.RelationKeyDescription.String()),
|
||||
IconCid: pbtypes.GetString(details, bundle.RelationKeyIconImage.String()),
|
||||
}
|
||||
observers, ok := s.identityObservers[s.myIdentity]
|
||||
if ok {
|
||||
|
@ -323,6 +280,7 @@ func (s *service) pushProfileToIdentityRegistry(ctx context.Context) error {
|
|||
identityProfile := &model.IdentityProfile{
|
||||
Identity: identity,
|
||||
Name: pbtypes.GetString(s.currentProfileDetails, bundle.RelationKeyName.String()),
|
||||
Description: pbtypes.GetString(s.currentProfileDetails, bundle.RelationKeyDescription.String()),
|
||||
IconCid: iconCid,
|
||||
IconEncryptionKeys: iconEncryptionKeys,
|
||||
}
|
||||
|
@ -341,7 +299,7 @@ func (s *service) pushProfileToIdentityRegistry(ctx context.Context) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to sign profile data: %w", err)
|
||||
}
|
||||
err = s.coordinatorClient.IdentityRepoPut(ctx, identity, []*identityrepoproto.Data{
|
||||
err = s.identityRepoClient.IdentityRepoPut(ctx, identity, []*identityrepoproto.Data{
|
||||
{
|
||||
Kind: "profile",
|
||||
Data: data,
|
||||
|
@ -409,7 +367,7 @@ func (s *service) observeIdentities(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (s *service) getIdentitiesDataFromRepo(ctx context.Context, identities []string) ([]*identityrepoproto.DataWithIdentity, error) {
|
||||
res, err := s.coordinatorClient.IdentityRepoGet(ctx, identities, []string{identityRepoDataKind})
|
||||
res, err := s.identityRepoClient.IdentityRepoGet(ctx, identities, []string{identityRepoDataKind})
|
||||
if err == nil {
|
||||
return res, nil
|
||||
}
|
||||
|
|
|
@ -8,13 +8,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/coordinator/coordinatorclient/mock_coordinatorclient"
|
||||
"github.com/anyproto/any-sync/identityrepo/identityrepoproto"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/anytype/account/mock_account"
|
||||
"github.com/anyproto/anytype-heart/core/files/fileacl/mock_fileacl"
|
||||
|
@ -28,16 +26,15 @@ import (
|
|||
|
||||
type fixture struct {
|
||||
*service
|
||||
coordinatorClient *mock_coordinatorclient.MockCoordinatorClient
|
||||
coordinatorClient *inMemoryIdentityRepo
|
||||
}
|
||||
|
||||
const testObserverPeriod = 5 * time.Millisecond
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
ctrl := gomock.NewController(t)
|
||||
ctx := context.Background()
|
||||
|
||||
coordinatorClient := mock_coordinatorclient.NewMockCoordinatorClient(ctrl)
|
||||
identityRepoClient := newInMemoryIdentityRepo()
|
||||
objectStore := objectstore.NewStoreFixture(t)
|
||||
accountService := mock_account.NewMockService(t)
|
||||
spaceService := mock_space.NewMockService(t)
|
||||
|
@ -50,7 +47,7 @@ func newFixture(t *testing.T) *fixture {
|
|||
a.Register(&spaceIdDeriverStub{})
|
||||
a.Register(dataStore)
|
||||
a.Register(objectStore)
|
||||
a.Register(testutil.PrepareMock(ctx, a, coordinatorClient))
|
||||
a.Register(identityRepoClient)
|
||||
a.Register(testutil.PrepareMock(ctx, a, accountService))
|
||||
a.Register(testutil.PrepareMock(ctx, a, spaceService))
|
||||
a.Register(testutil.PrepareMock(ctx, a, fileAclService))
|
||||
|
@ -68,7 +65,7 @@ func newFixture(t *testing.T) *fixture {
|
|||
svcRef.db = db
|
||||
fx := &fixture{
|
||||
service: svcRef,
|
||||
coordinatorClient: coordinatorClient,
|
||||
coordinatorClient: identityRepoClient,
|
||||
}
|
||||
go fx.observeIdentitiesLoop()
|
||||
|
||||
|
@ -83,13 +80,67 @@ func marshalProfile(t *testing.T, profile *model.IdentityProfile, key crypto.Sym
|
|||
return data
|
||||
}
|
||||
|
||||
type inMemoryIdentityRepo struct {
|
||||
lock sync.Mutex
|
||||
isUnavailable bool
|
||||
identitiesData map[string]*identityrepoproto.DataWithIdentity
|
||||
}
|
||||
|
||||
func newInMemoryIdentityRepo() *inMemoryIdentityRepo {
|
||||
return &inMemoryIdentityRepo{
|
||||
identitiesData: make(map[string]*identityrepoproto.DataWithIdentity),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *inMemoryIdentityRepo) Init(a *app.App) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *inMemoryIdentityRepo) Name() (name string) {
|
||||
return "inMemoryIdentityRepo"
|
||||
}
|
||||
|
||||
func (d *inMemoryIdentityRepo) setUnavailable() {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
d.isUnavailable = true
|
||||
}
|
||||
|
||||
func (d *inMemoryIdentityRepo) IdentityRepoPut(ctx context.Context, identity string, data []*identityrepoproto.Data) (err error) {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
d.identitiesData[identity] = &identityrepoproto.DataWithIdentity{
|
||||
Identity: identity,
|
||||
Data: data,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *inMemoryIdentityRepo) IdentityRepoGet(ctx context.Context, identities []string, kinds []string) (res []*identityrepoproto.DataWithIdentity, err error) {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
if d.isUnavailable {
|
||||
return nil, fmt.Errorf("network problem")
|
||||
}
|
||||
|
||||
res = make([]*identityrepoproto.DataWithIdentity, 0, len(identities))
|
||||
for _, identity := range identities {
|
||||
if data, ok := d.identitiesData[identity]; ok {
|
||||
res = append(res, data)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestIdentityProfileCache(t *testing.T) {
|
||||
fx := newFixture(t)
|
||||
|
||||
spaceId := "space1"
|
||||
identity := "identity1"
|
||||
|
||||
fx.coordinatorClient.EXPECT().IdentityRepoGet(gomock.Any(), []string{identity}, []string{identityRepoDataKind}).Return(nil, fmt.Errorf("network problem")).AnyTimes()
|
||||
fx.coordinatorClient.setUnavailable()
|
||||
|
||||
profileSymKey, err := crypto.NewRandomAES()
|
||||
require.NoError(t, err)
|
||||
|
@ -137,43 +188,32 @@ func TestObservers(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var identitiesFromRepo []*identityrepoproto.DataWithIdentity
|
||||
|
||||
fx.coordinatorClient.EXPECT().IdentityRepoGet(gomock.Any(), []string{identity}, []string{identityRepoDataKind}).DoAndReturn(func(_ context.Context, _ []string, _ []string) ([]*identityrepoproto.DataWithIdentity, error) {
|
||||
return identitiesFromRepo, nil
|
||||
}).AnyTimes()
|
||||
time.Sleep(testObserverPeriod)
|
||||
identitiesFromRepo = []*identityrepoproto.DataWithIdentity{
|
||||
|
||||
err = fx.identityRepoClient.IdentityRepoPut(context.Background(), identity, []*identityrepoproto.Data{
|
||||
{
|
||||
Identity: identity,
|
||||
Data: []*identityrepoproto.Data{
|
||||
{
|
||||
Kind: identityRepoDataKind,
|
||||
Data: wantData,
|
||||
},
|
||||
},
|
||||
Kind: identityRepoDataKind,
|
||||
Data: wantData,
|
||||
},
|
||||
}
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("change profile's name", func(t *testing.T) {
|
||||
wantProfile2 := &model.IdentityProfile{
|
||||
Identity: identity,
|
||||
Name: "name1 edited",
|
||||
Identity: identity,
|
||||
Name: "name1 edited",
|
||||
Description: "my description",
|
||||
}
|
||||
wantData2 := marshalProfile(t, wantProfile2, profileSymKey)
|
||||
|
||||
time.Sleep(testObserverPeriod)
|
||||
identitiesFromRepo = []*identityrepoproto.DataWithIdentity{
|
||||
err = fx.identityRepoClient.IdentityRepoPut(context.Background(), identity, []*identityrepoproto.Data{
|
||||
{
|
||||
Identity: identity,
|
||||
Data: []*identityrepoproto.Data{
|
||||
{
|
||||
Kind: identityRepoDataKind,
|
||||
Data: wantData2,
|
||||
},
|
||||
},
|
||||
Kind: identityRepoDataKind,
|
||||
Data: wantData2,
|
||||
},
|
||||
}
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
|
@ -184,8 +224,9 @@ func TestObservers(t *testing.T) {
|
|||
Name: "name1",
|
||||
},
|
||||
{
|
||||
Identity: identity,
|
||||
Name: "name1 edited",
|
||||
Identity: identity,
|
||||
Name: "name1 edited",
|
||||
Description: "my description",
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantCalls, callbackCalls)
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
const (
|
||||
// ForceObjectsReindexCounter reindex thread-based objects
|
||||
ForceObjectsReindexCounter int32 = 14
|
||||
ForceObjectsReindexCounter int32 = 15
|
||||
|
||||
// ForceFilesReindexCounter reindex ipfs-file-based objects
|
||||
ForceFilesReindexCounter int32 = 11 //
|
||||
|
|
|
@ -2,8 +2,10 @@ package core
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
|
@ -14,8 +16,16 @@ import (
|
|||
)
|
||||
|
||||
func (mw *Middleware) SpaceDelete(cctx context.Context, req *pb.RpcSpaceDeleteRequest) *pb.RpcSpaceDeleteResponse {
|
||||
spaceService := mw.applicationService.GetApp().MustComponent(space.CName).(space.Service)
|
||||
err := spaceService.Delete(cctx, req.SpaceId)
|
||||
spaceService := getService[space.Service](mw)
|
||||
aclService := getService[acl.AclService](mw)
|
||||
err := aclService.Leave(cctx, req.SpaceId)
|
||||
// we check for possible error cases:
|
||||
// 1. user is an owner
|
||||
// 2. user already left a request to delete
|
||||
// 3. user is not a member of the space anymore
|
||||
if err == nil || errors.Is(err, list.ErrIsOwner) || errors.Is(err, list.ErrPendingRequest) || errors.Is(err, list.ErrNoSuchAccount) {
|
||||
err = spaceService.Delete(cctx, req.SpaceId)
|
||||
}
|
||||
code := mapErrorCode(err,
|
||||
errToCode(space.ErrSpaceDeleted, pb.RpcSpaceDeleteResponseError_SPACE_IS_DELETED),
|
||||
errToCode(space.ErrSpaceNotExists, pb.RpcSpaceDeleteResponseError_NO_SUCH_SPACE),
|
||||
|
|
|
@ -24270,6 +24270,7 @@ Used to decode block meta only, without the content itself
|
|||
| name | [string](#string) | | |
|
||||
| iconCid | [string](#string) | | |
|
||||
| iconEncryptionKeys | [FileEncryptionKey](#anytype-model-FileEncryptionKey) | repeated | |
|
||||
| description | [string](#string) | | |
|
||||
|
||||
|
||||
|
||||
|
@ -25672,6 +25673,7 @@ RelationFormat describes how the underlying data is stored in the google.protobu
|
|||
| SpaceDeleted | 7 | SpaceDeleted - the space should be deleted in the network |
|
||||
| SpaceActive | 8 | SpaceActive - the space is active in the network |
|
||||
| SpaceJoining | 9 | SpaceJoining - the account is joining the space |
|
||||
| SpaceRemoving | 10 | SpaceRemoving - the account is removing from space or the space is removed from network |
|
||||
|
||||
|
||||
|
||||
|
|
2
go.mod
2
go.mod
|
@ -7,7 +7,7 @@ require (
|
|||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
github.com/VividCortex/ewma v1.2.0
|
||||
github.com/adrium/goheif v0.0.0-20230113233934-ca402e77a786
|
||||
github.com/anyproto/any-sync v0.3.19-alpha.0.20240201153711-141ab46e389c
|
||||
github.com/anyproto/any-sync v0.3.21
|
||||
github.com/anyproto/go-naturaldate/v2 v2.0.2-0.20230524105841-9829cfd13438
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
||||
github.com/blevesearch/bleve/v2 v2.3.10
|
||||
|
|
4
go.sum
4
go.sum
|
@ -107,8 +107,8 @@ github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxB
|
|||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/anyproto/any-sync v0.3.19-alpha.0.20240201153711-141ab46e389c h1:uuOCkIINAzzyG1+ten2rhwawwyZ13DeLjt2WUebJSFI=
|
||||
github.com/anyproto/any-sync v0.3.19-alpha.0.20240201153711-141ab46e389c/go.mod h1:exw/7+W3jfkuaVWsStQD8s8aLFT9L2NBiW/DVwTNi/E=
|
||||
github.com/anyproto/any-sync v0.3.21 h1:3U/EZRvOHzSQ9ukqVqNXqFKkqLbtIOjskSBMjUgQ5h4=
|
||||
github.com/anyproto/any-sync v0.3.21/go.mod h1:ViWsWYe+jDuYip9gdp89RqvTfgHzorGypZSn+Ae0WUU=
|
||||
github.com/anyproto/badger/v4 v4.2.1-0.20240110160636-80743fa3d580 h1:Ba80IlCCxkZ9H1GF+7vFu/TSpPvbpDCxXJ5ogc4euYc=
|
||||
github.com/anyproto/badger/v4 v4.2.1-0.20240110160636-80743fa3d580/go.mod h1:T/uWAYxrXdaXw64ihI++9RMbKTCpKd/yE9+saARew7k=
|
||||
github.com/anyproto/go-chash v0.1.0 h1:I9meTPjXFRfXZHRJzjOHC/XF7Q5vzysKkiT/grsogXY=
|
||||
|
|
|
@ -64,6 +64,12 @@ func New(eventEndpoint string, key string, isCompressed bool) Service {
|
|||
}
|
||||
|
||||
func (c *Client) SendEvents(amplEvents []Event, info AppInfoProvider) error {
|
||||
if c.key == "" {
|
||||
return nil
|
||||
}
|
||||
if len(amplEvents) == 0 {
|
||||
return nil
|
||||
}
|
||||
arena := c.arenaPool.Get()
|
||||
appVersion := arena.NewString(info.GetAppVersion())
|
||||
deviceId := arena.NewString(info.GetDeviceId())
|
||||
|
|
|
@ -28,14 +28,14 @@ import (
|
|||
type Format string
|
||||
|
||||
func init() {
|
||||
image.RegisterFormat("ico", string([]byte{0x00, 0x00, 0x01, 0x00}), ico.Decode, ico.DecodeConfig)
|
||||
image.RegisterFormat("vnd.microsoft.icon", string([]byte{0x00, 0x00, 0x01, 0x00}), ico.Decode, ico.DecodeConfig)
|
||||
}
|
||||
|
||||
const (
|
||||
JPEG Format = "jpeg"
|
||||
PNG Format = "png"
|
||||
GIF Format = "gif"
|
||||
ICO Format = "ico"
|
||||
ICO Format = "vnd.microsoft.icon"
|
||||
WEBP Format = "webp"
|
||||
HEIC Format = "heic"
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -911,6 +911,8 @@ enum SpaceStatus {
|
|||
SpaceActive = 8;
|
||||
// SpaceJoining - the account is joining the space
|
||||
SpaceJoining = 9;
|
||||
// SpaceRemoving - the account is removing from space or the space is removed from network
|
||||
SpaceRemoving = 10;
|
||||
}
|
||||
|
||||
message ParticipantPermissionChange {
|
||||
|
@ -1063,6 +1065,7 @@ message IdentityProfile {
|
|||
string name = 2;
|
||||
string iconCid = 3;
|
||||
repeated FileEncryptionKey iconEncryptionKeys = 4;
|
||||
string description = 5;
|
||||
}
|
||||
|
||||
message FileInfo {
|
||||
|
|
|
@ -1289,6 +1289,51 @@ func (_c *MockSpace_IsPersonal_Call) RunAndReturn(run func() bool) *MockSpace_Is
|
|||
return _c
|
||||
}
|
||||
|
||||
// IsReadOnly provides a mock function with given fields:
|
||||
func (_m *MockSpace) IsReadOnly() bool {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IsReadOnly")
|
||||
}
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func() bool); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockSpace_IsReadOnly_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsReadOnly'
|
||||
type MockSpace_IsReadOnly_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IsReadOnly is a helper method to define mock.On call
|
||||
func (_e *MockSpace_Expecter) IsReadOnly() *MockSpace_IsReadOnly_Call {
|
||||
return &MockSpace_IsReadOnly_Call{Call: _e.mock.On("IsReadOnly")}
|
||||
}
|
||||
|
||||
func (_c *MockSpace_IsReadOnly_Call) Run(run func()) *MockSpace_IsReadOnly_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpace_IsReadOnly_Call) Return(_a0 bool) *MockSpace_IsReadOnly_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockSpace_IsReadOnly_Call) RunAndReturn(run func() bool) *MockSpace_IsReadOnly_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// LoadObjects provides a mock function with given fields: ctx, ids
|
||||
func (_m *MockSpace) LoadObjects(ctx context.Context, ids []string) error {
|
||||
ret := _m.Called(ctx, ids)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/anyproto/any-sync/commonspace/headsync"
|
||||
"github.com/anyproto/any-sync/commonspace/objecttreebuilder"
|
||||
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/types"
|
||||
|
||||
"github.com/anyproto/anytype-heart/core/block/editor/smartblock"
|
||||
|
@ -43,6 +44,7 @@ type Space interface {
|
|||
GetRelationIdByKey(ctx context.Context, key domain.RelationKey) (id string, err error)
|
||||
GetTypeIdByKey(ctx context.Context, key domain.TypeKey) (id string, err error)
|
||||
|
||||
IsReadOnly() bool
|
||||
IsPersonal() bool
|
||||
|
||||
Close(ctx context.Context) error
|
||||
|
@ -70,7 +72,8 @@ type space struct {
|
|||
spaceCore spacecore.SpaceCoreService
|
||||
personalSpaceId string
|
||||
|
||||
common commonspace.Space
|
||||
myIdentity crypto.PubKey
|
||||
common commonspace.Space
|
||||
|
||||
loadMandatoryObjectsCh chan struct{}
|
||||
loadMandatoryObjectsErr error
|
||||
|
@ -95,6 +98,7 @@ func BuildSpace(ctx context.Context, deps SpaceDeps) (Space, error) {
|
|||
common: deps.CommonSpace,
|
||||
personalSpaceId: deps.PersonalSpaceId,
|
||||
spaceCore: deps.SpaceCore,
|
||||
myIdentity: deps.AccountService.Account().SignKey.GetPublic(),
|
||||
loadMandatoryObjectsCh: make(chan struct{}),
|
||||
}
|
||||
sp.Cache = objectcache.New(deps.AccountService, deps.ObjectFactory, deps.PersonalSpaceId, sp)
|
||||
|
@ -235,3 +239,7 @@ func (s *space) InstallBundledObjects(ctx context.Context) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *space) IsReadOnly() bool {
|
||||
return !s.CommonSpace().Acl().AclState().Permissions(s.myIdentity).CanWrite()
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ func NewTechSpace(deps TechSpaceDeps) *TechSpace {
|
|||
common: deps.CommonSpace,
|
||||
loadMandatoryObjectsCh: make(chan struct{}),
|
||||
personalSpaceId: deps.PersonalSpaceId,
|
||||
myIdentity: deps.AccountService.Account().SignKey.GetPublic(),
|
||||
},
|
||||
TechSpace: deps.TechSpace,
|
||||
}
|
||||
|
|
|
@ -143,3 +143,7 @@ func (c *virtualCommonSpace) TryClose(objectTTL time.Duration) (close bool, err
|
|||
func (c *virtualCommonSpace) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *virtualCommonSpace) IsReadOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
package space
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/coordinator/coordinatorclient"
|
||||
"github.com/anyproto/any-sync/coordinator/coordinatorproto"
|
||||
"github.com/anyproto/any-sync/util/periodicsync"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/space/spaceinfo"
|
||||
)
|
||||
|
||||
const (
|
||||
loopPeriodSecs = 60
|
||||
loopTimeout = time.Second * 120
|
||||
)
|
||||
|
||||
type localDeleter interface {
|
||||
updateRemoteStatus(ctx context.Context, spaceId string, status spaceinfo.RemoteStatus) error
|
||||
allIDs() (ids []string)
|
||||
}
|
||||
|
||||
type deletionController struct {
|
||||
deleter localDeleter
|
||||
client coordinatorclient.CoordinatorClient
|
||||
|
||||
periodicCall periodicsync.PeriodicSync
|
||||
}
|
||||
|
||||
func newDeletionController(
|
||||
localDeleter localDeleter,
|
||||
client coordinatorclient.CoordinatorClient) *deletionController {
|
||||
d := &deletionController{
|
||||
deleter: localDeleter,
|
||||
client: client,
|
||||
}
|
||||
d.periodicCall = periodicsync.NewPeriodicSync(loopPeriodSecs, loopTimeout, d.loopIterate, log)
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *deletionController) Run() {
|
||||
d.periodicCall.Run()
|
||||
}
|
||||
|
||||
func (d *deletionController) Close() {
|
||||
d.periodicCall.Close()
|
||||
}
|
||||
|
||||
func (d *deletionController) loopIterate(ctx context.Context) error {
|
||||
d.updateStatuses(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *deletionController) updateStatuses(ctx context.Context) {
|
||||
ids := d.deleter.allIDs()
|
||||
remoteStatuses, err := d.client.StatusCheckMany(ctx, ids)
|
||||
if err != nil {
|
||||
log.Warn("remote status check error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
convStatus := func(status coordinatorproto.SpaceStatus) spaceinfo.RemoteStatus {
|
||||
switch status {
|
||||
case coordinatorproto.SpaceStatus_SpaceStatusCreated:
|
||||
return spaceinfo.RemoteStatusOk
|
||||
case coordinatorproto.SpaceStatus_SpaceStatusPendingDeletion:
|
||||
return spaceinfo.RemoteStatusWaitingDeletion
|
||||
default:
|
||||
return spaceinfo.RemoteStatusDeleted
|
||||
}
|
||||
}
|
||||
for idx, nodeStatus := range remoteStatuses {
|
||||
remoteStatus := convStatus(nodeStatus.Status)
|
||||
err := d.deleter.updateRemoteStatus(ctx, ids[idx], remoteStatus)
|
||||
if err != nil {
|
||||
log.Warn("remote status update error", zap.Error(err), zap.String("spaceId", ids[idx]))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
148
space/deletioncontroller/deletioncontroller.go
Normal file
148
space/deletioncontroller/deletioncontroller.go
Normal file
|
@ -0,0 +1,148 @@
|
|||
package deletioncontroller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/accountservice"
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/acl/aclclient"
|
||||
"github.com/anyproto/any-sync/commonspace/object/accountdata"
|
||||
"github.com/anyproto/any-sync/coordinator/coordinatorclient"
|
||||
"github.com/anyproto/any-sync/coordinator/coordinatorproto"
|
||||
"github.com/anyproto/any-sync/util/periodicsync"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/space/spacecore"
|
||||
"github.com/anyproto/anytype-heart/space/spaceinfo"
|
||||
)
|
||||
|
||||
const CName = "client.space.deletioncontroller"
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
const (
|
||||
loopPeriodSecs = 60
|
||||
loopTimeout = time.Second * 120
|
||||
)
|
||||
|
||||
type DeletionController interface {
|
||||
app.ComponentRunnable
|
||||
AddSpace(spaceId string)
|
||||
}
|
||||
|
||||
func New() DeletionController {
|
||||
return &deletionController{}
|
||||
}
|
||||
|
||||
type spaceManager interface {
|
||||
UpdateRemoteStatus(ctx context.Context, spaceId string, status spaceinfo.RemoteStatus, isOwned bool) error
|
||||
AllSpaceIds() (ids []string)
|
||||
}
|
||||
|
||||
type deletionController struct {
|
||||
spaceManager spaceManager
|
||||
client coordinatorclient.CoordinatorClient
|
||||
spaceCore spacecore.SpaceCoreService
|
||||
joiningClient aclclient.AclJoiningClient
|
||||
keys *accountdata.AccountKeys
|
||||
|
||||
periodicCall periodicsync.PeriodicSync
|
||||
mx sync.Mutex
|
||||
toDelete map[string]struct{}
|
||||
}
|
||||
|
||||
func (d *deletionController) Init(a *app.App) (err error) {
|
||||
d.client = a.MustComponent(coordinatorclient.CName).(coordinatorclient.CoordinatorClient)
|
||||
d.spaceCore = a.MustComponent(spacecore.CName).(spacecore.SpaceCoreService)
|
||||
d.joiningClient = a.MustComponent(aclclient.CName).(aclclient.AclJoiningClient)
|
||||
d.spaceManager = app.MustComponent[spaceManager](a)
|
||||
d.keys = a.MustComponent(accountservice.CName).(accountservice.Service).Account()
|
||||
d.periodicCall = periodicsync.NewPeriodicSync(loopPeriodSecs, loopTimeout, d.loopIterate, log)
|
||||
d.toDelete = make(map[string]struct{})
|
||||
return
|
||||
}
|
||||
|
||||
func (d *deletionController) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (d *deletionController) AddSpace(spaceId string) {
|
||||
d.mx.Lock()
|
||||
defer d.mx.Unlock()
|
||||
d.toDelete[spaceId] = struct{}{}
|
||||
}
|
||||
|
||||
func (d *deletionController) Run(ctx context.Context) error {
|
||||
d.periodicCall.Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *deletionController) Close(ctx context.Context) error {
|
||||
d.periodicCall.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *deletionController) loopIterate(ctx context.Context) error {
|
||||
ownedIds := d.updateStatuses(ctx)
|
||||
d.mx.Lock()
|
||||
var toDeleteOwnedIds []string
|
||||
for _, id := range ownedIds {
|
||||
if _, exists := d.toDelete[id]; exists {
|
||||
toDeleteOwnedIds = append(toDeleteOwnedIds, id)
|
||||
}
|
||||
}
|
||||
d.mx.Unlock()
|
||||
d.deleteOwnedSpaces(ctx, toDeleteOwnedIds)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *deletionController) updateStatuses(ctx context.Context) (ownedIds []string) {
|
||||
ids := d.spaceManager.AllSpaceIds()
|
||||
remoteStatuses, err := d.client.StatusCheckMany(ctx, ids)
|
||||
if err != nil {
|
||||
log.Warn("remote status check error", zap.Error(err))
|
||||
return
|
||||
}
|
||||
convStatus := func(status coordinatorproto.SpaceStatus) spaceinfo.RemoteStatus {
|
||||
switch status {
|
||||
case coordinatorproto.SpaceStatus_SpaceStatusCreated:
|
||||
return spaceinfo.RemoteStatusOk
|
||||
case coordinatorproto.SpaceStatus_SpaceStatusPendingDeletion:
|
||||
return spaceinfo.RemoteStatusWaitingDeletion
|
||||
default:
|
||||
return spaceinfo.RemoteStatusDeleted
|
||||
}
|
||||
}
|
||||
for idx, nodeStatus := range remoteStatuses {
|
||||
if nodeStatus.Status == coordinatorproto.SpaceStatus_SpaceStatusNotExists {
|
||||
continue
|
||||
}
|
||||
isOwned := false
|
||||
if nodeStatus.Status == coordinatorproto.SpaceStatus_SpaceStatusCreated && nodeStatus.Permissions == coordinatorproto.SpacePermissions_SpacePermissionsOwner {
|
||||
isOwned = true
|
||||
ownedIds = append(ownedIds, ids[idx])
|
||||
}
|
||||
remoteStatus := convStatus(nodeStatus.Status)
|
||||
err := d.spaceManager.UpdateRemoteStatus(ctx, ids[idx], remoteStatus, isOwned)
|
||||
if err != nil {
|
||||
log.Warn("remote status update error", zap.Error(err), zap.String("spaceId", ids[idx]))
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *deletionController) deleteOwnedSpaces(ctx context.Context, spaceIds []string) {
|
||||
for _, spaceId := range spaceIds {
|
||||
if err := d.spaceCore.Delete(ctx, spaceId); err != nil {
|
||||
log.Warn("space deletion error", zap.Error(err), zap.String("spaceId", spaceId))
|
||||
continue
|
||||
}
|
||||
d.mx.Lock()
|
||||
delete(d.toDelete, spaceId)
|
||||
d.mx.Unlock()
|
||||
}
|
||||
}
|
|
@ -147,6 +147,7 @@ func (a *aclObjectManager) initAndRegisterMyIdentity(ctx context.Context) error
|
|||
}
|
||||
details := buildParticipantDetails(id, a.sp.Id(), myIdentity, model.ParticipantPermissions_Owner, model.ParticipantStatus_Active)
|
||||
details.Fields[bundle.RelationKeyName.String()] = pbtypes.String(pbtypes.GetString(profileDetails, bundle.RelationKeyName.String()))
|
||||
details.Fields[bundle.RelationKeyDescription.String()] = pbtypes.String(pbtypes.GetString(profileDetails, bundle.RelationKeyDescription.String()))
|
||||
details.Fields[bundle.RelationKeyIconImage.String()] = pbtypes.String(pbtypes.GetString(profileDetails, bundle.RelationKeyIconImage.String()))
|
||||
details.Fields[bundle.RelationKeyIdentityProfileLink.String()] = pbtypes.String(pbtypes.GetString(profileDetails, bundle.RelationKeyId.String()))
|
||||
err = a.modifier.ModifyDetails(id, func(current *types.Struct) (*types.Struct, error) {
|
||||
|
@ -190,7 +191,7 @@ func (a *aclObjectManager) processAcl() (err error) {
|
|||
}
|
||||
return common.Acl().AclState().GetMetadata(key, true)
|
||||
}
|
||||
states := common.Acl().AclState().CurrentStates()
|
||||
states := common.Acl().AclState().CurrentAccounts()
|
||||
// decrypt all metadata
|
||||
states, err = decryptAll(states, decrypt)
|
||||
if err != nil {
|
||||
|
@ -198,7 +199,7 @@ func (a *aclObjectManager) processAcl() (err error) {
|
|||
}
|
||||
a.mx.Lock()
|
||||
defer a.mx.Unlock()
|
||||
err = a.processStates(states)
|
||||
err = a.processStates(states, common.Acl().AclState().Identity())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -206,9 +207,19 @@ func (a *aclObjectManager) processAcl() (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (a *aclObjectManager) processStates(states []list.AccountState) (err error) {
|
||||
func (a *aclObjectManager) processStates(states []list.AccountState, myIdentity crypto.PubKey) (err error) {
|
||||
var numActiveUsers int
|
||||
for _, state := range states {
|
||||
if state.Permissions.NoPermissions() && state.PubKey.Equals(myIdentity) {
|
||||
a.status.Lock()
|
||||
err := a.status.SetPersistentStatus(a.ctx, spaceinfo.AccountStatusRemoving)
|
||||
if err != nil {
|
||||
a.status.Unlock()
|
||||
return err
|
||||
}
|
||||
a.status.Unlock()
|
||||
return nil
|
||||
}
|
||||
if !(state.Permissions.IsOwner() || state.Permissions.NoPermissions()) {
|
||||
numActiveUsers++
|
||||
}
|
||||
|
@ -275,8 +286,9 @@ func (a *aclObjectManager) updateParticipantFromIdentity(ctx context.Context, id
|
|||
return err
|
||||
}
|
||||
details := &types.Struct{Fields: map[string]*types.Value{
|
||||
bundle.RelationKeyName.String(): pbtypes.String(profile.Name),
|
||||
bundle.RelationKeyIconImage.String(): pbtypes.String(profile.IconCid),
|
||||
bundle.RelationKeyName.String(): pbtypes.String(profile.Name),
|
||||
bundle.RelationKeyDescription.String(): pbtypes.String(profile.Description),
|
||||
bundle.RelationKeyIconImage.String(): pbtypes.String(profile.IconCid),
|
||||
}}
|
||||
return a.modifier.ModifyDetails(id, func(current *types.Struct) (*types.Struct, error) {
|
||||
status := pbtypes.GetInt64(current, bundle.RelationKeyParticipantStatus.String())
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/anyproto/any-sync/app/logger"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/anyproto/anytype-heart/space/deletioncontroller"
|
||||
dependencies2 "github.com/anyproto/anytype-heart/space/internal/components/dependencies"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/spacestatus"
|
||||
"github.com/anyproto/anytype-heart/space/internal/spaceprocess/mode"
|
||||
|
@ -39,6 +40,7 @@ type spaceOffloader struct {
|
|||
storageService storage.ClientStorage
|
||||
indexer dependencies2.SpaceIndexer
|
||||
spaceCore spacecore.SpaceCoreService
|
||||
delController deletioncontroller.DeletionController
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
offloaded atomic.Bool
|
||||
|
@ -50,6 +52,7 @@ func (o *spaceOffloader) Init(a *app.App) (err error) {
|
|||
o.storageService = app.MustComponent[storage.ClientStorage](a)
|
||||
o.indexer = app.MustComponent[dependencies2.SpaceIndexer](a)
|
||||
o.spaceCore = app.MustComponent[spacecore.SpaceCoreService](a)
|
||||
o.delController = app.MustComponent[deletioncontroller.DeletionController](a)
|
||||
o.ctx, o.cancel = context.WithCancel(context.Background())
|
||||
return nil
|
||||
}
|
||||
|
@ -70,14 +73,8 @@ func (o *spaceOffloader) Run(ctx context.Context) (err error) {
|
|||
}
|
||||
}
|
||||
localStatus := o.status.GetLocalStatus()
|
||||
remoteStatus := o.status.GetRemoteStatus()
|
||||
o.status.Unlock()
|
||||
if !remoteStatus.IsDeleted() {
|
||||
err := o.spaceCore.Delete(ctx, o.status.SpaceId())
|
||||
if err != nil {
|
||||
log.Debug("network delete error", zap.Error(err), zap.String("spaceId", o.status.SpaceId()))
|
||||
}
|
||||
}
|
||||
o.delController.AddSpace(o.status.SpaceId())
|
||||
if localStatus == spaceinfo.LocalStatusMissing {
|
||||
o.offloaded.Store(true)
|
||||
return nil
|
||||
|
|
68
space/internal/components/syncstopper/syncstopper.go
Normal file
68
space/internal/components/syncstopper/syncstopper.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package syncstopper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/util/periodicsync"
|
||||
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/spacestatus"
|
||||
space "github.com/anyproto/anytype-heart/space/spacecore"
|
||||
)
|
||||
|
||||
const CName = "common.components.syncstopper"
|
||||
|
||||
const (
|
||||
timeout = 30 * time.Second
|
||||
loopIntervalSecs = 30
|
||||
stopSyncTimeout = 10 * time.Minute
|
||||
)
|
||||
|
||||
var log = logger.NewNamed(CName)
|
||||
|
||||
type SyncStopper struct {
|
||||
spaceCore space.SpaceCoreService
|
||||
spaceStatus spacestatus.SpaceStatus
|
||||
periodicCall periodicsync.PeriodicSync
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func New() *SyncStopper {
|
||||
return &SyncStopper{}
|
||||
}
|
||||
|
||||
func (s *SyncStopper) Init(a *app.App) (err error) {
|
||||
s.spaceCore = a.MustComponent(space.CName).(space.SpaceCoreService)
|
||||
s.spaceStatus = a.MustComponent(spacestatus.CName).(spacestatus.SpaceStatus)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SyncStopper) Name() (name string) {
|
||||
return CName
|
||||
}
|
||||
|
||||
func (s *SyncStopper) Run(ctx context.Context) (err error) {
|
||||
s.startTime = time.Now()
|
||||
s.periodicCall = periodicsync.NewPeriodicSync(loopIntervalSecs, timeout, s.spaceCheck, log)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SyncStopper) spaceCheck(ctx context.Context) (err error) {
|
||||
sp, err := s.spaceCore.Pick(ctx, s.spaceStatus.SpaceId())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if time.Since(s.startTime) > stopSyncTimeout {
|
||||
sp.TreeSyncer().StopSync()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SyncStopper) Close(ctx context.Context) (err error) {
|
||||
if s.periodicCall != nil {
|
||||
s.periodicCall.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/anyproto/anytype-heart/space/internal/spaceprocess/loader"
|
||||
"github.com/anyproto/anytype-heart/space/internal/spaceprocess/mode"
|
||||
"github.com/anyproto/anytype-heart/space/internal/spaceprocess/offloader"
|
||||
"github.com/anyproto/anytype-heart/space/internal/spaceprocess/remover"
|
||||
"github.com/anyproto/anytype-heart/space/spaceinfo"
|
||||
)
|
||||
|
||||
|
@ -58,6 +59,9 @@ func (s *spaceController) Start(ctx context.Context) error {
|
|||
case spaceinfo.AccountStatusJoining:
|
||||
_, err := s.sm.ChangeMode(mode.ModeJoining)
|
||||
return err
|
||||
case spaceinfo.AccountStatusRemoving:
|
||||
_, err := s.sm.ChangeMode(mode.ModeRemoving)
|
||||
return err
|
||||
default:
|
||||
_, err := s.sm.ChangeMode(mode.ModeLoading)
|
||||
return err
|
||||
|
@ -85,7 +89,7 @@ func (s *spaceController) SetStatus(ctx context.Context, status spaceinfo.Accoun
|
|||
|
||||
func (s *spaceController) UpdateStatus(ctx context.Context, status spaceinfo.AccountStatus) error {
|
||||
s.status.Lock()
|
||||
if s.lastUpdatedStatus == status {
|
||||
if s.lastUpdatedStatus == status || (s.lastUpdatedStatus == spaceinfo.AccountStatusDeleted && status == spaceinfo.AccountStatusRemoving) {
|
||||
s.status.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
@ -103,6 +107,8 @@ func (s *spaceController) UpdateStatus(ctx context.Context, status spaceinfo.Acc
|
|||
return updateStatus(mode.ModeOffloading)
|
||||
case spaceinfo.AccountStatusJoining:
|
||||
return updateStatus(mode.ModeJoining)
|
||||
case spaceinfo.AccountStatusRemoving:
|
||||
return updateStatus(mode.ModeRemoving)
|
||||
default:
|
||||
return updateStatus(mode.ModeLoading)
|
||||
}
|
||||
|
@ -136,6 +142,11 @@ func (s *spaceController) Process(md mode.Mode) mode.Process {
|
|||
return offloader.New(s.app, offloader.Params{
|
||||
Status: s.status,
|
||||
})
|
||||
case mode.ModeRemoving:
|
||||
return remover.New(s.app, remover.Params{
|
||||
SpaceId: s.spaceId,
|
||||
Status: s.status,
|
||||
})
|
||||
case mode.ModeJoining:
|
||||
return joiner.New(s.app, joiner.Params{
|
||||
SpaceId: s.spaceId,
|
||||
|
|
|
@ -17,6 +17,7 @@ const (
|
|||
ModeLoading
|
||||
ModeOffloading
|
||||
ModeJoining
|
||||
ModeRemoving
|
||||
)
|
||||
|
||||
type WaitResult struct {
|
||||
|
|
|
@ -44,7 +44,7 @@ func (o *offloader) Start(ctx context.Context) error {
|
|||
}
|
||||
|
||||
func (o *offloader) CanTransition(next mode.Mode) bool {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *offloader) WaitOffload(ctx context.Context) error {
|
||||
|
|
61
space/internal/spaceprocess/remover/remover.go
Normal file
61
space/internal/spaceprocess/remover/remover.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package remover
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anyproto/any-sync/app"
|
||||
|
||||
"github.com/anyproto/anytype-heart/space/clientspace"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/aclobjectmanager"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/builder"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/spaceloader"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/spacestatus"
|
||||
"github.com/anyproto/anytype-heart/space/internal/components/syncstopper"
|
||||
"github.com/anyproto/anytype-heart/space/internal/spaceprocess/loader"
|
||||
"github.com/anyproto/anytype-heart/space/internal/spaceprocess/mode"
|
||||
)
|
||||
|
||||
type remover struct {
|
||||
app *app.App
|
||||
}
|
||||
|
||||
type Remover interface {
|
||||
mode.Process
|
||||
loader.LoadWaiter
|
||||
}
|
||||
|
||||
type Params struct {
|
||||
SpaceId string
|
||||
Status spacestatus.SpaceStatus
|
||||
StopIfMandatoryFail bool
|
||||
OwnerMetadata []byte
|
||||
}
|
||||
|
||||
func New(app *app.App, params Params) Remover {
|
||||
child := app.ChildApp()
|
||||
child.Register(params.Status).
|
||||
Register(builder.New()).
|
||||
Register(spaceloader.New(params.StopIfMandatoryFail)).
|
||||
Register(syncstopper.New()).
|
||||
Register(aclobjectmanager.New(params.OwnerMetadata))
|
||||
return &remover{
|
||||
app: child,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *remover) Start(ctx context.Context) error {
|
||||
return r.app.Start(ctx)
|
||||
}
|
||||
|
||||
func (r *remover) Close(ctx context.Context) error {
|
||||
return r.app.Close(ctx)
|
||||
}
|
||||
|
||||
func (r *remover) CanTransition(next mode.Mode) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *remover) WaitLoad(ctx context.Context) (sp clientspace.Space, err error) {
|
||||
spaceLoader := app.MustComponent[spaceloader.SpaceLoader](r.app)
|
||||
return spaceLoader.WaitLoad(ctx)
|
||||
}
|
|
@ -63,7 +63,7 @@ func (s *service) waitLoad(ctx context.Context, ctrl spacecontroller.SpaceContro
|
|||
if ld, ok := ctrl.Current().(loader.LoadWaiter); ok {
|
||||
return ld.WaitLoad(ctx)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to load space, mode is %d", ctrl.Mode())
|
||||
return nil, fmt.Errorf("failed to load space, mode is %d: %w", ctrl.Mode(), ErrFailedToLoad)
|
||||
}
|
||||
|
||||
func (s *service) loadPersonalSpace(ctx context.Context) (err error) {
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"github.com/anyproto/any-sync/app/logger"
|
||||
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
|
||||
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
|
||||
"github.com/anyproto/any-sync/coordinator/coordinatorclient"
|
||||
"github.com/anyproto/any-sync/util/crypto"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"go.uber.org/zap"
|
||||
|
@ -21,7 +20,6 @@ import (
|
|||
"github.com/anyproto/anytype-heart/pkg/lib/localstore/addr"
|
||||
"github.com/anyproto/anytype-heart/space/clientspace"
|
||||
"github.com/anyproto/anytype-heart/space/internal/spacecontroller"
|
||||
"github.com/anyproto/anytype-heart/space/internal/spaceprocess/mode"
|
||||
"github.com/anyproto/anytype-heart/space/spacecore"
|
||||
"github.com/anyproto/anytype-heart/space/spacefactory"
|
||||
"github.com/anyproto/anytype-heart/space/spaceinfo"
|
||||
|
@ -36,6 +34,7 @@ var (
|
|||
ErrSpaceNotExists = errors.New("space not exists")
|
||||
ErrSpaceDeleted = errors.New("space is deleted")
|
||||
ErrSpaceIsClosing = errors.New("space is closing")
|
||||
ErrFailedToLoad = errors.New("failed to load space")
|
||||
)
|
||||
|
||||
func New() Service {
|
||||
|
@ -68,7 +67,6 @@ type service struct {
|
|||
spaceCore spacecore.SpaceCoreService
|
||||
accountService accountservice.Service
|
||||
config *config.Config
|
||||
delController *deletionController
|
||||
|
||||
personalSpaceId string
|
||||
newAccount bool
|
||||
|
@ -104,8 +102,6 @@ func (s *service) Delete(ctx context.Context, id string) (err error) {
|
|||
|
||||
func (s *service) Init(a *app.App) (err error) {
|
||||
s.newAccount = app.MustComponent[isNewAccount](a).IsNewAccount()
|
||||
coordClient := app.MustComponent[coordinatorclient.CoordinatorClient](a)
|
||||
s.delController = newDeletionController(s, coordClient)
|
||||
s.factory = app.MustComponent[spacefactory.SpaceFactory](a)
|
||||
s.spaceCore = app.MustComponent[spacecore.SpaceCoreService](a)
|
||||
s.accountService = app.MustComponent[accountservice.Service](a)
|
||||
|
@ -156,7 +152,6 @@ func (s *service) Run(ctx context.Context) (err error) {
|
|||
}
|
||||
return fmt.Errorf("init personal space: %w", err)
|
||||
}
|
||||
s.delController.Run()
|
||||
// only persist networkId after successful space init
|
||||
err = s.config.PersistAccountNetworkId()
|
||||
if err != nil {
|
||||
|
@ -222,7 +217,7 @@ func (s *service) AccountMetadataPayload() []byte {
|
|||
return s.accountMetadataPayload
|
||||
}
|
||||
|
||||
func (s *service) updateRemoteStatus(ctx context.Context, spaceId string, status spaceinfo.RemoteStatus) error {
|
||||
func (s *service) UpdateRemoteStatus(ctx context.Context, spaceId string, status spaceinfo.RemoteStatus, isOwned bool) error {
|
||||
s.mu.Lock()
|
||||
ctrl := s.spaceControllers[spaceId]
|
||||
s.mu.Unlock()
|
||||
|
@ -233,6 +228,9 @@ func (s *service) updateRemoteStatus(ctx context.Context, spaceId string, status
|
|||
if err != nil {
|
||||
return fmt.Errorf("updateRemoteStatus: %w", err)
|
||||
}
|
||||
if !isOwned && status == spaceinfo.RemoteStatusDeleted {
|
||||
return ctrl.SetStatus(ctx, spaceinfo.AccountStatusRemoving)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -262,15 +260,14 @@ func (s *service) Close(ctx context.Context) error {
|
|||
if err != nil {
|
||||
log.Error("close tech space", zap.Error(err))
|
||||
}
|
||||
s.delController.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) allIDs() (ids []string) {
|
||||
func (s *service) AllSpaceIds() (ids []string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
for id, sc := range s.spaceControllers {
|
||||
if id == addr.AnytypeMarketplaceWorkspace || sc.Mode() != mode.ModeLoading {
|
||||
for id := range s.spaceControllers {
|
||||
if id == addr.AnytypeMarketplaceWorkspace {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, id)
|
||||
|
|
|
@ -61,6 +61,7 @@ type SpaceCoreService interface {
|
|||
DeriveID(ctx context.Context, spaceType string) (id string, err error)
|
||||
Delete(ctx context.Context, spaceID string) (err error)
|
||||
Get(ctx context.Context, id string) (*AnySpace, error)
|
||||
Pick(ctx context.Context, id string) (*AnySpace, error)
|
||||
CloseSpace(ctx context.Context, id string) error
|
||||
|
||||
StreamPool() streampool.StreamPool
|
||||
|
@ -203,6 +204,14 @@ func (s *service) Get(ctx context.Context, id string) (space *AnySpace, err erro
|
|||
return v.(*AnySpace), nil
|
||||
}
|
||||
|
||||
func (s *service) Pick(ctx context.Context, id string) (space *AnySpace, err error) {
|
||||
v, err := s.spaceCache.Pick(ctx, id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return v.(*AnySpace), nil
|
||||
}
|
||||
|
||||
func (s *service) HandleMessage(ctx context.Context, senderId string, req *spacesyncproto.ObjectSyncMessage) (err error) {
|
||||
var msg = &spacesyncproto.SpaceSubscription{}
|
||||
if err = msg.Unmarshal(req.Payload); err != nil {
|
||||
|
|
|
@ -28,10 +28,11 @@ func (r RemoteStatus) IsDeleted() bool {
|
|||
type AccountStatus int
|
||||
|
||||
const (
|
||||
AccountStatusUnknown = AccountStatus(model.SpaceStatus_Unknown)
|
||||
AccountStatusDeleted = AccountStatus(model.SpaceStatus_SpaceDeleted)
|
||||
AccountStatusJoining = AccountStatus(model.SpaceStatus_SpaceJoining)
|
||||
AccountStatusActive = AccountStatus(model.SpaceStatus_SpaceActive)
|
||||
AccountStatusUnknown = AccountStatus(model.SpaceStatus_Unknown)
|
||||
AccountStatusDeleted = AccountStatus(model.SpaceStatus_SpaceDeleted)
|
||||
AccountStatusJoining = AccountStatus(model.SpaceStatus_SpaceJoining)
|
||||
AccountStatusActive = AccountStatus(model.SpaceStatus_SpaceActive)
|
||||
AccountStatusRemoving = AccountStatus(model.SpaceStatus_SpaceRemoving)
|
||||
)
|
||||
|
||||
type SpaceLocalInfo struct {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue